feat: 统一前端翻译调用走用户配置接口
This commit is contained in:
@@ -80,6 +80,7 @@ server/data/spanish_diary.db
|
|||||||
- 用户日记数据隔离
|
- 用户日记数据隔离
|
||||||
- 共享知识库
|
- 共享知识库
|
||||||
- 每用户翻译配置(本地 / MyMemory / 自定义接口)
|
- 每用户翻译配置(本地 / MyMemory / 自定义接口)
|
||||||
|
- 实时翻译、选中翻译、保存和中译西统一调用每用户翻译配置
|
||||||
- 每用户日记统计
|
- 每用户日记统计
|
||||||
- 每用户单词收藏
|
- 每用户单词收藏
|
||||||
- 手机、平板、桌面响应式适配
|
- 手机、平板、桌面响应式适配
|
||||||
|
|||||||
@@ -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); });
|
||||||
|
|||||||
Reference in New Issue
Block a user