452 lines
12 KiB
JavaScript
Executable File
452 lines
12 KiB
JavaScript
Executable File
// 全局变量
|
||
let supportedLanguages = {};
|
||
let isTranslating = false;
|
||
|
||
// DOM元素
|
||
const elements = {
|
||
fromLang: null,
|
||
toLang: null,
|
||
inputText: null,
|
||
outputText: null,
|
||
translateBtn: null,
|
||
swapBtn: null,
|
||
clearBtn: null,
|
||
copyBtn: null,
|
||
charCount: null,
|
||
detectedLang: null,
|
||
targetLang: null,
|
||
pronounceSection: null
|
||
};
|
||
|
||
// 初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializeElements();
|
||
loadSupportedLanguages();
|
||
bindEvents();
|
||
updateCharCount();
|
||
});
|
||
|
||
// 初始化DOM元素
|
||
function initializeElements() {
|
||
elements.fromLang = document.getElementById('from-lang');
|
||
elements.toLang = document.getElementById('to-lang');
|
||
elements.inputText = document.getElementById('input-text');
|
||
elements.outputText = document.getElementById('output-text');
|
||
elements.translateBtn = document.getElementById('translate-btn');
|
||
elements.swapBtn = document.getElementById('swap-btn');
|
||
elements.clearBtn = document.getElementById('clear-btn');
|
||
elements.copyBtn = document.getElementById('copy-btn');
|
||
elements.charCount = document.getElementById('char-count');
|
||
elements.detectedLang = document.getElementById('detected-lang');
|
||
elements.targetLang = document.getElementById('target-lang');
|
||
elements.pronounceSection = document.getElementById('pronounce-section');
|
||
}
|
||
|
||
// 加载支持的语言列表
|
||
async function loadSupportedLanguages() {
|
||
try {
|
||
const response = await fetch('https://60s.viki.moe/v2/fanyi/langs');
|
||
const data = await response.json();
|
||
|
||
if (data.code === 200 && data.data && Array.isArray(data.data)) {
|
||
// 转换数组格式为对象格式
|
||
supportedLanguages = {};
|
||
supportedLanguages['auto'] = '自动检测';
|
||
data.data.forEach(lang => {
|
||
supportedLanguages[lang.code] = lang.label;
|
||
});
|
||
populateLanguageSelectors();
|
||
} else {
|
||
throw new Error('获取语言列表失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('加载语言列表失败:', error);
|
||
showToast('加载语言列表失败,请刷新页面重试', 'error');
|
||
// 使用默认语言列表
|
||
useDefaultLanguages();
|
||
}
|
||
}
|
||
|
||
// 使用默认语言列表(备用方案)
|
||
function useDefaultLanguages() {
|
||
supportedLanguages = {
|
||
'auto': '自动检测',
|
||
'zh-CHS': '中文',
|
||
'en': '英语',
|
||
'ja': '日语',
|
||
'ko': '韩语',
|
||
'fr': '法语',
|
||
'de': '德语',
|
||
'es': '西班牙语',
|
||
'ru': '俄语',
|
||
'th': '泰语',
|
||
'ar': '阿拉伯语',
|
||
'pt': '葡萄牙语',
|
||
'it': '意大利语'
|
||
};
|
||
populateLanguageSelectors();
|
||
}
|
||
|
||
// 填充语言选择器
|
||
function populateLanguageSelectors() {
|
||
const fromSelect = elements.fromLang;
|
||
const toSelect = elements.toLang;
|
||
|
||
// 清空现有选项
|
||
fromSelect.innerHTML = '';
|
||
toSelect.innerHTML = '';
|
||
|
||
// 添加语言选项
|
||
Object.entries(supportedLanguages).forEach(([code, name]) => {
|
||
const fromOption = new Option(name, code);
|
||
const toOption = new Option(name, code);
|
||
|
||
fromSelect.appendChild(fromOption);
|
||
toSelect.appendChild(toOption);
|
||
});
|
||
|
||
// 设置默认值
|
||
fromSelect.value = 'auto';
|
||
toSelect.value = 'en';
|
||
|
||
// 如果没有auto选项,则设置为中文
|
||
if (!supportedLanguages['auto']) {
|
||
fromSelect.value = 'zh-CHS';
|
||
}
|
||
}
|
||
|
||
// 绑定事件
|
||
function bindEvents() {
|
||
// 输入框事件
|
||
elements.inputText.addEventListener('input', function() {
|
||
updateCharCount();
|
||
clearOutput();
|
||
});
|
||
|
||
elements.inputText.addEventListener('keydown', function(e) {
|
||
if (e.ctrlKey && e.key === 'Enter') {
|
||
translateText();
|
||
}
|
||
});
|
||
|
||
// 按钮事件
|
||
elements.translateBtn.addEventListener('click', translateText);
|
||
elements.swapBtn.addEventListener('click', swapLanguages);
|
||
elements.clearBtn.addEventListener('click', clearInput);
|
||
elements.copyBtn.addEventListener('click', copyOutput);
|
||
|
||
// 语言选择器事件
|
||
elements.fromLang.addEventListener('change', function() {
|
||
clearOutput();
|
||
updateLanguageLabels();
|
||
});
|
||
|
||
elements.toLang.addEventListener('change', function() {
|
||
clearOutput();
|
||
updateLanguageLabels();
|
||
});
|
||
}
|
||
|
||
// 更新字符计数
|
||
function updateCharCount() {
|
||
const text = elements.inputText.value;
|
||
const count = text.length;
|
||
elements.charCount.textContent = `${count}/5000`;
|
||
|
||
if (count > 5000) {
|
||
elements.charCount.style.color = '#e74c3c';
|
||
} else {
|
||
elements.charCount.style.color = '#74c69d';
|
||
}
|
||
}
|
||
|
||
// 更新语言标签
|
||
function updateLanguageLabels() {
|
||
const fromLang = elements.fromLang.value;
|
||
const toLang = elements.toLang.value;
|
||
|
||
elements.detectedLang.textContent = supportedLanguages[fromLang] || '未知语言';
|
||
elements.targetLang.textContent = supportedLanguages[toLang] || '未知语言';
|
||
}
|
||
|
||
// 翻译文本
|
||
async function translateText() {
|
||
const text = elements.inputText.value.trim();
|
||
|
||
if (!text) {
|
||
showToast('请输入要翻译的文本', 'error');
|
||
return;
|
||
}
|
||
|
||
if (text.length > 5000) {
|
||
showToast('文本长度不能超过5000字符', 'error');
|
||
return;
|
||
}
|
||
|
||
if (isTranslating) {
|
||
return;
|
||
}
|
||
|
||
setTranslating(true);
|
||
|
||
try {
|
||
const fromLang = elements.fromLang.value;
|
||
const toLang = elements.toLang.value;
|
||
|
||
// 构建请求URL
|
||
const params = new URLSearchParams({
|
||
text: text,
|
||
from: fromLang,
|
||
to: toLang
|
||
});
|
||
|
||
const response = await fetch(`https://60s.viki.moe/v2/fanyi?${params}`);
|
||
const data = await response.json();
|
||
|
||
if (data.code === 200 && data.data) {
|
||
displayTranslationResult(data.data);
|
||
} else {
|
||
throw new Error(data.msg || '翻译失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('翻译失败:', error);
|
||
showToast('翻译失败: ' + error.message, 'error');
|
||
elements.outputText.textContent = '翻译失败,请重试';
|
||
} finally {
|
||
setTranslating(false);
|
||
}
|
||
}
|
||
|
||
// 显示翻译结果
|
||
function displayTranslationResult(data) {
|
||
// 显示翻译结果
|
||
const translation = data.target ? data.target.text : '';
|
||
elements.outputText.textContent = translation;
|
||
|
||
// 更新检测到的语言
|
||
if (data.source && data.source.type_desc) {
|
||
elements.detectedLang.textContent = `检测: ${data.source.type_desc}`;
|
||
}
|
||
|
||
// 显示发音信息
|
||
displayPronunciation(data);
|
||
|
||
// 如果翻译结果为空
|
||
if (!translation) {
|
||
elements.outputText.textContent = '未获取到翻译结果';
|
||
}
|
||
}
|
||
|
||
// 显示发音信息
|
||
function displayPronunciation(data) {
|
||
const pronounceSection = elements.pronounceSection;
|
||
if (!pronounceSection) {
|
||
return;
|
||
}
|
||
pronounceSection.innerHTML = '';
|
||
|
||
// 原文发音
|
||
if (data.source && data.source.pronounce) {
|
||
const sourcePhoneticDiv = document.createElement('div');
|
||
sourcePhoneticDiv.className = 'pronounce-item show';
|
||
sourcePhoneticDiv.textContent = `原文发音: [${data.source.pronounce}]`;
|
||
pronounceSection.appendChild(sourcePhoneticDiv);
|
||
}
|
||
|
||
// 译文发音
|
||
if (data.target && data.target.pronounce) {
|
||
const targetPhoneticDiv = document.createElement('div');
|
||
targetPhoneticDiv.className = 'pronounce-item show';
|
||
targetPhoneticDiv.textContent = `译文发音: [${data.target.pronounce}]`;
|
||
pronounceSection.appendChild(targetPhoneticDiv);
|
||
}
|
||
}
|
||
|
||
// 设置翻译状态
|
||
function setTranslating(translating) {
|
||
isTranslating = translating;
|
||
elements.translateBtn.disabled = translating;
|
||
|
||
if (translating) {
|
||
elements.translateBtn.classList.add('loading');
|
||
} else {
|
||
elements.translateBtn.classList.remove('loading');
|
||
}
|
||
}
|
||
|
||
// 交换语言
|
||
function swapLanguages() {
|
||
const fromValue = elements.fromLang.value;
|
||
const toValue = elements.toLang.value;
|
||
|
||
// 不能交换自动检测
|
||
if (fromValue === 'auto') {
|
||
showToast('自动检测语言无法交换', 'error');
|
||
return;
|
||
}
|
||
|
||
elements.fromLang.value = toValue;
|
||
elements.toLang.value = fromValue;
|
||
|
||
// 交换文本内容
|
||
const inputText = elements.inputText.value;
|
||
const outputText = elements.outputText.textContent;
|
||
|
||
if (outputText && outputText !== '翻译结果将在这里显示...' && outputText !== '翻译失败,请重试' && outputText !== '未获取到翻译结果') {
|
||
elements.inputText.value = outputText;
|
||
elements.outputText.textContent = inputText;
|
||
}
|
||
|
||
updateCharCount();
|
||
updateLanguageLabels();
|
||
clearPronunciation();
|
||
}
|
||
|
||
// 清空输入
|
||
function clearInput() {
|
||
elements.inputText.value = '';
|
||
updateCharCount();
|
||
clearOutput();
|
||
}
|
||
|
||
// 清空输出
|
||
function clearOutput() {
|
||
elements.outputText.textContent = '翻译结果将在这里显示...';
|
||
clearPronunciation();
|
||
}
|
||
|
||
// 清空发音信息
|
||
function clearPronunciation() {
|
||
if (elements.pronounceSection) {
|
||
elements.pronounceSection.innerHTML = '';
|
||
}
|
||
}
|
||
|
||
// 复制输出
|
||
function copyOutput() {
|
||
const text = elements.outputText.textContent;
|
||
|
||
if (!text || text === '翻译结果将在这里显示...' || text === '翻译失败,请重试' || text === '未获取到翻译结果') {
|
||
showToast('没有可复制的内容', 'error');
|
||
return;
|
||
}
|
||
|
||
// 使用现代API复制
|
||
if (navigator.clipboard) {
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
showToast('已复制到剪贴板');
|
||
}).catch(() => {
|
||
fallbackCopy(text);
|
||
});
|
||
} else {
|
||
fallbackCopy(text);
|
||
}
|
||
}
|
||
|
||
// 备用复制方法
|
||
function fallbackCopy(text) {
|
||
const textArea = document.createElement('textarea');
|
||
textArea.value = text;
|
||
textArea.style.position = 'fixed';
|
||
textArea.style.opacity = '0';
|
||
document.body.appendChild(textArea);
|
||
textArea.select();
|
||
|
||
try {
|
||
document.execCommand('copy');
|
||
showToast('已复制到剪贴板');
|
||
} catch (err) {
|
||
showToast('复制失败,请手动复制', 'error');
|
||
}
|
||
|
||
document.body.removeChild(textArea);
|
||
}
|
||
|
||
// 显示提示消息
|
||
function showToast(message, type = 'success') {
|
||
// 移除现有的toast
|
||
const existingToast = document.querySelector('.toast');
|
||
if (existingToast) {
|
||
existingToast.remove();
|
||
}
|
||
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast ${type}`;
|
||
toast.textContent = message;
|
||
|
||
document.body.appendChild(toast);
|
||
|
||
// 显示toast
|
||
setTimeout(() => {
|
||
toast.classList.add('show');
|
||
}, 100);
|
||
|
||
// 自动隐藏
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
setTimeout(() => {
|
||
if (toast.parentNode) {
|
||
toast.parentNode.removeChild(toast);
|
||
}
|
||
}, 300);
|
||
}, 3000);
|
||
}
|
||
|
||
// 工具函数:防抖
|
||
function debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func(...args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
}
|
||
|
||
// 添加键盘快捷键支持
|
||
document.addEventListener('keydown', function(e) {
|
||
// Ctrl+Enter 翻译
|
||
if (e.ctrlKey && e.key === 'Enter') {
|
||
e.preventDefault();
|
||
translateText();
|
||
}
|
||
|
||
// Ctrl+Shift+C 复制结果
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
|
||
e.preventDefault();
|
||
copyOutput();
|
||
}
|
||
|
||
// Ctrl+Shift+X 清空输入
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'X') {
|
||
e.preventDefault();
|
||
clearInput();
|
||
}
|
||
|
||
// Ctrl+Shift+S 交换语言
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
|
||
e.preventDefault();
|
||
swapLanguages();
|
||
}
|
||
});
|
||
|
||
// 页面可见性变化时的处理
|
||
document.addEventListener('visibilitychange', function() {
|
||
if (document.hidden) {
|
||
// 页面隐藏时暂停翻译请求
|
||
if (isTranslating) {
|
||
setTranslating(false);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 错误处理
|
||
window.addEventListener('error', function(e) {
|
||
console.error('页面错误:', e.error);
|
||
});
|
||
|
||
window.addEventListener('unhandledrejection', function(e) {
|
||
console.error('未处理的Promise拒绝:', e.reason);
|
||
}); |