add word translate
This commit is contained in:
parent
d33e96e662
commit
3eeb9f66b9
91
README.md
91
README.md
@ -1,51 +1,88 @@
|
||||
# 丰链工具箱 Chrome 扩展
|
||||
|
||||
一个实用的 Chrome 扩展工具集合,目前包含二维码生成器和 Word 转 HTML 功能。
|
||||
一个集成多种实用工具的 Chrome 扩展,包括二维码生成、Word 转 HTML、Word 文档翻译等功能。
|
||||
|
||||
## 功能特性
|
||||
## 功能特点
|
||||
|
||||
### 二维码生成器 🔲
|
||||
### 1. 二维码生成
|
||||
- 自动获取当前标签页 URL 并生成二维码
|
||||
- 支持手动输入文本/链接生成二维码
|
||||
- 支持手动输入文本或链接生成二维码
|
||||
- 高清晰度二维码输出
|
||||
|
||||
### Word 转 HTML 转换器 📄
|
||||
### 2. Word 转 HTML
|
||||
- 支持 .docx 文件转换为 HTML
|
||||
- 保持原文档格式和样式
|
||||
- 实时预览转换结果
|
||||
- 保留文档格式和样式
|
||||
- 支持下载转换后的 HTML 文件
|
||||
- 一键下载 HTML 文件
|
||||
|
||||
## 安装方法
|
||||
### 3. Word 文档翻译
|
||||
- 支持中文文档翻译为英文、日文、繁体中文
|
||||
- 完整保留原文档格式(包括字体、颜色、加粗等样式)
|
||||
- 支持表格内容翻译
|
||||
- 实时显示翻译进度
|
||||
- 保持文档的换行格式
|
||||
- 自动重试机制,提高翻译稳定性
|
||||
|
||||
## 安装说明
|
||||
|
||||
### 扩展安装
|
||||
1. 下载本项目代码
|
||||
2. 打开 Chrome 浏览器,进入扩展程序页面 (chrome://extensions/)
|
||||
2. 打开 Chrome 浏览器,进入扩展管理页面 (chrome://extensions/)
|
||||
3. 开启"开发者模式"
|
||||
4. 点击"加载已解压的扩展程序"
|
||||
5. 选择项目文件夹即可完成安装
|
||||
4. 点击"加载已解压的扩展程序",选择项目目录
|
||||
|
||||
## 使用方法
|
||||
### 后端服务安装
|
||||
1. 确保已安装 Python 3.8+
|
||||
2. 进入 backend 目录
|
||||
3. 创建虚拟环境(可选):
|
||||
```bash
|
||||
python -m venv venv
|
||||
# Windows
|
||||
venv\Scripts\activate
|
||||
# Mac/Linux
|
||||
source venv/bin/activate
|
||||
```
|
||||
4. 安装依赖:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
5. 启动后端服务:
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
1. 点击 Chrome 工具栏中的扩展图标打开工具箱
|
||||
2. 选择需要使用的功能标签页
|
||||
3. 根据需要使用相应功能
|
||||
## 使用说明
|
||||
|
||||
### 二维码生成
|
||||
- 自动显示当前页面的二维码
|
||||
- 可以在输入框中输入其他文本/链接并点击"生成二维码"
|
||||
1. 点击扩展图标打开工具箱
|
||||
2. 默认显示当前页面 URL 的二维码
|
||||
3. 可在输入框中修改文本,点击"生成二维码"更新
|
||||
|
||||
### Word 转 HTML
|
||||
- 点击选择文件上传 Word 文档
|
||||
- 等待转换完成后预览效果
|
||||
- 点击"下载 HTML"保存转换结果
|
||||
1. 选择"Word转HTML"选项卡
|
||||
2. 点击上传区域选择 .docx 文件
|
||||
3. 等待转换完成后预览结果
|
||||
4. 点击"下载HTML"保存文件
|
||||
|
||||
### Word 文档翻译
|
||||
1. 选择"Word翻译"选项卡
|
||||
2. 点击上传区域选择要翻译的 Word 文档
|
||||
3. 从下拉菜单选择目标语言(英文/日文/繁体中文)
|
||||
4. 点击"开始翻译"按钮
|
||||
5. 等待翻译完成,自动下载翻译后的文档
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 使用翻译功能时需要确保后端服务正在运行
|
||||
2. 翻译功能使用百度翻译 API,请确保网络连接正常
|
||||
3. 大文件翻译可能需要较长时间,请耐心等待
|
||||
4. 建议定期清理 temp 目录下的临时文件
|
||||
|
||||
## 技术栈
|
||||
|
||||
- HTML5
|
||||
- CSS3
|
||||
- JavaScript
|
||||
- Chrome Extension API
|
||||
- QRCode.js
|
||||
- Mammoth.js
|
||||
- 前端:HTML, CSS, JavaScript
|
||||
- 后端:Python, Flask
|
||||
- 依赖库:python-docx, mammoth.js, qrcode.js
|
||||
|
||||
## 开发者
|
||||
|
||||
@ -53,4 +90,4 @@ Made by Scout
|
||||
|
||||
## 许可证
|
||||
|
||||
[添加许可证类型]
|
||||
MIT License
|
85
backend/app.py
Normal file
85
backend/app.py
Normal file
@ -0,0 +1,85 @@
|
||||
from flask import Flask, request, send_file
|
||||
from flask_cors import CORS
|
||||
import os
|
||||
from utils.docx_translator import DocxTranslator
|
||||
import shutil
|
||||
import time
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app, resources={
|
||||
r"/*": {
|
||||
"origins": ["chrome-extension://*"],
|
||||
"methods": ["POST", "OPTIONS", "GET"],
|
||||
"allow_headers": ["Content-Type"]
|
||||
}
|
||||
})
|
||||
|
||||
# 全局变量存储翻译进度
|
||||
translation_progress = 0
|
||||
|
||||
UPLOAD_FOLDER = 'temp'
|
||||
if not os.path.exists(UPLOAD_FOLDER):
|
||||
os.makedirs(UPLOAD_FOLDER)
|
||||
|
||||
def update_progress(progress):
|
||||
global translation_progress
|
||||
translation_progress = progress
|
||||
|
||||
@app.route('/progress', methods=['GET'])
|
||||
def get_progress():
|
||||
return {'progress': translation_progress}
|
||||
|
||||
@app.route('/translate', methods=['POST'])
|
||||
def translate_docx():
|
||||
global translation_progress
|
||||
translation_progress = 0
|
||||
input_path = None
|
||||
output_path = None
|
||||
|
||||
if 'file' not in request.files:
|
||||
return {'error': 'No file provided'}, 400
|
||||
|
||||
file = request.files['file']
|
||||
target_lang = request.form.get('target_lang', 'en')
|
||||
|
||||
if file.filename == '':
|
||||
return {'error': 'No file selected'}, 400
|
||||
|
||||
try:
|
||||
timestamp = str(int(time.time()))
|
||||
safe_filename = f"{timestamp}_{file.filename}"
|
||||
input_path = os.path.join(UPLOAD_FOLDER, safe_filename)
|
||||
file.save(input_path)
|
||||
|
||||
translator = DocxTranslator()
|
||||
output_path = translator.translate_document(
|
||||
input_path,
|
||||
target_lang=target_lang,
|
||||
progress_callback=update_progress
|
||||
)
|
||||
|
||||
temp_output = output_path + '.tmp'
|
||||
shutil.copy2(output_path, temp_output)
|
||||
|
||||
return send_file(
|
||||
temp_output,
|
||||
as_attachment=True,
|
||||
download_name=f"translated_{file.filename}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return {'error': str(e)}, 500
|
||||
|
||||
finally:
|
||||
try:
|
||||
if input_path and os.path.exists(input_path):
|
||||
os.remove(input_path)
|
||||
if output_path and os.path.exists(output_path):
|
||||
os.remove(output_path)
|
||||
if 'temp_output' in locals() and os.path.exists(temp_output):
|
||||
os.remove(temp_output)
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up files: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, port=5000)
|
5
backend/requirements.txt
Normal file
5
backend/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
flask==2.3.3
|
||||
werkzeug==2.3.7
|
||||
flask-cors==4.0.0
|
||||
python-docx==0.8.11
|
||||
requests==2.31.0
|
BIN
backend/utils/__pycache__/docx_translator.cpython-310.pyc
Normal file
BIN
backend/utils/__pycache__/docx_translator.cpython-310.pyc
Normal file
Binary file not shown.
244
backend/utils/docx_translator.py
Normal file
244
backend/utils/docx_translator.py
Normal file
@ -0,0 +1,244 @@
|
||||
from docx import Document
|
||||
import hashlib
|
||||
import time
|
||||
import requests
|
||||
import os
|
||||
|
||||
class DocxTranslator:
|
||||
def __init__(self):
|
||||
self.appid = "20241112002200806"
|
||||
self.secret_key = "preM0becByYCdotRTP_a"
|
||||
self.api_url = "https://api.fanyi.baidu.com/api/trans/vip/translate"
|
||||
self.max_length = 2000
|
||||
self.lang_map = {
|
||||
'en': 'en', # English
|
||||
'jp': 'jp', # Japanese
|
||||
'cht': 'cht' # Traditional Chinese
|
||||
}
|
||||
|
||||
def _get_sign(self, text, salt):
|
||||
sign_str = f"{self.appid}{text}{salt}{self.secret_key}"
|
||||
return hashlib.md5(sign_str.encode()).hexdigest()
|
||||
|
||||
def translate_text(self, text, to_lang):
|
||||
target_lang = self.lang_map.get(to_lang, to_lang)
|
||||
|
||||
if len(text) > self.max_length:
|
||||
segments = text.split('。')
|
||||
current_segment = ''
|
||||
translated_segments = []
|
||||
|
||||
for segment in segments:
|
||||
if len(current_segment) + len(segment) < self.max_length:
|
||||
current_segment += segment + '。'
|
||||
else:
|
||||
if current_segment:
|
||||
translated_segments.append(self._translate_segment(current_segment, target_lang))
|
||||
current_segment = segment + '。'
|
||||
|
||||
if current_segment:
|
||||
translated_segments.append(self._translate_segment(current_segment, target_lang))
|
||||
|
||||
return ''.join(translated_segments)
|
||||
else:
|
||||
return self._translate_segment(text, target_lang)
|
||||
|
||||
def _translate_segment(self, text, to_lang):
|
||||
if not text.strip():
|
||||
return text
|
||||
|
||||
salt = str(int(time.time()))
|
||||
sign = self._get_sign(text, salt)
|
||||
|
||||
params = {
|
||||
'q': text,
|
||||
'from': 'zh',
|
||||
'to': to_lang,
|
||||
'appid': self.appid,
|
||||
'salt': salt,
|
||||
'sign': sign
|
||||
}
|
||||
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = requests.get(self.api_url, params=params)
|
||||
result = response.json()
|
||||
|
||||
if 'error_code' in result:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(1)
|
||||
continue
|
||||
raise Exception(f"Translation error: {result['error_msg']}")
|
||||
|
||||
return result['trans_result'][0]['dst']
|
||||
|
||||
except Exception as e:
|
||||
if attempt < max_retries - 1:
|
||||
time.sleep(1)
|
||||
continue
|
||||
raise e
|
||||
|
||||
def translate_document(self, input_path, target_lang='en', progress_callback=None):
|
||||
doc = Document(input_path)
|
||||
output_path = input_path.replace('.docx', f'_translated.docx')
|
||||
|
||||
try:
|
||||
# 计算总翻译项
|
||||
total_items = len(doc.paragraphs)
|
||||
for table in doc.tables:
|
||||
total_items += sum(len(row.cells) for row in table.rows)
|
||||
|
||||
current_item = 0
|
||||
|
||||
# 翻译段落
|
||||
for paragraph in doc.paragraphs:
|
||||
if paragraph.text.strip():
|
||||
try:
|
||||
# 保存原始的段落格式和换行
|
||||
runs = paragraph.runs
|
||||
original_runs = []
|
||||
|
||||
# 收集每个run的文本和格式信息,保留原始换行符
|
||||
for run in runs:
|
||||
text = run.text
|
||||
if text: # 不去除空白字符,保留原始格式
|
||||
original_runs.append({
|
||||
'text': text,
|
||||
'bold': run.bold,
|
||||
'italic': run.italic,
|
||||
'underline': run.underline,
|
||||
'font_name': run.font.name,
|
||||
'font_size': run.font.size,
|
||||
'color_rgb': run.font.color.rgb if run.font.color else None,
|
||||
'break_type': 'break' if any(text.endswith(x) for x in ['\n', '\v', '\r']) else None
|
||||
})
|
||||
|
||||
# 清除原有内容但保持段落格式
|
||||
paragraph.clear()
|
||||
|
||||
# 分别翻译和添加每个run的文本
|
||||
for i, orig_run in enumerate(original_runs):
|
||||
if orig_run['text']:
|
||||
# 保留换行符
|
||||
has_break = orig_run['break_type'] == 'break'
|
||||
text_to_translate = orig_run['text'].rstrip('\n\r\v')
|
||||
|
||||
# 翻译非空文本
|
||||
if text_to_translate.strip():
|
||||
translated_text = self.translate_text(text_to_translate, target_lang)
|
||||
else:
|
||||
translated_text = text_to_translate
|
||||
|
||||
# 创建新的run并应用格式
|
||||
new_run = paragraph.add_run()
|
||||
new_run.bold = orig_run['bold']
|
||||
new_run.italic = orig_run['italic']
|
||||
new_run.underline = orig_run['underline']
|
||||
new_run.font.name = orig_run['font_name']
|
||||
if orig_run['font_size']:
|
||||
new_run.font.size = orig_run['font_size']
|
||||
if orig_run['color_rgb']:
|
||||
new_run.font.color.rgb = orig_run['color_rgb']
|
||||
|
||||
# 添加翻译后的文本
|
||||
new_run.text = translated_text
|
||||
|
||||
# 如果原文有换行,添加换行符
|
||||
if has_break:
|
||||
new_run.add_break() # 不指定类型,使用默认换行
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error translating paragraph: {e}")
|
||||
# 保留原文和格式
|
||||
paragraph.clear()
|
||||
for run in runs:
|
||||
new_run = paragraph.add_run(run.text)
|
||||
new_run.bold = run.bold
|
||||
new_run.italic = run.italic
|
||||
new_run.underline = run.underline
|
||||
new_run.font.name = run.font.name
|
||||
if run.font.size:
|
||||
new_run.font.size = run.font.size
|
||||
if run.font.color and run.font.color.rgb:
|
||||
new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
current_item += 1
|
||||
if progress_callback:
|
||||
progress_callback(int((current_item / total_items) * 100))
|
||||
|
||||
# 翻译表格 (使用相同的逻辑处理表格单元格)
|
||||
for table in doc.tables:
|
||||
for row in table.rows:
|
||||
for cell in row.cells:
|
||||
if cell.text.strip():
|
||||
try:
|
||||
for paragraph in cell.paragraphs:
|
||||
runs = paragraph.runs
|
||||
original_runs = []
|
||||
|
||||
for run in runs:
|
||||
text = run.text
|
||||
if text:
|
||||
original_runs.append({
|
||||
'text': text,
|
||||
'bold': run.bold,
|
||||
'italic': run.italic,
|
||||
'underline': run.underline,
|
||||
'font_name': run.font.name,
|
||||
'font_size': run.font.size,
|
||||
'color_rgb': run.font.color.rgb if run.font.color else None,
|
||||
'break_type': 'break' if any(text.endswith(x) for x in ['\n', '\v', '\r']) else None
|
||||
})
|
||||
|
||||
paragraph.clear()
|
||||
|
||||
for i, orig_run in enumerate(original_runs):
|
||||
if orig_run['text']:
|
||||
has_break = orig_run['break_type'] == 'break'
|
||||
text_to_translate = orig_run['text'].rstrip('\n\r\v')
|
||||
|
||||
if text_to_translate.strip():
|
||||
translated_text = self.translate_text(text_to_translate, target_lang)
|
||||
else:
|
||||
translated_text = text_to_translate
|
||||
|
||||
new_run = paragraph.add_run()
|
||||
new_run.bold = orig_run['bold']
|
||||
new_run.italic = orig_run['italic']
|
||||
new_run.underline = orig_run['underline']
|
||||
new_run.font.name = orig_run['font_name']
|
||||
if orig_run['font_size']:
|
||||
new_run.font.size = orig_run['font_size']
|
||||
if orig_run['color_rgb']:
|
||||
new_run.font.color.rgb = orig_run['color_rgb']
|
||||
|
||||
new_run.text = translated_text
|
||||
|
||||
if has_break:
|
||||
new_run.add_break()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error translating cell: {e}")
|
||||
# 保留原文和格式
|
||||
for paragraph in cell.paragraphs:
|
||||
paragraph.clear()
|
||||
for run in runs:
|
||||
new_run = paragraph.add_run(run.text)
|
||||
new_run.bold = run.bold
|
||||
new_run.italic = run.italic
|
||||
new_run.underline = run.underline
|
||||
new_run.font.name = run.font.name
|
||||
if run.font.size:
|
||||
new_run.font.size = run.font.size
|
||||
if run.font.color and run.font.color.rgb:
|
||||
new_run.font.color.rgb = run.font.color.rgb
|
||||
|
||||
current_item += 1
|
||||
if progress_callback:
|
||||
progress_callback(int((current_item / total_items) * 100))
|
||||
|
||||
doc.save(output_path)
|
||||
return output_path
|
||||
finally:
|
||||
del doc
|
@ -11,5 +11,10 @@
|
||||
"action": {
|
||||
"default_popup": "src/html/popup.html"
|
||||
},
|
||||
"permissions": ["activeTab"]
|
||||
"permissions": [
|
||||
"activeTab"
|
||||
],
|
||||
"host_permissions": [
|
||||
"http://localhost:5000/*"
|
||||
]
|
||||
}
|
@ -25,8 +25,9 @@ h1 {
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 5px;
|
||||
margin-bottom: 20px;
|
||||
background: #f5f5f5;
|
||||
padding: 5px;
|
||||
@ -34,19 +35,24 @@ h1 {
|
||||
}
|
||||
|
||||
.tab button {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
padding: 8px 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.tab button .icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tab button:hover {
|
||||
@ -203,11 +209,10 @@ button:hover {
|
||||
|
||||
.status-message {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
animation: fadeIn 0.3s ease;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-message.success {
|
||||
@ -242,3 +247,81 @@ button:hover {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 添加新的样式 */
|
||||
.language-select {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.language-select select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.translate-btn {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.translate-btn:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
/* 添加进度条样式 */
|
||||
.progress-container {
|
||||
margin: 15px 0;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
height: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #1a73e8;
|
||||
width: 0;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
/* 添加进度文本样式 */
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 修改选项卡文字样式 */
|
||||
.tab button[data-tab="qr"] {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tab button[data-tab="qr"]::after {
|
||||
content: "二维码生成";
|
||||
}
|
||||
|
||||
.tab button[data-tab="word"] {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tab button[data-tab="word"]::after {
|
||||
content: "Word转HTML";
|
||||
}
|
||||
|
||||
.tab button[data-tab="translate"] {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tab button[data-tab="translate"]::after {
|
||||
content: "Word翻译";
|
||||
}
|
@ -12,11 +12,12 @@
|
||||
<div class="tab">
|
||||
<button class="tablinks active" data-tab="qr">
|
||||
<span class="icon">🔲</span>
|
||||
二维码生成
|
||||
</button>
|
||||
<button class="tablinks" data-tab="word">
|
||||
<span class="icon">📄</span>
|
||||
Word转HTML
|
||||
</button>
|
||||
<button class="tablinks" data-tab="translate">
|
||||
<span class="icon">🔄</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -43,6 +44,32 @@
|
||||
下载HTML
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="translate" class="tabcontent" style="display:none;">
|
||||
<div class="file-upload">
|
||||
<label for="wordTranslateFile" class="file-label">
|
||||
<span class="icon">📎</span>
|
||||
<span id="translateFileNameDisplay">选择Word文件</span>
|
||||
</label>
|
||||
<input type="file" id="wordTranslateFile" accept=".docx" hidden>
|
||||
</div>
|
||||
<div class="language-select">
|
||||
<select id="targetLang">
|
||||
<option value="en">英文</option>
|
||||
<option value="jp">日文</option>
|
||||
<option value="cht">繁体中文</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="translateProgress" class="progress-container">
|
||||
<div class="progress-bar"></div>
|
||||
</div>
|
||||
<div id="progressText" class="progress-text"></div>
|
||||
<div id="translateStatus" class="status-message"></div>
|
||||
<button id="translateBtn" class="translate-btn" style="display:none;">
|
||||
<span class="icon">🔄</span>
|
||||
开始翻译
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../js/popup.js"></script>
|
||||
</body>
|
||||
|
150
src/js/popup.js
150
src/js/popup.js
@ -13,24 +13,41 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const tabs = document.querySelectorAll('.tablinks');
|
||||
const tabContents = document.querySelectorAll('.tabcontent');
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
const tabName = e.target.getAttribute('data-tab');
|
||||
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
function switchTab(tabName) {
|
||||
// 移除所有标签的激活状态
|
||||
tabs.forEach(tab => tab.classList.remove('active'));
|
||||
|
||||
// 隐藏所有内容
|
||||
tabContents.forEach(content => {
|
||||
content.style.display = 'none';
|
||||
});
|
||||
|
||||
// 激活选中的标签
|
||||
const selectedTab = document.querySelector(`[data-tab="${tabName}"]`);
|
||||
if (selectedTab) {
|
||||
selectedTab.classList.add('active');
|
||||
}
|
||||
|
||||
// 显示对应内容
|
||||
const selectedContent = document.getElementById(tabName);
|
||||
if (selectedContent) {
|
||||
selectedContent.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个标签添加点击事件
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
const tabName = e.currentTarget.getAttribute('data-tab');
|
||||
if (tabName) {
|
||||
switchTab(tabName);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 初始化显示第一个标签页
|
||||
switchTab('qr');
|
||||
|
||||
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
|
||||
if (tabs[0] && tabs[0].url) {
|
||||
urlInput.value = tabs[0].url;
|
||||
@ -131,4 +148,125 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
saveAs: true
|
||||
});
|
||||
});
|
||||
|
||||
// 添加百度翻译API配置
|
||||
const BAIDU_APPID = '20241112002200806';
|
||||
const BAIDU_KEY = 'preM0becByYCdotRTP_a';
|
||||
|
||||
let wordTranslateFile = null;
|
||||
|
||||
document.getElementById('wordTranslateFile').addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
wordTranslateFile = file;
|
||||
document.getElementById('translateFileNameDisplay').textContent = file.name;
|
||||
document.getElementById('translateBtn').style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('translateBtn').addEventListener('click', async () => {
|
||||
if (!wordTranslateFile) return;
|
||||
|
||||
const translateBtn = document.getElementById('translateBtn');
|
||||
const translateStatus = document.getElementById('translateStatus');
|
||||
const progressBar = document.querySelector('.progress-bar');
|
||||
const progressContainer = document.querySelector('.progress-container');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
// 禁用翻译按钮
|
||||
translateBtn.disabled = true;
|
||||
translateBtn.style.opacity = '0.6';
|
||||
translateBtn.textContent = '翻译中...';
|
||||
|
||||
translateStatus.textContent = '正在处理文档...';
|
||||
progressContainer.style.display = 'block';
|
||||
progressText.style.display = 'block';
|
||||
progressBar.style.width = '0%';
|
||||
|
||||
// 进度检查变量
|
||||
let lastProgress = 0;
|
||||
let noProgressCount = 0;
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', wordTranslateFile);
|
||||
formData.append('target_lang', document.getElementById('targetLang').value);
|
||||
|
||||
// 开始翻译请求
|
||||
const response = await fetch('http://localhost:5000/translate', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
// 定时检查进度
|
||||
const progressCheck = setInterval(async () => {
|
||||
try {
|
||||
const progressResponse = await fetch('http://localhost:5000/progress');
|
||||
const progressData = await progressResponse.json();
|
||||
|
||||
// 检查进度是否停滞
|
||||
if (progressData.progress === lastProgress) {
|
||||
noProgressCount++;
|
||||
if (noProgressCount > 10) { // 5秒无进度更新
|
||||
clearInterval(progressCheck);
|
||||
throw new Error('翻译进度停滞,请重试');
|
||||
}
|
||||
} else {
|
||||
noProgressCount = 0;
|
||||
lastProgress = progressData.progress;
|
||||
}
|
||||
|
||||
progressBar.style.width = `${progressData.progress}%`;
|
||||
progressText.textContent = `翻译进度: ${progressData.progress}%`;
|
||||
} catch (error) {
|
||||
clearInterval(progressCheck);
|
||||
throw error;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
if (!response.ok) {
|
||||
clearInterval(progressCheck);
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || '翻译失败');
|
||||
}
|
||||
|
||||
// 下载翻译后的文件
|
||||
const blob = await response.blob();
|
||||
clearInterval(progressCheck);
|
||||
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `translated_${wordTranslateFile.name}`;
|
||||
a.click();
|
||||
|
||||
translateStatus.textContent = '翻译完成!';
|
||||
progressBar.style.width = '100%';
|
||||
|
||||
// 重置按钮状态
|
||||
setTimeout(() => {
|
||||
translateBtn.disabled = false;
|
||||
translateBtn.style.opacity = '1';
|
||||
translateBtn.innerHTML = '<span class="icon">🔄</span>开始翻译';
|
||||
progressContainer.style.display = 'none';
|
||||
progressText.style.display = 'none';
|
||||
}, 2000);
|
||||
|
||||
} catch (error) {
|
||||
translateStatus.textContent = '翻译失败:' + error.message;
|
||||
progressContainer.style.display = 'none';
|
||||
progressText.style.display = 'none';
|
||||
|
||||
// 重置按钮状态
|
||||
translateBtn.disabled = false;
|
||||
translateBtn.style.opacity = '1';
|
||||
translateBtn.innerHTML = '<span class="icon">🔄</span>开始翻译';
|
||||
}
|
||||
});
|
||||
|
||||
// MD5函数实现
|
||||
function MD5(string) {
|
||||
// 实现MD5加密
|
||||
// 可以使用第三方库如crypto-js
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user