fix(shared): i18n coverage and contract updates

- keyPath field no longer tells users to enter a server-side path
- settings.highlight.builtin.* notes, timeout messages in four languages
- prototype-safe dictionary lookup; language as a render-time dependency
- Partial<AppSettings> saveSettings contract, update:stateGet channel
This commit is contained in:
Bill committed 2026-09-20 20:27:03 +08:00
1 parent 23fa566fa9
commit c0342db1e8
16 files changed
+100 -9

No files matched your search

+2 -1
View File
@@ -106,7 +106,7 @@ const api: AppApi = {
ipcRenderer.send(Ipc.HOSTKEY_RESPOND, promptId, action), ipcRenderer.send(Ipc.HOSTKEY_RESPOND, promptId, action),
getSettings: () => ipcRenderer.invoke(Ipc.SETTINGS_GET), getSettings: () => ipcRenderer.invoke(Ipc.SETTINGS_GET),
saveSettings: (next: AppSettings) => ipcRenderer.invoke(Ipc.SETTINGS_SET, next), saveSettings: (next: Partial<AppSettings>) => ipcRenderer.invoke(Ipc.SETTINGS_SET, next),
onSettingsChanged: (cb: (s: AppSettings) => void) => { onSettingsChanged: (cb: (s: AppSettings) => void) => {
const listener = (_: unknown, s: AppSettings): void => cb(s) const listener = (_: unknown, s: AppSettings): void => cb(s)
ipcRenderer.on(Ipc.SETTINGS_CHANGED, listener) ipcRenderer.on(Ipc.SETTINGS_CHANGED, listener)
@@ -121,6 +121,7 @@ const api: AppApi = {
deleteLayout: (id: string) => ipcRenderer.invoke(Ipc.LAYOUTS_DELETE, id), deleteLayout: (id: string) => ipcRenderer.invoke(Ipc.LAYOUTS_DELETE, id),
updateCheck: () => ipcRenderer.invoke(Ipc.UPDATE_CHECK), updateCheck: () => ipcRenderer.invoke(Ipc.UPDATE_CHECK),
getUpdateState: () => ipcRenderer.invoke(Ipc.UPDATE_STATE_GET),
updateDownload: () => ipcRenderer.invoke(Ipc.UPDATE_DOWNLOAD), updateDownload: () => ipcRenderer.invoke(Ipc.UPDATE_DOWNLOAD),
updateInstall: () => ipcRenderer.invoke(Ipc.UPDATE_INSTALL), updateInstall: () => ipcRenderer.invoke(Ipc.UPDATE_INSTALL),
updateChangelog: () => ipcRenderer.invoke(Ipc.UPDATE_CHANGELOG), updateChangelog: () => ipcRenderer.invoke(Ipc.UPDATE_CHANGELOG),
+3 -1
View File
@@ -97,7 +97,7 @@ export interface AppApi {
// ---- settings ---- // ---- settings ----
getSettings(): Promise<AppSettings> getSettings(): Promise<AppSettings>
saveSettings(next: AppSettings): Promise<AppSettings> saveSettings(next: Partial<AppSettings>): Promise<AppSettings>
onSettingsChanged(cb: (s: AppSettings) => void): () => void onSettingsChanged(cb: (s: AppSettings) => void): () => void
// ---- fonts ---- // ---- fonts ----
@@ -112,6 +112,8 @@ export interface AppApi {
// ---- updater (domestic feed first, GitHub fallback) ---- // ---- updater (domestic feed first, GitHub fallback) ----
updateCheck(): Promise<UpdateState> updateCheck(): Promise<UpdateState>
/** read the updater's current state without triggering a network check */
getUpdateState(): Promise<UpdateState>
updateDownload(): Promise<void> updateDownload(): Promise<void>
updateInstall(): Promise<void> updateInstall(): Promise<void>
updateChangelog(): Promise<ReleaseNote[]> updateChangelog(): Promise<ReleaseNote[]>
+3 -1
View File
@@ -13,7 +13,7 @@ const main: Record<string, string> = {
'main.tray.closeDetail': 'Minimizing to tray keeps your terminal sessions running.', 'main.tray.closeDetail': 'Minimizing to tray keeps your terminal sessions running.',
'main.tray.rememberChoice': 'Remember my choice, never ask again', 'main.tray.rememberChoice': 'Remember my choice, never ask again',
'main.updater.feedFailed': 'Domestic feed: {gitea}; GitHub: {github}', 'main.updater.feedFailed': 'Gitea feed: {gitea}; GitHub: {github}',
'main.ssh.connectTimeout': 'Connection timed out ({host}:{port})', 'main.ssh.connectTimeout': 'Connection timed out ({host}:{port})',
'main.ssh.saveFingerprintFailed': 'Failed to save the host fingerprint: {detail}', 'main.ssh.saveFingerprintFailed': 'Failed to save the host fingerprint: {detail}',
@@ -27,6 +27,7 @@ const main: Record<string, string> = {
'main.sftp.deleteFailed': 'Delete failed: {detail}', 'main.sftp.deleteFailed': 'Delete failed: {detail}',
'main.sftp.invalidMode': 'Invalid permission value: {mode}', 'main.sftp.invalidMode': 'Invalid permission value: {mode}',
'main.sftp.invalidUidGid': 'Invalid uid/gid', 'main.sftp.invalidUidGid': 'Invalid uid/gid',
'main.sftp.commandTimeout': 'Command timed out',
'main.sftp.commandExitCode': 'Command exited with code {code}', 'main.sftp.commandExitCode': 'Command exited with code {code}',
'main.sftp.cancelled': 'Cancelled', 'main.sftp.cancelled': 'Cancelled',
@@ -47,6 +48,7 @@ const main: Record<string, string> = {
'main.sysinfo.sessionGone': 'SSH session is missing or disconnected', 'main.sysinfo.sessionGone': 'SSH session is missing or disconnected',
'main.sysinfo.execFailed': 'SSH exec failed', 'main.sysinfo.execFailed': 'SSH exec failed',
'main.sysinfo.pollTimeout': 'Monitoring command timed out',
'main.sysinfo.parseFailed': 'Parse failed: {message}', 'main.sysinfo.parseFailed': 'Parse failed: {message}',
'main.pty.serviceNotReady': 'SSH session service is not initialised yet', 'main.pty.serviceNotReady': 'SSH session service is not initialised yet',
'main.pty.missingConnectionId': 'SSH session is missing its connectionId', 'main.pty.missingConnectionId': 'SSH session is missing its connectionId',
+16
View File
@@ -98,6 +98,11 @@ const settings: Record<string, string> = {
'Together with the global key this shows or hides the window from anywhere; if registration fails (a conflict with another app) it simply has no effect and the app reports no error.', 'Together with the global key this shows or hides the window from anywhere; if registration fails (a conflict with another app) it simply has no effect and the app reports no error.',
'settings.system.shortcutRecording': 'Press the keys…', 'settings.system.shortcutRecording': 'Press the keys…',
'settings.system.shortcutPlaceholder': 'Click and press a shortcut, leave empty to disable', 'settings.system.shortcutPlaceholder': 'Click and press a shortcut, leave empty to disable',
'settings.system.shortcutConflict':
'This shortcut is already used by the app (Ctrl+= / Ctrl+- / Ctrl+0 / Ctrl+PgUp / Ctrl+PgDn). Please choose another.',
'settings.resetTerminal': 'Reset terminal settings',
'settings.resetTerminalTitle': 'Reset terminal settings?',
'settings.resetTerminalDesc': 'All terminal settings — font, cursor, rendering and theme — will be restored to defaults.',
'settings.highlight.alert': 'settings.highlight.alert':
'Custom highlighting improves readability at a small performance cost. Rules are applied in priority order. PS: colors are injected as text is written to the terminal, so existing scrollback is unaffected', 'Custom highlighting improves readability at a small performance cost. Rules are applied in priority order. PS: colors are injected as text is written to the terminal, so existing scrollback is unaffected',
'settings.highlight.enabled': 'Enabled', 'settings.highlight.enabled': 'Enabled',
@@ -121,6 +126,17 @@ const settings: Record<string, string> = {
'settings.highlight.resetOk': 'Restore', 'settings.highlight.resetOk': 'Restore',
'settings.highlight.createTitle': 'New highlight rule', 'settings.highlight.createTitle': 'New highlight rule',
'settings.highlight.editTitle': 'Edit highlight rule', 'settings.highlight.editTitle': 'Edit highlight rule',
'settings.highlight.builtin.perm': 'Linux permissions and user/group',
'settings.highlight.builtin.path': 'Linux file paths',
'settings.highlight.builtin.shellkw': 'Shell keywords and control flow',
'settings.highlight.builtin.okstate': 'Success states',
'settings.highlight.builtin.badstate': 'Error/warning states',
'settings.highlight.builtin.quoted': 'Strings and quotes',
'settings.highlight.builtin.envvar': 'Environment variables and arguments',
'settings.highlight.builtin.ipv4': 'Network and IP addresses',
'settings.highlight.builtin.datetime': 'Time and date',
'settings.highlight.builtin.numbers': 'Numbers and counts',
'settings.highlight.builtin.url': 'Web links',
'settings.theme.accent': 'Tab accent color', 'settings.theme.accent': 'Tab accent color',
'settings.theme.accentDesc': 'Accent color for the active tab border, the sidebar indicator, and SSH badges', 'settings.theme.accentDesc': 'Accent color for the active tab border, the sidebar indicator, and SSH badges',
'settings.theme.customBadge': 'Custom', 'settings.theme.customBadge': 'Custom',
+3 -1
View File
@@ -13,7 +13,7 @@ const ssh: Record<string, string> = {
'ssh.fields.askPasswordHint': 'ssh.fields.askPasswordHint':
'Prompts for the password on every connection instead of using the saved one', 'Prompts for the password on every connection instead of using the saved one',
'ssh.fields.passwordPlaceholder': 'Leave empty to keep the saved password', 'ssh.fields.passwordPlaceholder': 'Leave empty to keep the saved password',
'ssh.fields.keyPath': 'Private key path (path on the server, optional)', 'ssh.fields.keyPath': 'Private key file path (on this computer, optional)',
'ssh.fields.keyContent': 'Private key content', 'ssh.fields.keyContent': 'Private key content',
'ssh.fields.keyContentPlaceholder': 'ssh.fields.keyContentPlaceholder':
'Paste the private key; leave empty to use the path above (leave empty while editing to keep the saved key)', 'Paste the private key; leave empty to use the path above (leave empty while editing to keep the saved key)',
@@ -100,6 +100,8 @@ const ssh: Record<string, string> = {
'ssh.file.up': 'Parent folder', 'ssh.file.up': 'Parent folder',
'ssh.file.refresh': 'Refresh', 'ssh.file.refresh': 'Refresh',
'ssh.file.uploadFile': 'Upload files', 'ssh.file.uploadFile': 'Upload files',
'ssh.file.overwriteTitle': 'Overwrite existing file?',
'ssh.file.overwriteDesc': 'A file with the same name exists in the target directory; uploading will overwrite its contents.',
'ssh.file.downloadSelected': 'Download selection', 'ssh.file.downloadSelected': 'Download selection',
'ssh.file.permOwner': 'Permissions / owner', 'ssh.file.permOwner': 'Permissions / owner',
'ssh.file.open': 'Open', 'ssh.file.open': 'Open',
+3 -1
View File
@@ -13,7 +13,7 @@ const main: Record<string, string> = {
'main.tray.closeDetail': 'トレイに最小化すると、ターミナルセッションは実行を続けます。', 'main.tray.closeDetail': 'トレイに最小化すると、ターミナルセッションは実行を続けます。',
'main.tray.rememberChoice': '選択を記憶して今後確認しない', 'main.tray.rememberChoice': '選択を記憶して今後確認しない',
'main.updater.feedFailed': '国内フィード: {gitea};GitHub: {github}', 'main.updater.feedFailed': 'Gitea フィード: {gitea};GitHub: {github}',
'main.ssh.connectTimeout': '接続がタイムアウトしました ({host}:{port})', 'main.ssh.connectTimeout': '接続がタイムアウトしました ({host}:{port})',
'main.ssh.saveFingerprintFailed': 'ホスト鍵のフィンガープリントを保存できませんでした: {detail}', 'main.ssh.saveFingerprintFailed': 'ホスト鍵のフィンガープリントを保存できませんでした: {detail}',
@@ -27,6 +27,7 @@ const main: Record<string, string> = {
'main.sftp.deleteFailed': '削除に失敗しました: {detail}', 'main.sftp.deleteFailed': '削除に失敗しました: {detail}',
'main.sftp.invalidMode': '不正なパーミッション値: {mode}', 'main.sftp.invalidMode': '不正なパーミッション値: {mode}',
'main.sftp.invalidUidGid': '不正な uid/gid', 'main.sftp.invalidUidGid': '不正な uid/gid',
'main.sftp.commandTimeout': 'コマンドがタイムアウトしました',
'main.sftp.commandExitCode': 'コマンドの終了コード {code}', 'main.sftp.commandExitCode': 'コマンドの終了コード {code}',
'main.sftp.cancelled': 'キャンセルしました', 'main.sftp.cancelled': 'キャンセルしました',
@@ -47,6 +48,7 @@ const main: Record<string, string> = {
'main.sysinfo.sessionGone': 'SSH セッションが存在しないか切断されています', 'main.sysinfo.sessionGone': 'SSH セッションが存在しないか切断されています',
'main.sysinfo.execFailed': 'SSH exec に失敗しました', 'main.sysinfo.execFailed': 'SSH exec に失敗しました',
'main.sysinfo.pollTimeout': '監視コマンドがタイムアウトしました',
'main.sysinfo.parseFailed': '解析に失敗しました: {message}', 'main.sysinfo.parseFailed': '解析に失敗しました: {message}',
'main.pty.serviceNotReady': 'SSH セッションサービスが未初期化です', 'main.pty.serviceNotReady': 'SSH セッションサービスが未初期化です',
'main.pty.missingConnectionId': 'SSH セッションに connectionId がありません', 'main.pty.missingConnectionId': 'SSH セッションに connectionId がありません',
+16
View File
@@ -95,6 +95,11 @@ const settings: Record<string, string> = {
'グローバルキーと組み合わせると、どの画面からでもウィンドウを呼び出し/非表示にできます。登録に失敗した場合(他のソフトとの競合)は効果がなく、アプリにエラーは表示されません。', 'グローバルキーと組み合わせると、どの画面からでもウィンドウを呼び出し/非表示にできます。登録に失敗した場合(他のソフトとの競合)は効果がなく、アプリにエラーは表示されません。',
'settings.system.shortcutRecording': 'キーを押してください…', 'settings.system.shortcutRecording': 'キーを押してください…',
'settings.system.shortcutPlaceholder': 'クリックしてショートカットを押す。空欄で無効', 'settings.system.shortcutPlaceholder': 'クリックしてショートカットを押す。空欄で無効',
'settings.system.shortcutConflict':
'このショートカットはアプリ内で既に使用されています(Ctrl+= / Ctrl+- / Ctrl+0 / Ctrl+PgUp / Ctrl+PgDn)。別のものを選んでください。',
'settings.resetTerminal': 'ターミナル設定をリセット',
'settings.resetTerminalTitle': 'ターミナル設定をリセットしますか?',
'settings.resetTerminalDesc': 'フォント、カーソル、レンダリング、テーマなどすべてのターミナル設定がデフォルトに戻ります。',
'settings.highlight.alert': 'settings.highlight.alert':
'独自のハイライトは見やすさを高めますが、わずかに性能を消費します。ルールは優先順に適用されます。PS: ハイライトはターミナルへの書き込み時に色を注入するため、既存のスクロールバックには反映されません', '独自のハイライトは見やすさを高めますが、わずかに性能を消費します。ルールは優先順に適用されます。PS: ハイライトはターミナルへの書き込み時に色を注入するため、既存のスクロールバックには反映されません',
'settings.highlight.enabled': '有効', 'settings.highlight.enabled': '有効',
@@ -118,6 +123,17 @@ const settings: Record<string, string> = {
'settings.highlight.resetOk': '元に戻す', 'settings.highlight.resetOk': '元に戻す',
'settings.highlight.createTitle': 'ハイライトルールを作成', 'settings.highlight.createTitle': 'ハイライトルールを作成',
'settings.highlight.editTitle': 'ハイライトルールを編集', 'settings.highlight.editTitle': 'ハイライトルールを編集',
'settings.highlight.builtin.perm': 'Linux のパーミッションとユーザー',
'settings.highlight.builtin.path': 'Linux のファイルパス',
'settings.highlight.builtin.shellkw': 'Shell のキーワードと制御構文',
'settings.highlight.builtin.okstate': '成功ステータス',
'settings.highlight.builtin.badstate': 'エラー・警告ステータス',
'settings.highlight.builtin.quoted': '文字列と引用符',
'settings.highlight.builtin.envvar': '環境変数と引数',
'settings.highlight.builtin.ipv4': 'ネットワークと IP アドレス',
'settings.highlight.builtin.datetime': '時刻と日付',
'settings.highlight.builtin.numbers': '数値とカウント',
'settings.highlight.builtin.url': 'ウェブリンク',
'settings.theme.accent': 'タブのアクセントカラー', 'settings.theme.accent': 'タブのアクセントカラー',
'settings.theme.accentDesc': 'アクティブなタブの枠線、サイドバーのインジケーター、SSH バッジのアクセントカラー', 'settings.theme.accentDesc': 'アクティブなタブの枠線、サイドバーのインジケーター、SSH バッジのアクセントカラー',
'settings.theme.customBadge': 'カスタム', 'settings.theme.customBadge': 'カスタム',
+3 -1
View File
@@ -13,7 +13,7 @@ const ssh: Record<string, string> = {
'ssh.fields.askPasswordHint': 'ssh.fields.askPasswordHint':
'オンにすると毎回の接続でパスワード入力ダイアログを表示し、保存済みのパスワードを使いません', 'オンにすると毎回の接続でパスワード入力ダイアログを表示し、保存済みのパスワードを使いません',
'ssh.fields.passwordPlaceholder': '編集時に空欄のままにすると保存済みのパスワードを保持します', 'ssh.fields.passwordPlaceholder': '編集時に空欄のままにすると保存済みのパスワードを保持します',
'ssh.fields.keyPath': '秘密鍵のパス(サーバー上の秘密鍵のパス、省略可)', 'ssh.fields.keyPath': '秘密鍵ファイルのパス(このコンピューター上のパス、省略可)',
'ssh.fields.keyContent': '秘密鍵の内容', 'ssh.fields.keyContent': '秘密鍵の内容',
'ssh.fields.keyContentPlaceholder': 'ssh.fields.keyContentPlaceholder':
'秘密鍵の内容を貼り付けます。空欄の場合は上のパスを使用します(編集時に空欄のままにすると保存済みの秘密鍵を保持します)', '秘密鍵の内容を貼り付けます。空欄の場合は上のパスを使用します(編集時に空欄のままにすると保存済みの秘密鍵を保持します)',
@@ -102,6 +102,8 @@ const ssh: Record<string, string> = {
'ssh.file.up': '親フォルダへ', 'ssh.file.up': '親フォルダへ',
'ssh.file.refresh': '更新', 'ssh.file.refresh': '更新',
'ssh.file.uploadFile': 'ファイルをアップロード', 'ssh.file.uploadFile': 'ファイルをアップロード',
'ssh.file.overwriteTitle': '既存のファイルを上書きしますか?',
'ssh.file.overwriteDesc': '対象ディレクトリに同名のファイルが存在します。アップロードすると内容が上書きされます。',
'ssh.file.downloadSelected': '選択項目をダウンロード', 'ssh.file.downloadSelected': '選択項目をダウンロード',
'ssh.file.permOwner': '権限 / 所有者', 'ssh.file.permOwner': '権限 / 所有者',
'ssh.file.open': '開く', 'ssh.file.open': '開く',
+2
View File
@@ -27,6 +27,7 @@ const main: Record<string, string> = {
'main.sftp.deleteFailed': '删除失败: {detail}', 'main.sftp.deleteFailed': '删除失败: {detail}',
'main.sftp.invalidMode': '非法权限值: {mode}', 'main.sftp.invalidMode': '非法权限值: {mode}',
'main.sftp.invalidUidGid': '非法 uid/gid', 'main.sftp.invalidUidGid': '非法 uid/gid',
'main.sftp.commandTimeout': '命令执行超时',
'main.sftp.commandExitCode': '命令退出码 {code}', 'main.sftp.commandExitCode': '命令退出码 {code}',
'main.sftp.cancelled': '已取消', 'main.sftp.cancelled': '已取消',
@@ -47,6 +48,7 @@ const main: Record<string, string> = {
'main.sysinfo.sessionGone': 'SSH 会话不存在或已断开', 'main.sysinfo.sessionGone': 'SSH 会话不存在或已断开',
'main.sysinfo.execFailed': 'SSH exec 失败', 'main.sysinfo.execFailed': 'SSH exec 失败',
'main.sysinfo.pollTimeout': '监控命令响应超时',
'main.sysinfo.parseFailed': '解析失败: {message}', 'main.sysinfo.parseFailed': '解析失败: {message}',
'main.pty.serviceNotReady': 'SSH 会话服务尚未初始化', 'main.pty.serviceNotReady': 'SSH 会话服务尚未初始化',
'main.pty.missingConnectionId': 'SSH 会话缺少 connectionId', 'main.pty.missingConnectionId': 'SSH 会话缺少 connectionId',
+16
View File
@@ -92,6 +92,11 @@ const settings: Record<string, string> = {
'配合全局键可在任何界面唤起/隐藏窗口;若注册失败(与其他软件冲突)则不生效,应用不会报错。', '配合全局键可在任何界面唤起/隐藏窗口;若注册失败(与其他软件冲突)则不生效,应用不会报错。',
'settings.system.shortcutRecording': '按下组合键…', 'settings.system.shortcutRecording': '按下组合键…',
'settings.system.shortcutPlaceholder': '点击后按下快捷键,留空禁用', 'settings.system.shortcutPlaceholder': '点击后按下快捷键,留空禁用',
'settings.system.shortcutConflict':
'该组合键已被应用内快捷键占用(Ctrl+= / Ctrl+- / Ctrl+0 / Ctrl+PgUp / Ctrl+PgDn),请换一个。',
'settings.resetTerminal': '恢复默认终端设置',
'settings.resetTerminalTitle': '恢复默认终端设置?',
'settings.resetTerminalDesc': '字体、光标、渲染与主题等全部终端设置将恢复为默认值。',
'settings.highlight.alert': 'settings.highlight.alert':
'自定义高亮可增强显示效果,但会略微增加性能消耗。规则按优先级顺序应用。PS: 高亮在写入终端时注入颜色,对已有回滚内容不生效', '自定义高亮可增强显示效果,但会略微增加性能消耗。规则按优先级顺序应用。PS: 高亮在写入终端时注入颜色,对已有回滚内容不生效',
'settings.highlight.enabled': '启用', 'settings.highlight.enabled': '启用',
@@ -115,6 +120,17 @@ const settings: Record<string, string> = {
'settings.highlight.resetOk': '恢复', 'settings.highlight.resetOk': '恢复',
'settings.highlight.createTitle': '新建高亮规则', 'settings.highlight.createTitle': '新建高亮规则',
'settings.highlight.editTitle': '编辑高亮规则', 'settings.highlight.editTitle': '编辑高亮规则',
'settings.highlight.builtin.perm': 'Linux 权限与用户',
'settings.highlight.builtin.path': 'Linux 文件路径',
'settings.highlight.builtin.shellkw': 'Shell 关键字与流程',
'settings.highlight.builtin.okstate': '成功状态',
'settings.highlight.builtin.badstate': '错误/警告状态',
'settings.highlight.builtin.quoted': '字符串与引号',
'settings.highlight.builtin.envvar': '环境变量与参数',
'settings.highlight.builtin.ipv4': '网络与 IP 地址',
'settings.highlight.builtin.datetime': '时间与日期',
'settings.highlight.builtin.numbers': '数字与计数',
'settings.highlight.builtin.url': '网址链接',
'settings.theme.accent': '标签强调色', 'settings.theme.accent': '标签强调色',
'settings.theme.accentDesc': '活动标签边框、侧栏指示条与 SSH 徽章的强调颜色', 'settings.theme.accentDesc': '活动标签边框、侧栏指示条与 SSH 徽章的强调颜色',
'settings.theme.customBadge': '自定义', 'settings.theme.customBadge': '自定义',
+3 -1
View File
@@ -11,7 +11,7 @@ const ssh: Record<string, string> = {
'ssh.fields.askPasswordLabel': '连接时询问密码', 'ssh.fields.askPasswordLabel': '连接时询问密码',
'ssh.fields.askPasswordHint': '开启后每次连接弹出对话框输入密码,不再使用已保存的密码', 'ssh.fields.askPasswordHint': '开启后每次连接弹出对话框输入密码,不再使用已保存的密码',
'ssh.fields.passwordPlaceholder': '编辑时留空表示保留已保存的密码', 'ssh.fields.passwordPlaceholder': '编辑时留空表示保留已保存的密码',
'ssh.fields.keyPath': '私钥路径(服务器上的本地私钥路径,可留空)', 'ssh.fields.keyPath': '私钥文件路径(本机路径,可留空)',
'ssh.fields.keyContent': '私钥内容', 'ssh.fields.keyContent': '私钥内容',
'ssh.fields.keyContentPlaceholder': 'ssh.fields.keyContentPlaceholder':
'粘贴私钥内容;留空则使用上面的路径(编辑时留空表示保留已保存的私钥)', '粘贴私钥内容;留空则使用上面的路径(编辑时留空表示保留已保存的私钥)',
@@ -94,6 +94,8 @@ const ssh: Record<string, string> = {
'ssh.file.up': '上一级', 'ssh.file.up': '上一级',
'ssh.file.refresh': '刷新', 'ssh.file.refresh': '刷新',
'ssh.file.uploadFile': '上传文件', 'ssh.file.uploadFile': '上传文件',
'ssh.file.overwriteTitle': '覆盖已存在的文件?',
'ssh.file.overwriteDesc': '目标目录已存在同名文件,上传将覆盖其内容。',
'ssh.file.downloadSelected': '下载选中项', 'ssh.file.downloadSelected': '下载选中项',
'ssh.file.permOwner': '权限 / 属主', 'ssh.file.permOwner': '权限 / 属主',
'ssh.file.open': '打开', 'ssh.file.open': '打开',
+2
View File
@@ -27,6 +27,7 @@ const main: Record<string, string> = {
'main.sftp.deleteFailed': '刪除失敗: {detail}', 'main.sftp.deleteFailed': '刪除失敗: {detail}',
'main.sftp.invalidMode': '權限值無效: {mode}', 'main.sftp.invalidMode': '權限值無效: {mode}',
'main.sftp.invalidUidGid': 'uid/gid 無效', 'main.sftp.invalidUidGid': 'uid/gid 無效',
'main.sftp.commandTimeout': '命令執行逾時',
'main.sftp.commandExitCode': '指令結束碼 {code}', 'main.sftp.commandExitCode': '指令結束碼 {code}',
'main.sftp.cancelled': '已取消', 'main.sftp.cancelled': '已取消',
@@ -47,6 +48,7 @@ const main: Record<string, string> = {
'main.sysinfo.sessionGone': 'SSH 工作階段不存在或已中斷', 'main.sysinfo.sessionGone': 'SSH 工作階段不存在或已中斷',
'main.sysinfo.execFailed': 'SSH exec 失敗', 'main.sysinfo.execFailed': 'SSH exec 失敗',
'main.sysinfo.pollTimeout': '監控命令回應逾時',
'main.sysinfo.parseFailed': '解析失敗: {message}', 'main.sysinfo.parseFailed': '解析失敗: {message}',
'main.pty.serviceNotReady': 'SSH 工作階段服務尚未初始化', 'main.pty.serviceNotReady': 'SSH 工作階段服務尚未初始化',
'main.pty.missingConnectionId': 'SSH 工作階段缺少 connectionId', 'main.pty.missingConnectionId': 'SSH 工作階段缺少 connectionId',
+16
View File
@@ -92,6 +92,11 @@ const settings: Record<string, string> = {
'搭配全域鍵可在任何畫面喚出/隱藏視窗;若註冊失敗(與其他軟體衝突)則不會生效,應用程式也不會報錯。', '搭配全域鍵可在任何畫面喚出/隱藏視窗;若註冊失敗(與其他軟體衝突)則不會生效,應用程式也不會報錯。',
'settings.system.shortcutRecording': '請按下組合鍵…', 'settings.system.shortcutRecording': '請按下組合鍵…',
'settings.system.shortcutPlaceholder': '點擊後按下快速鍵,留空為停用', 'settings.system.shortcutPlaceholder': '點擊後按下快速鍵,留空為停用',
'settings.system.shortcutConflict':
'此組合鍵已被應用內快捷鍵佔用(Ctrl+= / Ctrl+- / Ctrl+0 / Ctrl+PgUp / Ctrl+PgDn),請換一個。',
'settings.resetTerminal': '恢復預設終端設定',
'settings.resetTerminalTitle': '恢復預設終端設定?',
'settings.resetTerminalDesc': '字體、游標、渲染與主題等全部終端設定將恢復為預設值。',
'settings.highlight.alert': 'settings.highlight.alert':
'自訂高亮可強化顯示效果,但會略微增加效能負擔。規則依優先順序套用。PS:高亮是在寫入終端時注入顏色,對既有的回捲內容不生效', '自訂高亮可強化顯示效果,但會略微增加效能負擔。規則依優先順序套用。PS:高亮是在寫入終端時注入顏色,對既有的回捲內容不生效',
'settings.highlight.enabled': '啟用', 'settings.highlight.enabled': '啟用',
@@ -115,6 +120,17 @@ const settings: Record<string, string> = {
'settings.highlight.resetOk': '還原', 'settings.highlight.resetOk': '還原',
'settings.highlight.createTitle': '新增高亮規則', 'settings.highlight.createTitle': '新增高亮規則',
'settings.highlight.editTitle': '編輯高亮規則', 'settings.highlight.editTitle': '編輯高亮規則',
'settings.highlight.builtin.perm': 'Linux 權限與使用者',
'settings.highlight.builtin.path': 'Linux 檔案路徑',
'settings.highlight.builtin.shellkw': 'Shell 關鍵字與流程',
'settings.highlight.builtin.okstate': '成功狀態',
'settings.highlight.builtin.badstate': '錯誤/警告狀態',
'settings.highlight.builtin.quoted': '字串與引號',
'settings.highlight.builtin.envvar': '環境變數與參數',
'settings.highlight.builtin.ipv4': '網路與 IP 位址',
'settings.highlight.builtin.datetime': '時間與日期',
'settings.highlight.builtin.numbers': '數字與計數',
'settings.highlight.builtin.url': '網址連結',
'settings.theme.accent': '標籤強調色', 'settings.theme.accent': '標籤強調色',
'settings.theme.accentDesc': '作用中標籤的邊框、側邊欄指示條與 SSH 徽章的強調顏色', 'settings.theme.accentDesc': '作用中標籤的邊框、側邊欄指示條與 SSH 徽章的強調顏色',
'settings.theme.customBadge': '自訂', 'settings.theme.customBadge': '自訂',
+3 -1
View File
@@ -11,7 +11,7 @@ const ssh: Record<string, string> = {
'ssh.fields.askPasswordLabel': '連線時詢問密碼', 'ssh.fields.askPasswordLabel': '連線時詢問密碼',
'ssh.fields.askPasswordHint': '開啟後每次連線彈出對話框輸入密碼,不再使用已儲存的密碼', 'ssh.fields.askPasswordHint': '開啟後每次連線彈出對話框輸入密碼,不再使用已儲存的密碼',
'ssh.fields.passwordPlaceholder': '編輯時留空表示保留已儲存的密碼', 'ssh.fields.passwordPlaceholder': '編輯時留空表示保留已儲存的密碼',
'ssh.fields.keyPath': '私鑰路徑(伺服器上的本機私鑰路徑,可留空)', 'ssh.fields.keyPath': '私鑰檔案路徑(本機路徑,可留空)',
'ssh.fields.keyContent': '私鑰內容', 'ssh.fields.keyContent': '私鑰內容',
'ssh.fields.keyContentPlaceholder': 'ssh.fields.keyContentPlaceholder':
'貼上私鑰內容;留空則使用上面的路徑(編輯時留空表示保留已儲存的私鑰)', '貼上私鑰內容;留空則使用上面的路徑(編輯時留空表示保留已儲存的私鑰)',
@@ -94,6 +94,8 @@ const ssh: Record<string, string> = {
'ssh.file.up': '上一層', 'ssh.file.up': '上一層',
'ssh.file.refresh': '重新整理', 'ssh.file.refresh': '重新整理',
'ssh.file.uploadFile': '上傳檔案', 'ssh.file.uploadFile': '上傳檔案',
'ssh.file.overwriteTitle': '覆蓋已存在的檔案?',
'ssh.file.overwriteDesc': '目標目錄已存在同名檔案,上傳將覆蓋其內容。',
'ssh.file.downloadSelected': '下載選取項目', 'ssh.file.downloadSelected': '下載選取項目',
'ssh.file.permOwner': '權限 / 擁有者', 'ssh.file.permOwner': '權限 / 擁有者',
'ssh.file.open': '開啟', 'ssh.file.open': '開啟',
+7 -1
View File
@@ -53,6 +53,12 @@ const DICTS: Record<Language, Dict> = {
ja ja
} }
/** Own-property read: dictionary literals would otherwise answer `constructor`,
* `toString`, … straight out of Object.prototype and then throw in replace(). */
function lookup(dict: Dict, key: string): string | undefined {
return Object.prototype.hasOwnProperty.call(dict, key) ? dict[key] : undefined
}
let current: Language = DEFAULT_LANGUAGE let current: Language = DEFAULT_LANGUAGE
const listeners = new Set<(lang: Language) => void>() const listeners = new Set<(lang: Language) => void>()
@@ -75,7 +81,7 @@ export function onLanguageChange(cb: (lang: Language) => void): () => void {
/** Translate `key`; `{name}` placeholders are filled from `vars`. */ /** Translate `key`; `{name}` placeholders are filled from `vars`. */
export function t(key: string, vars?: Record<string, string | number>): string { export function t(key: string, vars?: Record<string, string | number>): string {
const raw = DICTS[current][key] ?? DICTS[DEFAULT_LANGUAGE][key] ?? key const raw = lookup(DICTS[current], key) ?? lookup(DICTS[DEFAULT_LANGUAGE], key) ?? key
if (!vars) return raw if (!vars) return raw
return raw.replace(/\{(\w+)\}/g, (match, name: string) => return raw.replace(/\{(\w+)\}/g, (match, name: string) =>
vars[name] === undefined ? match : String(vars[name]) vars[name] === undefined ? match : String(vars[name])
+2
View File
@@ -97,6 +97,8 @@ export const Ipc = {
UPDATE_DOWNLOAD: 'update:download', UPDATE_DOWNLOAD: 'update:download',
UPDATE_INSTALL: 'update:install', UPDATE_INSTALL: 'update:install',
UPDATE_CHANGELOG: 'update:changelog', UPDATE_CHANGELOG: 'update:changelog',
/** renderer pulls the current updater state without triggering a check */
UPDATE_STATE_GET: 'update:stateGet',
/** main -> renderer broadcast: UpdateState */ /** main -> renderer broadcast: UpdateState */
UPDATE_STATE: 'update:state', UPDATE_STATE: 'update:state',