translate / static /index.html
mistpe's picture
Update static/index.html
7010add verified
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Translation Assistant</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--primary-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
--secondary-gradient: linear-gradient(135deg, #f472b6 0%, #c084fc 100%);
}
body {
font-family: 'Inter', sans-serif;
background: #f8fafc;
}
.gradient-text {
background: var(--primary-gradient);
background-clip: text;
-webkit-text-fill-color: transparent;
}
.gradient-border {
position: relative;
background: linear-gradient(#fff, #fff) padding-box,
var(--primary-gradient) border-box;
border: 2px solid transparent;
border-radius: 0.75rem;
}
.glass-effect {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.nav-link {
position: relative;
transition: all 0.3s ease;
}
.nav-link::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: -2px;
left: 0;
background: var(--primary-gradient);
transition: width 0.3s ease;
}
.nav-link:hover::after {
width: 100%;
}
.floating {
animation: floating 3s ease-in-out infinite;
}
@keyframes floating {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
.progress-bar-animation {
background-size: 200% 200%;
animation: gradientMove 2s linear infinite;
}
@keyframes gradientMove {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="glass-effect fixed w-full top-0 z-50 border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<a href="#" class="flex items-center space-x-2">
<div class="w-8 h-8 rounded-lg bg-gradient-to-r from-indigo-500 to-purple-500 flex items-center justify-center">
<i class="fas fa-language text-white"></i>
</div>
<span class="text-xl font-bold gradient-text">TranslateAI</span>
</a>
</div>
<div class="hidden md:flex items-center space-x-8">
<a href="#" class="nav-link text-gray-700 hover:text-indigo-600 font-medium" data-target="text-trans">
<i class="fas fa-text-width mr-2"></i>文本翻译
</a>
<a href="#" class="nav-link text-gray-700 hover:text-indigo-600 font-medium" data-target="file-trans">
<i class="fas fa-file-alt mr-2"></i>文件翻译
</a>
<a href="#" class="nav-link text-gray-700 hover:text-indigo-600 font-medium" data-target="about">
<i class="fas fa-info-circle mr-2"></i>关于我们
</a>
</div>
<div class="flex items-center md:hidden">
<button id="mobile-menu-btn" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-bars text-xl"></i>
</button>
</div>
</div>
</div>
<!-- 移动端菜单 -->
<div id="mobile-menu" class="hidden md:hidden animated fadeIn faster">
<div class="glass-effect border-t border-gray-200 py-2">
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600" data-target="text-trans">
<i class="fas fa-text-width mr-2"></i>文本翻译
</a>
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600" data-target="file-trans">
<i class="fas fa-file-alt mr-2"></i>文件翻译
</a>
<a href="#" class="block px-4 py-3 text-gray-700 hover:bg-indigo-50 hover:text-indigo-600" data-target="about">
<i class="fas fa-info-circle mr-2"></i>关于我们
</a>
</div>
</div>
</nav>
<!-- 主要内容区域 -->
<main class="pt-20 px-4 pb-12">
<!-- 文本翻译页面 -->
<div id="text-trans" class="page max-w-7xl mx-auto">
<div class="mb-8">
<h1 class="text-3xl font-bold gradient-text mb-2">智能文本翻译</h1>
<p class="text-gray-600">支持多语言实时翻译,让沟通无国界</p>
</div>
<div class="flex flex-col lg:flex-row gap-6">
<!-- 左侧工具栏 -->
<div class="lg:w-64">
<div class="gradient-border bg-white p-6 space-y-6">
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">源语言</label>
<select id="source-lang" class="w-full rounded-lg border-gray-300 focus:border-indigo-500 focus:ring focus:ring-indigo-200">
<option value="AUTO">自动检测</option>
<option value="ZH">中文</option>
<option value="EN">英语</option>
<option value="JA">日语</option>
<option value="KO">韩语</option>
<option value="FR">法语</option>
<option value="DE">德语</option>
<option value="ES">西班牙语</option>
<option value="RU">俄语</option>
<option value="IT">意大利语</option>
<option value="PT">葡萄牙语</option>
<option value="VI">越南语</option>
<option value="ID">印尼语</option>
<option value="TH">泰语</option>
<option value="MS">马来语</option>
<option value="AR">阿拉伯语</option>
<option value="HI">印地语</option>
<!-- 其他语言选项 -->
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">目标语言</label>
<select id="target-lang" class="w-full rounded-lg border-gray-300 focus:border-indigo-500 focus:ring focus:ring-indigo-200">
<option value="ZH">中文</option>
<option value="EN">英语</option>
<option value="JA">日语</option>
<option value="KO">韩语</option>
<option value="FR">法语</option>
<option value="DE">德语</option>
<option value="ES">西班牙语</option>
<option value="RU">俄语</option>
<option value="IT">意大利语</option>
<option value="PT">葡萄牙语</option>
<option value="VI">越南语</option>
<option value="ID">印尼语</option>
<option value="TH">泰语</option>
<option value="MS">马来语</option>
<option value="AR">阿拉伯语</option>
<option value="HI">印地语</option>
<!-- 其他语言选项 -->
</select>
</div>
</div>
<button id="switch-lang" class="w-full py-2 px-4 rounded-lg border-2 border-indigo-500 text-indigo-600 hover:bg-indigo-50 transition-colors flex items-center justify-center space-x-2">
<i class="fas fa-exchange-alt"></i>
<span>切换语言</span>
</button>
</div>
</div>
<!-- 翻译区域 -->
<div class="flex-1">
<div class="grid md:grid-cols-2 gap-6">
<!-- 源文本 -->
<div class="gradient-border bg-white p-6">
<div class="mb-4 flex items-center justify-between">
<span class="text-sm font-medium text-gray-700">原文</span>
<button id="clear-text" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<textarea
id="source-text"
class="w-full h-48 p-4 border-0 focus:ring-0 resize-none rounded-lg bg-gray-50"
placeholder="请输入要翻译的文本..."
></textarea>
<div class="mt-2 flex justify-between text-sm text-gray-500">
<span id="char-count">0/10000</span>
<!-- <button class="text-indigo-600 hover:text-indigo-700">
<i class="fas fa-paste mr-1"></i>粘贴
</button> -->
</div>
</div>
<!-- 译文 -->
<div class="gradient-border bg-white p-6">
<div class="mb-4 flex items-center justify-between">
<span class="text-sm font-medium text-gray-700">译文</span>
<button id="copy-result" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-copy"></i>
</button>
</div>
<div id="target-text" class="w-full h-48 p-4 border-0 focus:ring-0 resize-none rounded-lg bg-gray-50 overflow-auto"></div>
<div class="mt-2 flex justify-end">
<div class="flex space-x-4 text-sm">
<!-- <button class="text-indigo-600 hover:text-indigo-700">
<i class="fas fa-volume-up mr-1"></i>朗读
</button>
<button class="text-indigo-600 hover:text-indigo-700">
<i class="fas fa-star mr-1"></i>收藏
</button> -->
</div>
</div>
</div>
</div>
<button id="translate-btn" class="mt-6 w-full py-4 bg-gradient-to-r from-indigo-500 to-purple-500 text-white rounded-lg hover:opacity-90 transition-opacity flex items-center justify-center space-x-2">
<i class="fas fa-language text-xl"></i>
<span class="text-lg font-medium">立即翻译</span>
</button>
</div>
</div>
</div>
<!-- 文件翻译页面 -->
<div id="file-trans" class="page max-w-7xl mx-auto hidden">
<div class="mb-8">
<h1 class="text-3xl font-bold gradient-text mb-2">文档智能翻译</h1>
<p class="text-gray-600">支持多种格式文档翻译,保留原文档排版</p>
</div>
<div class="flex flex-col lg:flex-row gap-6">
<!-- 左侧工具栏 -->
<div class="lg:w-64 space-y-6">
<!-- 语言选择 -->
<div class="gradient-border bg-white p-6 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">源语言</label>
<select id="file-source-lang" class="w-full rounded-lg border-gray-300 focus:border-indigo-500 focus:ring focus:ring-indigo-200">
<option value="AUTO">自动检测</option>
<option value="ZH">中文</option>
<option value="EN">英语</option>
<option value="JA">日语</option>
<option value="KO">韩语</option>
<option value="FR">法语</option>
<option value="DE">德语</option>
<option value="ES">西班牙语</option>
<option value="RU">俄语</option>
<option value="IT">意大利语</option>
<option value="PT">葡萄牙语</option>
<option value="VI">越南语</option>
<option value="ID">印尼语</option>
<option value="TH">泰语</option>
<option value="MS">马来语</option>
<option value="AR">阿拉伯语</option>
<option value="HI">印地语</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">目标语言</label>
<select id="file-target-lang" class="w-full rounded-lg border-gray-300 focus:border-indigo-500 focus:ring focus:ring-indigo-200">
<option value="ZH">中文</option>
<option value="EN">英语</option>
<option value="JA">日语</option>
<option value="KO">韩语</option>
<option value="FR">法语</option>
<option value="DE">德语</option>
<option value="ES">西班牙语</option>
<option value="RU">俄语</option>
<option value="IT">意大利语</option>
<option value="PT">葡萄牙语</option>
<option value="VI">越南语</option>
<option value="ID">印尼语</option>
<option value="TH">泰语</option>
<option value="MS">马来语</option>
<option value="AR">阿拉伯语</option>
<option value="HI">印地语</option>
</select>
</div>
</div>
<!-- 操作按钮 -->
<div class="gradient-border bg-white p-6 space-y-4">
<button id="upload-btn" class="w-full py-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white rounded-lg hover:opacity-90 transition-opacity flex items-center justify-center space-x-2">
<i class="fas fa-cloud-upload-alt"></i>
<span>上传文件</span>
</button>
<button id="file-translate-btn" class="w-full py-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white rounded-lg hover:opacity-90 transition-opacity flex items-center justify-center space-x-2" disabled>
<i class="fas fa-language"></i>
<span>开始翻译</span>
</button>
<button id="export-btn" class="w-full py-3 bg-gradient-to-r from-indigo-500 to-purple-500 text-white rounded-lg hover:opacity-90 transition-opacity flex items-center justify-center space-x-2" disabled>
<i class="fas fa-download"></i>
<span>导出文档</span>
</button>
</div>
<!-- 翻译进度 -->
<div id="progress-panel" class="gradient-border bg-white p-6 hidden">
<h3 class="text-sm font-medium text-gray-700 mb-4">翻译进度</h3>
<div class="space-y-4">
<div class="flex justify-between text-sm">
<span>总段落数</span>
<span id="total-segments">0</span>
</div>
<div class="flex justify-between text-sm">
<span>已翻译</span>
<span id="translated-segments">0</span>
</div>
<div class="h-2 bg-gray-100 rounded-full overflow-hidden">
<div id="progress-bar" class="h-full bg-gradient-to-r from-indigo-500 to-purple-500 progress-bar-animation w-0"></div>
</div>
</div>
</div>
</div>
<!-- 文件内容区域 -->
<div class="flex-1">
<!-- 上传区域 -->
<div id="upload-zone" class="gradient-border bg-white h-96 flex flex-col items-center justify-center cursor-pointer group">
<div class="text-center p-8 rounded-2xl">
<div class="w-20 h-20 mx-auto mb-6 floating">
<i class="fas fa-cloud-upload-alt text-6xl text-indigo-500 group-hover:text-indigo-600"></i>
</div>
<h3 class="text-xl font-medium text-gray-700 mb-2">拖放文件到这里或点击上传</h3>
<p class="text-gray-500">支持 TXT、DOCX、PDF、Markdown 格式</p>
<p class="text-sm text-gray-400 mt-2">最大支持 20MB</p>
</div>
</div>
<!-- 文档预览 -->
<div id="file-preview" class="hidden">
<div class="gradient-border bg-white">
<div class="border-b border-gray-200 p-4 flex justify-between items-center">
<div class="flex items-center space-x-3">
<i class="fas fa-file-alt text-indigo-500 text-xl"></i>
<span id="file-name" class="font-medium"></span>
</div>
<button id="close-preview" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-6">
<div class="grid md:grid-cols-2 gap-6">
<div>
<h3 class="text-sm font-medium text-gray-700 mb-4 flex items-center">
<i class="fas fa-file-alt mr-2 text-indigo-500"></i>原文
</h3>
<div id="file-content" class="space-y-4"></div>
</div>
<div>
<h3 class="text-sm font-medium text-gray-700 mb-4 flex items-center">
<i class="fas fa-language mr-2 text-indigo-500"></i>译文
</h3>
<div id="file-translation" class="space-y-4"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- About页面 -->
<div id="about" class="page max-w-7xl mx-auto hidden">
<div class="mb-12 text-center">
<h1 class="text-4xl font-bold gradient-text mb-4">关于 TranslateAI</h1>
<p class="text-xl text-gray-600 max-w-2xl mx-auto">
我们致力于打造最智能、最准确的翻译助手,让语言不再成为沟通的障碍
</p>
</div>
<!-- 特性展示 -->
<div class="grid md:grid-cols-3 gap-8 mb-16">
<div class="card-hover gradient-border bg-white p-6 rounded-xl text-center">
<div class="w-16 h-16 mx-auto mb-6 rounded-full bg-indigo-100 flex items-center justify-center">
<i class="fas fa-brain text-2xl text-indigo-600"></i>
</div>
<h3 class="text-xl font-semibold mb-4">AI 智能翻译</h3>
<p class="text-gray-600">采用先进的人工智能技术,提供准确、流畅的翻译服务</p>
</div>
<div class="card-hover gradient-border bg-white p-6 rounded-xl text-center">
<div class="w-16 h-16 mx-auto mb-6 rounded-full bg-purple-100 flex items-center justify-center">
<i class="fas fa-file-alt text-2xl text-purple-600"></i>
</div>
<h3 class="text-xl font-semibold mb-4">多格式支持</h3>
<p class="text-gray-600">支持txt、docx、pdf等多种文件格式,保留原文档排版</p>
</div>
<div class="card-hover gradient-border bg-white p-6 rounded-xl text-center">
<div class="w-16 h-16 mx-auto mb-6 rounded-full bg-pink-100 flex items-center justify-center">
<i class="fas fa-globe text-2xl text-pink-600"></i>
</div>
<h3 class="text-xl font-semibold mb-4">多语言支持</h3>
<p class="text-gray-600">支持全球100+种语言互译,满足各类翻译需求</p>
</div>
</div>
<!-- 使用统计 -->
<div class="grid md:grid-cols-4 gap-6 mb-16">
<div class="gradient-border bg-white p-6 rounded-xl text-center">
<div class="text-3xl font-bold gradient-text mb-2">100+</div>
<div class="text-gray-600">支持语言</div>
</div>
<div class="gradient-border bg-white p-6 rounded-xl text-center">
<div class="text-3xl font-bold gradient-text mb-2">99%</div>
<div class="text-gray-600">翻译准确率</div>
</div>
<div class="gradient-border bg-white p-6 rounded-xl text-center">
<div class="text-3xl font-bold gradient-text mb-2">1M+</div>
<div class="text-gray-600">用户数量</div>
</div>
<div class="gradient-border bg-white p-6 rounded-xl text-center">
<div class="text-3xl font-bold gradient-text mb-2">10M+</div>
<div class="text-gray-600">日翻译量</div>
</div>
</div>
<!-- 联系我们 -->
<div class="gradient-border bg-white p-8 rounded-xl text-center">
<h2 class="text-2xl font-bold mb-6">联系我们</h2>
<div class="flex justify-center space-x-8">
<a href="#" class="text-gray-600 hover:text-indigo-600 transition-colors">
<i class="fab fa-github text-2xl"></i>
</a>
<a href="#" class="text-gray-600 hover:text-indigo-600 transition-colors">
<i class="fab fa-twitter text-2xl"></i>
</a>
<a href="#" class="text-gray-600 hover:text-indigo-600 transition-colors">
<i class="fas fa-envelope text-2xl"></i>
</a>
</div>
</div>
</div>
</main>
<!-- 导出设置模态框 -->
<div id="export-modal" class="fixed inset-0 z-50 hidden">
<div class="absolute inset-0 bg-gray-500 bg-opacity-75"></div>
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 sm:items-center">
<div class="relative gradient-border bg-white w-full max-w-md p-6 rounded-xl">
<h3 class="text-lg font-medium text-gray-900 mb-4">导出设置</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">导出格式</label>
<select id="export-format" class="w-full rounded-lg border-gray-300 focus:border-indigo-500 focus:ring focus:ring-indigo-200">
<option value="txt">纯文本 (TXT)</option>
<option value="docx">Word文档 (DOCX)</option>
<option value="md">Markdown (MD)</option>
<option value="html">网页 (HTML)</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">导出模式</label>
<div class="space-y-2">
<label class="inline-flex items-center">
<input type="radio" name="export-mode" value="translated" class="text-indigo-600">
<span class="ml-2">仅译文</span>
</label>
<label class="inline-flex items-center ml-4">
<input type="radio" name="export-mode" value="parallel" class="text-indigo-600">
<span class="ml-2">对照模式</span>
</label>
</div>
</div>
</div>
<div class="mt-6 flex justify-end space-x-3">
<button id="cancel-export" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50">
取消
</button>
<button id="confirm-export" class="px-4 py-2 bg-gradient-to-r from-indigo-500 to-purple-500 text-white rounded-lg hover:opacity-90">
导出
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// 常量定义
const API_ENDPOINTS = {
TRANSLATE_TEXT: '/translate_text',
UPLOAD_FILE: '/upload',
TRANSLATE_FILE: '/translate',
EXPORT: '/export'
};
// 工具函数
const utils = {
showError(message) {
// 简单的错误提示
alert(message);
},
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.error('Copy failed:', err);
return false;
}
},
formatBytes(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
};
// API封装
const api = {
async translateText(text, sourceLang, targetLang) {
try {
const response = await fetch(API_ENDPOINTS.TRANSLATE_TEXT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text,
source_lang: sourceLang,
target_lang: targetLang
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Translation failed:', error);
throw error;
}
},
async uploadFile(file, sourceLang, targetLang) {
const formData = new FormData();
formData.append('file', file);
formData.append('source_lang', sourceLang);
formData.append('target_lang', targetLang);
try {
const response = await fetch(API_ENDPOINTS.UPLOAD_FILE, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('File upload failed:', error);
throw error;
}
},
async translateFile(segments, sourceLang, targetLang) {
try {
const response = await fetch(API_ENDPOINTS.TRANSLATE_FILE, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
segments,
source_lang: sourceLang,
target_lang: targetLang
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('File translation failed:', error);
throw error;
}
},
async exportDocument(segments, format, mode, sourceFileType) {
try {
const response = await fetch(API_ENDPOINTS.EXPORT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
segments,
format,
mode,
source_file_type: sourceFileType
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.blob();
} catch (error) {
console.error('Export failed:', error);
throw error;
}
}
};
// 文本翻译页面类
class TextTranslation {
constructor() {
this.sourceText = document.getElementById('source-text');
this.targetText = document.getElementById('target-text');
this.translateBtn = document.getElementById('translate-btn');
this.sourceLang = document.getElementById('source-lang');
this.targetLang = document.getElementById('target-lang');
this.charCount = document.getElementById('char-count');
this.clearBtn = document.getElementById('clear-text');
this.copyBtn = document.getElementById('copy-result');
this.switchBtn = document.getElementById('switch-lang');
this.init();
}
init() {
this.bindEvents();
this.updateCharCount();
}
bindEvents() {
this.sourceText.addEventListener('input', () => this.updateCharCount());
this.translateBtn.addEventListener('click', () => this.translate());
this.clearBtn.addEventListener('click', () => this.clearText());
this.copyBtn.addEventListener('click', () => this.copyResult());
this.switchBtn.addEventListener('click', () => this.switchLanguages());
}
updateCharCount() {
const count = this.sourceText.value.length;
this.charCount.textContent = `${count}/10000`;
this.translateBtn.disabled = count === 0;
}
clearText() {
this.sourceText.value = '';
this.targetText.textContent = '';
this.updateCharCount();
}
async copyResult() {
const success = await utils.copyToClipboard(this.targetText.textContent);
if (success) {
this.copyBtn.textContent = '已复制';
setTimeout(() => {
this.copyBtn.textContent = '复制结果';
}, 2000);
}
}
switchLanguages() {
if (this.sourceLang.value === 'auto') return;
[this.sourceLang.value, this.targetLang.value] =
[this.targetLang.value, this.sourceLang.value];
}
async translate() {
if (!this.sourceText.value.trim()) return;
this.translateBtn.disabled = true;
try {
const result = await api.translateText(
this.sourceText.value,
this.sourceLang.value,
this.targetLang.value
);
this.targetText.textContent = result.translated;
} catch (error) {
utils.showError('翻译失败,请稍后重试');
} finally {
this.translateBtn.disabled = false;
}
}
}
// 文件翻译页面类
class FileTranslation {
constructor() {
this.uploadZone = document.getElementById('upload-zone');
this.filePreview = document.getElementById('file-preview');
this.uploadBtn = document.getElementById('upload-btn');
this.translateBtn = document.getElementById('file-translate-btn');
this.exportBtn = document.getElementById('export-btn');
this.progressPanel = document.getElementById('progress-panel');
this.closePreviewBtn = document.getElementById('close-preview');
this.sourceLang = document.getElementById('file-source-lang');
this.targetLang = document.getElementById('file-target-lang');
this.currentFile = null;
this.segments = [];
this.init();
}
init() {
this.bindEvents();
this.initDragDrop();
}
bindEvents() {
this.uploadBtn.addEventListener('click', () => this.triggerFileInput());
this.translateBtn.addEventListener('click', () => this.translateFile());
this.exportBtn.addEventListener('click', () => this.showExportModal());
this.closePreviewBtn.addEventListener('click', () => this.closePreview());
// 导出模态框事件
document.getElementById('confirm-export').addEventListener('click', () => this.exportFile());
document.getElementById('cancel-export').addEventListener('click', () => this.hideExportModal());
}
initDragDrop() {
this.uploadZone.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
this.uploadZone.classList.add('border-purple-600');
});
this.uploadZone.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
this.uploadZone.classList.remove('border-purple-600');
});
this.uploadZone.addEventListener('drop', async (e) => {
e.preventDefault();
e.stopPropagation();
this.uploadZone.classList.remove('border-purple-600');
const file = e.dataTransfer.files[0];
await this.handleFile(file);
});
}
triggerFileInput() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.txt,.docx,.pdf,.md';
input.onchange = (e) => this.handleFile(e.target.files[0]);
input.click();
}
async handleFile(file) {
if (!file) return;
const validTypes = [
'text/plain',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/pdf',
'text/markdown'
];
if (!validTypes.includes(file.type)) {
utils.showError('不支持的文件格式');
return;
}
this.currentFile = file;
try {
const result = await api.uploadFile(
file,
this.sourceLang.value,
this.targetLang.value
);
this.segments = result.segments;
this.updatePreview();
this.translateBtn.disabled = false;
this.showProgressPanel();
} catch (error) {
utils.showError('文件上传失败,请重试');
}
}
updatePreview() {
this.uploadZone.classList.add('hidden');
this.filePreview.classList.remove('hidden');
document.getElementById('file-name').textContent = this.currentFile.name;
const contentDiv = document.getElementById('file-content');
contentDiv.innerHTML = this.segments.map((segment, index) => `
<div class="p-4 bg-gray-50 rounded">
<div class="text-gray-900">${segment.text}</div>
${segment.type === 'heading' ? '<div class="text-xs text-gray-500 mt-1">标题</div>' : ''}
</div>
`).join('');
}
async translateFile() {
if (!this.segments.length) return;
this.translateBtn.disabled = true;
try {
const result = await api.translateFile(
this.segments,
this.sourceLang.value,
this.targetLang.value
);
this.segments = result.segments;
this.updateTranslations();
this.exportBtn.disabled = false;
this.updateProgress();
} catch (error) {
utils.showError('翻译失败,请重试');
} finally {
this.translateBtn.disabled = false;
}
}
updateTranslations() {
const translationDiv = document.getElementById('file-translation');
translationDiv.innerHTML = this.segments.map((segment, index) => `
<div class="p-4 bg-gray-50 rounded">
<div class="text-gray-900">${segment.translated || ''}</div>
${segment.confidence ? `
<div class="mt-2 h-1 bg-gray-200 rounded-full overflow-hidden">
<div class="h-full bg-purple-600" style="width: ${segment.confidence * 100}%"></div>
</div>
` : ''}
</div>
`).join('');
}
showProgressPanel() {
this.progressPanel.classList.remove('hidden');
this.updateProgress();
}
updateProgress() {
const total = this.segments.length;
const translated = this.segments.filter(s => s.translated).length;
document.getElementById('total-segments').textContent = total;
document.getElementById('translated-segments').textContent = translated;
document.getElementById('progress-bar').style.width = `${(translated / total * 100)}%`;
}
closePreview() {
this.uploadZone.classList.remove('hidden');
this.filePreview.classList.add('hidden');
this.progressPanel.classList.add('hidden');
this.translateBtn.disabled = true;
this.exportBtn.disabled = true;
this.currentFile = null;
this.segments = [];
}
showExportModal() {
document.getElementById('export-modal').classList.remove('hidden');
}
hideExportModal() {
document.getElementById('export-modal').classList.add('hidden');
}
async exportFile() {
const formatSelect = document.getElementById('export-format');
const selectedFormat = formatSelect.value;
const mode = document.querySelector('input[name="export-mode"]:checked').value;
// 确定最终使用的导出格式
let exportFormat;
if (selectedFormat === 'auto') {
// 如果选择"自动",则使用原文件格式
exportFormat = this.currentFile.name.split('.').pop();
} else {
// 否则使用用户选择的格式
exportFormat = selectedFormat;
}
try {
const blob = await api.exportDocument(
this.segments,
exportFormat,
mode,
this.currentFile.name.split('.').pop() // 原文件类型仍然需要传递给后端
);
// 根据选择的格式确定文件扩展名
let fileExtension = exportFormat;
if (exportFormat === 'auto') {
fileExtension = this.currentFile.name.split('.').pop();
}
// 创建新的文件名
const originalName = this.currentFile.name.split('.')[0];
const newFileName = `translated_${originalName}.${fileExtension}`;
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = newFileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.hideExportModal();
} catch (error) {
utils.showError('导出失败,请重试');
}
}
}
// 页面导航控制类
class Navigation {
constructor() {
// 获取所有导航链接(包括桌面端和移动端)
this.navLinks = document.querySelectorAll('a[data-target="text-trans"], a[data-target="file-trans"], a[data-target="about"]');
this.mobileMenuBtn = document.getElementById('mobile-menu-btn');
this.mobileMenu = document.getElementById('mobile-menu');
this.pages = {
'text-trans': document.getElementById('text-trans'),
'file-trans': document.getElementById('file-trans'),
'about': document.getElementById('about')
};
this.init();
}
init() {
this.bindEvents();
// 默认显示文本翻译页面
this.switchPage('text-trans');
}
bindEvents() {
// 移动端菜单切换
this.mobileMenuBtn.addEventListener('click', () => {
this.mobileMenu.classList.toggle('hidden');
});
// 为所有导航链接添加点击事件
this.navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const target = link.getAttribute('data-target');
this.switchPage(target);
// 如果是移动端,点击后关闭菜单
if(!this.mobileMenu.classList.contains('hidden')) {
this.mobileMenu.classList.add('hidden');
}
});
});
}
switchPage(pageId) {
// 隐藏所有页面
Object.values(this.pages).forEach(page => {
page.classList.add('hidden');
});
// 显示目标页面
if(this.pages[pageId]) {
this.pages[pageId].classList.remove('hidden');
}
// 更新导航链接状态
this.navLinks.forEach(link => {
const isActive = link.getAttribute('data-target') === pageId;
link.classList.toggle('text-indigo-600', isActive);
link.classList.toggle('text-gray-700', !isActive);
});
}
}
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
new Navigation();
new TextTranslation();
new FileTranslation();
});
</script>
</body>
</html>