feat: 统一前端翻译调用走用户配置接口

This commit is contained in:
guoqingshan
2026-08-01 21:41:20 +08:00
parent c70649475a
commit 3eabdc9f32
2 changed files with 171 additions and 54 deletions
+1
View File
@@ -80,6 +80,7 @@ server/data/spanish_diary.db
- 用户日记数据隔离 - 用户日记数据隔离
- 共享知识库 - 共享知识库
- 每用户翻译配置(本地 / MyMemory / 自定义接口) - 每用户翻译配置(本地 / MyMemory / 自定义接口)
- 实时翻译、选中翻译、保存和中译西统一调用每用户翻译配置
- 每用户日记统计 - 每用户日记统计
- 每用户单词收藏 - 每用户单词收藏
- 手机、平板、桌面响应式适配 - 手机、平板、桌面响应式适配
+170 -54
View File
@@ -8,6 +8,11 @@ const dbDeleteEntry = deleteEntry;
var BACKEND_MODE = false; var BACKEND_MODE = false;
var currentUser = null; var currentUser = null;
var favoriteWords = {}; var favoriteWords = {};
var currentTranslationSettings = { translator: 'local' };
var liveTranslationTimer = null;
var liveTranslationSeq = 0;
var selectionTimer = null;
var selectionSeq = 0;
function safeGetAllEntries() { function safeGetAllEntries() {
if (BACKEND_MODE) return apiGetEntries(); if (BACKEND_MODE) return apiGetEntries();
@@ -100,20 +105,67 @@ function clearEditor() {
// ----- Live translation & vocabulary ----- // ----- Live translation & vocabulary -----
function shouldUseApiTranslation() {
return BACKEND_MODE && (currentTranslationSettings.translator || 'local') !== 'local';
}
function translateEditorToEnZh(text) {
if (!shouldUseApiTranslation()) {
var local = translateText(text);
return Promise.resolve({ english: local.english, chinese: local.chinese, provider: 'local' });
}
return Promise.all([
apiTranslate({ text: text, source: 'es', target: 'en' }),
apiTranslate({ text: text, source: 'es', target: 'zh' })
]).then(function(results) {
if (!results[0].translatedText || !results[1].translatedText) {
throw new Error('翻译接口未返回结果');
}
return {
english: results[0].translatedText,
chinese: results[1].translatedText,
provider: 'api'
};
}).catch(function() {
var local = translateText(text);
return { english: local.english, chinese: local.chinese, provider: 'local' };
});
}
function updateLiveTranslation() { function updateLiveTranslation() {
var text = document.getElementById('entry-text').value; var text = document.getElementById('entry-text').value;
if (!text.trim()) { if (!text.trim()) {
clearLiveTranslation(); clearLiveTranslation();
return; return;
} }
var result = translateText(text);
document.getElementById('editor-translate-en').textContent = result.english;
document.getElementById('editor-translate-cn').textContent = result.chinese;
updateVocabulary(text); updateVocabulary(text);
if (!shouldUseApiTranslation()) {
var result = translateText(text);
document.getElementById('editor-translate-en').textContent = result.english;
document.getElementById('editor-translate-cn').textContent = result.chinese;
return;
}
if (liveTranslationTimer) clearTimeout(liveTranslationTimer);
liveTranslationTimer = setTimeout(function() {
fetchLiveTranslation(text);
}, 350);
}
function fetchLiveTranslation(text) {
var seq = ++liveTranslationSeq;
translateEditorToEnZh(text).then(function(result) {
if (seq !== liveTranslationSeq) return;
if (document.getElementById('entry-text').value !== text) return;
document.getElementById('editor-translate-en').textContent = result.english;
document.getElementById('editor-translate-cn').textContent = result.chinese;
});
} }
function clearLiveTranslation() { function clearLiveTranslation() {
liveTranslationSeq++;
if (liveTranslationTimer) clearTimeout(liveTranslationTimer);
selectionSeq++;
if (selectionTimer) clearTimeout(selectionTimer);
document.getElementById('editor-translate-en').textContent = '-'; document.getElementById('editor-translate-en').textContent = '-';
document.getElementById('editor-translate-cn').textContent = '-'; document.getElementById('editor-translate-cn').textContent = '-';
document.getElementById('vocab-list').innerHTML = ''; document.getElementById('vocab-list').innerHTML = '';
@@ -157,14 +209,36 @@ function handleSelection() {
var ta = document.getElementById('entry-text'); var ta = document.getElementById('entry-text');
var selText = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim(); var selText = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim();
var el = document.getElementById('selection-translate'); var el = document.getElementById('selection-translate');
if (selText && selText.length > 0) { if (!selText) {
selectionSeq++;
if (selectionTimer) clearTimeout(selectionTimer);
el.style.display = 'none';
return;
}
if (!shouldUseApiTranslation()) {
var result = translateSelection(selText); var result = translateSelection(selText);
document.getElementById('selected-translation').textContent = document.getElementById('selected-translation').textContent =
result.english + ' / ' + result.chinese; result.english + ' / ' + result.chinese;
el.style.display = 'block'; el.style.display = 'block';
} else { return;
el.style.display = 'none';
} }
if (selectionTimer) clearTimeout(selectionTimer);
selectionTimer = setTimeout(function() {
fetchSelectionTranslation(selText);
}, 250);
}
function fetchSelectionTranslation(selText) {
var seq = ++selectionSeq;
translateEditorToEnZh(selText).then(function(result) {
if (seq !== selectionSeq) return;
var ta = document.getElementById('entry-text');
var current = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim();
if (current !== selText) return;
document.getElementById('selected-translation').textContent =
result.english + ' / ' + result.chinese;
document.getElementById('selection-translate').style.display = 'block';
});
} }
// ----- Event handlers ----- // ----- Event handlers -----
@@ -186,45 +260,50 @@ function saveCurrentEntry() {
var spanish = document.getElementById('entry-text').value.trim(); var spanish = document.getElementById('entry-text').value.trim();
if (!spanish) { alert('请输入西语内容'); return; } if (!spanish) { alert('请输入西语内容'); return; }
var translation = translateText(spanish); var saveBtn = document.getElementById('btn-save');
var data = { saveBtn.textContent = '\u23F3 翻译中...';
date: date, translateEditorToEnZh(spanish).then(function(translation) {
spanish: spanish, var data = {
english: translation.english,
chinese: translation.chinese
};
var savePromise;
if (state.currentId) {
savePromise = safeUpdateEntry(state.currentId, data);
} else if (BACKEND_MODE) {
savePromise = apiAddEntry(data).then(function(entry) {
state.currentId = entry.id;
});
} else {
var entry = {
id: uuid(),
date: date, date: date,
spanish: spanish, spanish: spanish,
english: translation.english, english: translation.english,
chinese: translation.chinese, chinese: translation.chinese
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}; };
savePromise = dbAddEntry(entry).then(function() { var savePromise;
state.currentId = entry.id; if (state.currentId) {
}); savePromise = safeUpdateEntry(state.currentId, data);
} } else if (BACKEND_MODE) {
savePromise = apiAddEntry(data).then(function(entry) {
state.currentId = entry.id;
});
} else {
var entry = {
id: uuid(),
date: date,
spanish: spanish,
english: translation.english,
chinese: translation.chinese,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
savePromise = dbAddEntry(entry).then(function() {
state.currentId = entry.id;
});
}
savePromise.then(function() { return savePromise.then(function() {
return safeGetAllEntries(); return safeGetAllEntries();
}).then(function(entries) { }).then(function(entries) {
state.entries = entries; state.entries = entries;
renderList(); renderList();
renderTranslate({ english: translation.english, chinese: translation.chinese }); renderTranslate({ english: translation.english, chinese: translation.chinese });
var found = entries.find(function(e) { return e.id === state.currentId; }); var found = entries.find(function(e) { return e.id === state.currentId; });
if (found) setEditor(found); if (found) setEditor(found);
document.getElementById('btn-save').textContent = '\uD83D\uDCBE 已保存 \u2713'; document.getElementById('btn-save').textContent = '\uD83D\uDCBE 已保存 \u2713';
setTimeout(function() { document.getElementById('btn-save').textContent = '\uD83D\uDCBE 保存'; }, 1500); setTimeout(function() { document.getElementById('btn-save').textContent = '\uD83D\uDCBE 保存'; }, 1500);
});
}).catch(function() {
document.getElementById('btn-save').textContent = '\uD83D\uDCBE 保存';
}); });
} }
@@ -300,26 +379,61 @@ function updateHelperPreview() {
return; return;
} }
var local = translateZhToEs(zh); var local = translateZhToEs(zh);
document.getElementById('helper-result').textContent = local || '本地词典未收录,正在尝试在线翻译...'; if (shouldUseApiTranslation()) {
document.getElementById('helper-confirm').disabled = !local; document.getElementById('helper-result').textContent = local || '正在调用已配置的翻译接口...';
helperDebounceTimer = setTimeout(function() { document.getElementById('helper-confirm').disabled = true;
translateZhOnline(zh); } else {
}, 600); document.getElementById('helper-result').textContent = local || (BACKEND_MODE ? '本地词典未收录' : '本地词典未收录,正在尝试在线翻译...');
document.getElementById('helper-confirm').disabled = !local;
}
if (shouldUseApiTranslation() || !BACKEND_MODE) {
helperDebounceTimer = setTimeout(function() {
translateZhOnline(zh);
}, 600);
}
} }
function translateZhOnline(zh) { function translateZhOnline(zh) {
var seq = ++helperRequestSeq; var seq = ++helperRequestSeq;
var url = 'https://api.mymemory.translated.net/get?q=' + var request;
encodeURIComponent(zh) + '&langpair=zh-CN|es'; if (shouldUseApiTranslation()) {
fetch(url) request = apiTranslate({ text: zh, source: 'zh', target: 'es' }).then(function(data) {
.then(function(res) { return res.json(); }) return { translatedText: data.translatedText };
.then(function(data) { });
} else if (!BACKEND_MODE) {
var url = 'https://api.mymemory.translated.net/get?q=' +
encodeURIComponent(zh) + '&langpair=zh-CN|es';
request = fetch(url)
.then(function(res) { return res.json(); })
.then(function(data) {
return {
translatedText: data && data.responseData && data.responseData.translatedText,
quotaFinished: data && data.quotaFinished
};
});
} else {
var localOnly = translateZhToEs(zh);
if (localOnly) {
document.getElementById('helper-result').textContent = localOnly;
document.getElementById('helper-confirm').disabled = false;
}
return;
}
request.then(function(result) {
if (seq !== helperRequestSeq) return; if (seq !== helperRequestSeq) return;
var text = data && data.responseData && data.responseData.translatedText; var text = result && result.translatedText;
if (text && !data.quotaFinished) { if (text && !result.quotaFinished) {
helperOnlineResult = text.trim(); helperOnlineResult = text.trim();
document.getElementById('helper-result').textContent = helperOnlineResult; document.getElementById('helper-result').textContent = helperOnlineResult;
document.getElementById('helper-confirm').disabled = false; document.getElementById('helper-confirm').disabled = false;
} else {
var local = translateZhToEs(zh);
if (local) {
document.getElementById('helper-result').textContent = local;
document.getElementById('helper-confirm').disabled = false;
} else {
document.getElementById('helper-result').textContent = '翻译接口未返回结果,且本地词典未收录';
}
} }
}) })
.catch(function() { .catch(function() {
@@ -463,6 +577,7 @@ function toggleFavorite(btn) {
function loadSettingsForm() { function loadSettingsForm() {
if (!BACKEND_MODE) return; if (!BACKEND_MODE) return;
apiGetTranslationSettings().then(function(settings) { apiGetTranslationSettings().then(function(settings) {
currentTranslationSettings = settings;
document.getElementById('translator-type').value = settings.translator || 'local'; document.getElementById('translator-type').value = settings.translator || 'local';
document.getElementById('api-url').value = settings.api_url || ''; document.getElementById('api-url').value = settings.api_url || '';
document.getElementById('api-key').value = settings.api_key || ''; document.getElementById('api-key').value = settings.api_key || '';
@@ -535,7 +650,8 @@ function saveSettingsModal() {
api_url: document.getElementById('api-url').value.trim(), api_url: document.getElementById('api-url').value.trim(),
api_key: document.getElementById('api-key').value.trim(), api_key: document.getElementById('api-key').value.trim(),
extra_config: extra extra_config: extra
}).then(function() { }).then(function(saved) {
currentTranslationSettings = saved;
closeSettingsModal(); closeSettingsModal();
alert('翻译设置已保存'); alert('翻译设置已保存');
}).catch(function(err) { alert(err.message); }); }).catch(function(err) { alert(err.message); });