Provide an entry for setting the Tencent Cloud Translation API key.

This commit is contained in:
fanlusky committed 2026-08-22 13:38:42 +08:00
1 parent 79b5f6dad2
commit edcd29d4bb
4 files changed
+198 -1

No files matched your search

@@ -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);
@@ -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<string, unknown> | 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<HTMLInputElement>('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<string, string> = {
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',
@@ -125,6 +125,38 @@
<div class="ftb-toggle-knob"></div>
</div>
</div>
<div class="translation-api-options" id="candidateTranslationApiOptions">
<div class="input-option-divider"></div>
<div class="translation-api-heading">
<div class="section-title">腾讯云 API 凭据</div>
<div class="input-setting-description">用于本地词库未命中时请求机器翻译,凭据仅保存在本机配置文件中</div>
</div>
<div class="translation-api-grid">
<label class="translation-api-field">
<span>SecretId</span>
<input id="tencentTmtSecretId" type="text" autocomplete="off" spellcheck="false" placeholder="AKID...">
</label>
<label class="translation-api-field">
<span>SecretKey</span>
<span class="translation-secret-input">
<input id="tencentTmtSecretKey" type="password" autocomplete="new-password" spellcheck="false" placeholder="SecretKey">
<button id="tencentTmtSecretKeyVisibility" class="translation-secret-toggle" type="button"
aria-label="显示 SecretKey" aria-pressed="false" title="显示 SecretKey">
<svg class="translation-secret-icon translation-secret-icon-show" viewBox="0 0 24 24" aria-hidden="true">
<path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z"></path>
<circle cx="12" cy="12" r="2.6"></circle>
</svg>
<svg class="translation-secret-icon translation-secret-icon-hide" viewBox="0 0 24 24" aria-hidden="true">
<path d="m3 3 18 18"></path>
<path d="M10.6 6.1A9.6 9.6 0 0 1 12 6c6 0 9.5 6 9.5 6a16 16 0 0 1-2.2 3"></path>
<path d="M6.2 6.3C3.8 8.1 2.5 12 2.5 12s3.5 6 9.5 6a9 9 0 0 0 3-.5"></path>
</svg>
</button>
</span>
</label>
</div>
<div class="translation-api-warning" id="candidateTranslationApiWarning" role="status">请填写 SecretId 和 SecretKey 后使用云端翻译</div>
</div>
</div>
<div class="section">
@@ -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;