From edcd29d4bb0ae83c1cb79560a6f1b1348dfbbbae Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Sat, 22 Aug 2026 13:38:42 +0800 Subject: [PATCH] Provide an entry for setting the Tencent Cloud Translation API key. --- .../ime-settings/src/modules/config-sync.ts | 6 +- .../ime-settings/src/modules/input.ts | 44 +++++++ .../ime-settings/src/partials/input.html | 32 +++++ .../ime-settings/src/styles/modules/input.css | 117 ++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) diff --git a/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts b/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts index 58e160e3..bbac086a 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/config-sync.ts @@ -1,6 +1,6 @@ import { applyCandidateArrange, applyDropdownValue, applyToggleState } from './shared'; import { applyAppearanceConfig, updateCandidatePreviewHelpcode } from './appearance'; -import { applyFrequencyConfig, applyInputConfig, applyZhEnMixedInputConfig } from './input'; +import { applyFrequencyConfig, applyInputConfig, applyTencentTmtConfig, applyZhEnMixedInputConfig } from './input'; import { applyVoiceConfig } from './voice'; import { applyAiConfig } from './ai-settings'; import { applyFloatingToolbarAppearanceConfig, applyFloatingToolbarItemsConfig } from './floating-toolbar'; @@ -121,8 +121,12 @@ export function setupConfigSync(): void { payload.data?.general?.cn_en_mixed_input, payload.data?.general?.cn_en_mixed_input_min_chars ); + applyTencentTmtConfig(payload.data?.tencent_tmt); if (typeof payload.data?.general?.candidate_translations === 'boolean') { applyToggleState('candidateTranslationsToggleBtn', payload.data.general.candidate_translations); + document.getElementById('candidateTranslationApiOptions')?.classList.toggle( + 'is-disabled', !payload.data.general.candidate_translations + ); } if (typeof payload.data?.general?.emoji_mixed_input === 'boolean') { applyToggleState('emojiMixedInputToggleBtn', payload.data.general.emoji_mixed_input); diff --git a/ui-html/webview2/settings/ime-settings/src/modules/input.ts b/ui-html/webview2/settings/ime-settings/src/modules/input.ts index 25c25dac..595cbfa5 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/input.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/input.ts @@ -83,6 +83,26 @@ export function applyZhEnMixedInputConfig(enabled?: boolean, minChars?: number): } } +function syncCandidateTranslationOptions(enabled: boolean): void { + document.getElementById('candidateTranslationApiOptions')?.classList.toggle('is-disabled', !enabled); +} + +function syncCandidateTranslationWarning(): void { + const secretId = (document.getElementById('tencentTmtSecretId') as HTMLInputElement | null)?.value.trim(); + const secretKey = (document.getElementById('tencentTmtSecretKey') as HTMLInputElement | null)?.value.trim(); + document.getElementById('candidateTranslationApiWarning')?.classList.toggle( + 'is-hidden', Boolean(secretId && secretKey) + ); +} + +export function applyTencentTmtConfig(config: Record | undefined): void { + const secretId = document.getElementById('tencentTmtSecretId') as HTMLInputElement | null; + const secretKey = document.getElementById('tencentTmtSecretKey') as HTMLInputElement | null; + if (secretId && typeof config?.secret_id === 'string') secretId.value = config.secret_id; + if (secretKey && typeof config?.secret_key === 'string') secretKey.value = config.secret_key; + syncCandidateTranslationWarning(); +} + export function setupInput(): void { document.querySelectorAll('input[name="input-mode"]').forEach((radio) => { radio.addEventListener('change', () => { @@ -145,8 +165,32 @@ export function setupInput(): void { updateConfig('general.cn_en_mixed_input', active); }); setupToggleButton('candidateTranslationsToggleBtn', (active) => { + syncCandidateTranslationOptions(active); updateConfig('general.candidate_translations', active); }); + const translationFields: Record = { + tencentTmtSecretId: 'tencent_tmt.secret_id', + tencentTmtSecretKey: 'tencent_tmt.secret_key' + }; + Object.entries(translationFields).forEach(([id, path]) => { + const input = document.getElementById(id) as HTMLInputElement | null; + input?.addEventListener('change', () => { + input.value = input.value.trim(); + updateConfig(path, input.value); + syncCandidateTranslationWarning(); + }); + }); + const secretKey = document.getElementById('tencentTmtSecretKey') as HTMLInputElement | null; + const visibility = document.getElementById('tencentTmtSecretKeyVisibility') as HTMLButtonElement | null; + visibility?.addEventListener('click', () => { + if (!secretKey) return; + const show = secretKey.type === 'password'; + secretKey.type = show ? 'text' : 'password'; + visibility.setAttribute('aria-pressed', String(show)); + const label = show ? '隐藏 SecretKey' : '显示 SecretKey'; + visibility.setAttribute('aria-label', label); + visibility.title = label; + }); setupDropdownMenu( 'zhEnTriggerLengthBtn', 'zhEnTriggerLengthMenu', diff --git a/ui-html/webview2/settings/ime-settings/src/partials/input.html b/ui-html/webview2/settings/ime-settings/src/partials/input.html index 910d1f2d..6decd806 100644 --- a/ui-html/webview2/settings/ime-settings/src/partials/input.html +++ b/ui-html/webview2/settings/ime-settings/src/partials/input.html @@ -125,6 +125,38 @@
+
+
+
+
腾讯云 API 凭据
+
用于本地词库未命中时请求机器翻译,凭据仅保存在本机配置文件中
+
+
+ + +
+
请填写 SecretId 和 SecretKey 后使用云端翻译
+
diff --git a/ui-html/webview2/settings/ime-settings/src/styles/modules/input.css b/ui-html/webview2/settings/ime-settings/src/styles/modules/input.css index 51179b64..1b404b7d 100644 --- a/ui-html/webview2/settings/ime-settings/src/styles/modules/input.css +++ b/ui-html/webview2/settings/ime-settings/src/styles/modules/input.css @@ -45,6 +45,123 @@ margin-top: 5px; } +.translation-api-options { + margin-top: 8px; +} + +.translation-api-options.is-disabled { + opacity: 0.45; +} + +.translation-api-options.is-disabled .translation-api-grid { + pointer-events: none; +} + +.translation-api-heading { + padding-top: 14px; +} + +.translation-api-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + margin-top: 14px; +} + +.translation-api-field { + display: flex; + flex-direction: column; + gap: 7px; + color: var(--text-secondary); + font-size: 12px; + font-weight: 500; +} + +.translation-api-field input { + box-sizing: border-box; + width: 100%; + padding: 10px 12px; + color: var(--text-color); + background: var(--surface-sunken); + border: 1px solid var(--control-border); + border-radius: 6px; + outline: none; + font: 13px Consolas, "Cascadia Mono", monospace; +} + +.translation-api-field input:focus { + border-color: var(--accent-color); + background: var(--control-bg); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-color) 20%, transparent); +} + +.translation-secret-input { + position: relative; + display: block; +} + +.translation-secret-input input { + padding-right: 42px; +} + +.translation-secret-input input::-ms-reveal, +.translation-secret-input input::-ms-clear { + display: none; +} + +.translation-secret-toggle { + position: absolute; + top: 50%; + right: 5px; + display: grid; + width: 32px; + height: 32px; + padding: 0; + color: var(--text-muted); + background: transparent; + border: 0; + border-radius: 5px; + outline: none; + cursor: pointer; + place-items: center; + transform: translateY(-50%); +} + +.translation-secret-toggle:hover { + color: var(--text-color); + background: color-mix(in srgb, var(--text-color) 8%, transparent); +} + +.translation-secret-icon { + grid-area: 1 / 1; + width: 18px; + height: 18px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.7; +} + +.translation-secret-icon-hide, +.translation-secret-toggle[aria-pressed="true"] .translation-secret-icon-show { + display: none; +} + +.translation-secret-toggle[aria-pressed="true"] .translation-secret-icon-hide { + display: block; +} + +.translation-api-warning { + margin-top: 10px; + color: #d7a64a; + font-size: 12px; +} + +.translation-api-warning.is-hidden { + display: none; +} + /* 输入 -> 双拼方案 */ .shuangpin-option-content { display: flex;