// ── i18n: locale bundles and t() helper ────────────────────────────────────── // To add a new language: add an entry to LOCALES below with all keys translated. // The language code must match a valid BCP 47 tag (used for speech recognition). // Keys missing in a non-English locale fall back to English automatically. const LOCALES = { en: { _lang: 'en', _label: 'English', _speech: 'en-US', // boot.js cancelling: 'Cancelling\u2026', cancel_failed: 'Cancel failed: ', mic_denied: 'Microphone access denied. Check browser permissions.', mic_no_speech: 'No speech detected. Try again.', mic_network: 'Speech recognition unavailable.', mic_error: 'Voice input error: ', session_imported: 'Session imported', import_failed: 'Import failed: ', import_invalid_json: 'Invalid JSON', image_pasted: 'Image pasted: ', // messages.js edit_message: 'Edit message', regenerate: 'Regenerate response', copy: 'Copy', copied: 'Copied!', you: 'You', thinking: 'Thinking', expand_all: 'Expand all', collapse_all: 'Collapse all', edit_failed: 'Edit failed: ', regen_failed: 'Regenerate failed: ', reconnect_active: 'A response is still being generated. Reload when ready?', reconnect_finished: 'A response was in progress when you last left. Messages may have updated.', // approval card approval_heading: 'Approval required', approval_desc_prefix: 'Dangerous command detected', approval_btn_once: 'Allow once', approval_btn_once_title: 'Allow this one command (Enter)', approval_btn_session: 'Allow session', approval_btn_session_title: 'Allow for this conversation session', approval_btn_always: 'Always allow', approval_btn_always_title: 'Always allow this command pattern', approval_btn_deny: 'Deny', approval_btn_deny_title: 'Deny — do not run this command', approval_responding: 'Responding\u2026', untitled: 'Untitled', n_messages: (n) => `${n} messages`, model_unavailable: ' (unavailable)', model_unavailable_title: 'This model is no longer in your current provider list', // commands.js cmd_help: 'List available commands', cmd_clear: 'Clear conversation messages', cmd_compact: 'Compress conversation context', cmd_model: 'Switch model (e.g. /model gpt-4o)', cmd_workspace: 'Switch workspace by name', cmd_new: 'Start a new chat session', cmd_usage: 'Toggle token usage display on/off', cmd_theme: 'Switch theme (dark/light/slate/solarized/monokai/nord/oled)', cmd_personality: 'Switch agent personality', cmd_skills: 'List available Hermes skills', available_commands: 'Available commands:', type_slash: 'Type / to see commands', conversation_cleared: 'Conversation cleared', model_usage: 'Usage: /model ', no_model_match: 'No model matching "', switched_to: 'Switched to ', workspace_usage: 'Usage: /workspace ', no_workspace_match: 'No workspace matching "', switched_workspace: 'Switched to workspace: ', workspace_switch_failed: 'Workspace switch failed: ', new_session: 'New session created', compressing: 'Requesting context compression...', token_usage_on: 'Token usage on', token_usage_off: 'Token usage off', theme_usage: 'Usage: /theme ', theme_set: 'Theme: ', no_active_session: 'No active session', no_personalities: 'No personalities found (add them to ~/.hermes/personalities/)', available_personalities: 'Available personalities:', personality_switch_hint: '\n\nUse `/personality ` to switch, or `/personality none` to clear.', personalities_load_failed: 'Failed to load personalities', personality_cleared: 'Personality cleared', personality_set: 'Personality: ', failed_colon: 'Failed: ', // ui.js no_workspace: 'No workspace', // workspace.js unsaved_confirm: 'You have unsaved changes in the preview. Discard and navigate?', save: 'Save', edit: 'Edit', save_title: 'Save changes', edit_title: 'Edit this file', saved: 'Saved', save_failed: 'Save failed: ', image_load_failed: 'Could not load image', file_open_failed: 'Could not open file', downloading: (name) => `Downloading ${name}\u2026`, double_click_rename: 'Double-click to rename', renamed_to: 'Renamed to ', rename_failed: 'Rename failed: ', delete_title: 'Delete', delete_confirm: (name) => `Delete ${name}?`, deleted: 'Deleted ', delete_failed: 'Delete failed: ', new_file_prompt: 'New file name (e.g. notes.md):', created: 'Created ', create_failed: 'Create failed: ', new_folder_prompt: 'New folder name:', folder_created: 'Created folder ', folder_create_failed: 'Create folder failed: ', remove_title: 'Remove', empty_dir: '(empty)', upload_failed: 'Upload failed: ', all_uploads_failed: (n) => `All ${n} upload(s) failed`, // settings panel settings_title: 'Settings', settings_save_btn: 'Save Settings', settings_label_model: 'Default Model', settings_label_send_key: 'Send Key', settings_label_theme: 'Theme', settings_label_language: 'Language', settings_label_token_usage: 'Show token usage', settings_label_cli_sessions: 'Show CLI sessions', settings_label_sync_insights: 'Sync to insights', settings_label_check_updates: 'Check for updates', settings_label_bot_name: 'Assistant Name', settings_label_password: 'Access Password', settings_saved: 'Settings saved', settings_save_failed: 'Save failed: ', settings_load_failed: 'Failed to load settings: ', settings_saved_pw: 'Settings saved (password set \u2014 login now required)', // login page (used server-side via /api/i18n/login endpoint) login_title: 'Sign in', login_subtitle: 'Enter your password to continue', login_placeholder: 'Password', login_btn: 'Sign in', login_invalid_pw: 'Invalid password', login_conn_failed: 'Connection failed', dialog_confirm_title: 'Confirm action', dialog_prompt_title: 'Enter a value', dialog_confirm_btn: 'Confirm', discard: 'Discard', clear: 'Clear', create: 'Create', remove: 'Remove', project_name_prompt: 'Project name:', // Sidebar & Tabs tab_chat: 'Chat', tab_tasks: 'Tasks', tab_skills: 'Skills', tab_memory: 'Memory', tab_workspaces: 'Spaces', tab_profiles: 'Profiles', tab_todos: 'Todos', new_conversation: 'New conversation', filter_conversations: 'Filter conversations...', scheduled_jobs: 'Scheduled jobs', new_job: 'New job', loading: 'Loading...', search_skills: 'Search skills...', new_skill: 'New skill', personal_memory: 'Personal memory', current_task_list: 'Current task list', workspace_desc: 'Add and switch workspaces for your sessions.', new_profile: 'New profile', transcript: 'Transcript', download_transcript: 'Download as Markdown', import: 'Import', // Settings detail settings_label_sound: 'Notification sound', settings_desc_sound: 'Play a sound when the assistant finishes a response.', settings_label_notifications: 'Browser notifications', settings_desc_notifications: 'Show a system notification when a response completes while the tab is in the background.', settings_desc_token_usage: 'Displays input/output token count below each assistant reply. Also toggled with /usage.', settings_desc_cli_sessions: 'Merges sessions from the Hermes CLI (state.db) into the session list. Click a CLI session to import it and continue the conversation.', settings_desc_sync_insights: 'Mirrors WebUI token usage to state.db so hermes /insights includes browser session data. Off by default.', settings_desc_check_updates: 'Show a banner when newer versions of the WebUI or Agent are available. Runs a background git fetch periodically.', settings_desc_bot_name: 'Display name for the assistant throughout the UI. Defaults to Hermes.', settings_desc_password: 'Enter a new password to set or change it. Leave blank to keep current setting.', password_placeholder: 'Enter new password…', disable_auth: 'Disable Auth', sign_out: 'Sign Out', cancel: 'Cancel', create_job: 'Create job', save_skill: 'Save skill', editing: 'Editing', // Empty state empty_title: 'What can I help with?', empty_subtitle: 'Ask anything, run commands, explore files, or manage your scheduled tasks.', suggest_files: 'What files are in this workspace?', suggest_schedule: "What's on my schedule today?", suggest_plan: 'Help me plan a small project.', }, de: { _lang: 'de', _label: 'Deutsch', _speech: 'de-DE', // boot.js cancelling: 'Wird abgebrochen\u2026', cancel_failed: 'Abbrechen fehlgeschlagen: ', mic_denied: 'Mikrofonzugriff verweigert. Überprüfen Sie die Browserberechtigungen.', mic_no_speech: 'Keine Sprache erkannt. Versuchen Sie es erneut.', mic_network: 'Spracherkennung nicht verfügbar.', mic_error: 'Spracheingabefehler: ', session_imported: 'Sitzung importiert', import_failed: 'Import fehlgeschlagen: ', import_invalid_json: 'Ungültiges JSON', image_pasted: 'Bild eingefügt: ', // messages.js edit_message: 'Nachricht bearbeiten', regenerate: 'Antwort regenerieren', copy: 'Kopieren', copied: 'Kopiert!', you: 'Du', thinking: 'Nachdenken', expand_all: 'Alle ausklappen', collapse_all: 'Alle einklappen', edit_failed: 'Bearbeiten fehlgeschlagen: ', regen_failed: 'Regeneration fehlgeschlagen: ', reconnect_active: 'Eine Antwort wird noch generiert. Neu laden, wenn bereit?', reconnect_finished: 'Eine Antwort war in Arbeit, als Sie zuletzt gegangen sind. Nachrichten könnten aktualisiert worden sein.', // approval card approval_heading: 'Genehmigung erforderlich', approval_desc_prefix: 'Gefährlicher Befehl erkannt', approval_btn_once: 'Einmal zulassen', approval_btn_once_title: 'Diesen einen Befehl zulassen (Enter)', approval_btn_session: 'Sitzung zulassen', approval_btn_session_title: 'Für diese Konversationssitzung zulassen', approval_btn_always: 'Immer zulassen', approval_btn_always_title: 'Dieses Befehlsmuster immer zulassen', approval_btn_deny: 'Ablehnen', approval_btn_deny_title: 'Ablehnen \u2014 diesen Befehl nicht ausführen', approval_responding: 'Antwortet\u2026', untitled: 'Unbenannt', n_messages: (n) => `${n} Nachrichten`, model_unavailable: ' (nicht verfügbar)', model_unavailable_title: 'Dieses Modell ist nicht mehr in Ihrer aktuellen Provider-Liste', // commands.js cmd_help: 'Verfügbare Befehle auflisten', cmd_clear: 'Konversationsverlauf löschen', cmd_compact: 'Kontext komprimieren', cmd_model: 'Modell wechseln (z.B. /model gpt-4o)', cmd_workspace: 'Workspace nach Namen wechseln', cmd_new: 'Neue Chat-Sitzung starten', cmd_usage: 'Token-Verbrauchsanzeige umschalten', cmd_theme: 'Theme wechseln (dark/light/slate/solarized/monokai/nord/oled)', cmd_personality: 'Agenten-Persönlichkeit wechseln', cmd_skills: 'Verfügbare Hermes-Skills auflisten', available_commands: 'Verfügbare Befehle:', type_slash: 'Tippe / für Befehle', conversation_cleared: 'Konversation gelöscht', model_usage: 'Nutzung: /model ', no_model_match: 'Kein Modell gefunden für "', switched_to: 'Gewechselt zu ', workspace_usage: 'Nutzung: /workspace ', no_workspace_match: 'Kein Workspace gefunden für "', switched_workspace: 'Gewechselt zu Workspace: ', workspace_switch_failed: 'Workspace-Wechsel fehlgeschlagen: ', new_session: 'Neue Sitzung erstellt', compressing: 'Kontext-Komprimierung wird angefordert...', token_usage_on: 'Token-Verbrauch an', token_usage_off: 'Token-Verbrauch aus', theme_usage: 'Nutzung: /theme ', theme_set: 'Theme: ', no_active_session: 'Keine aktive Sitzung', no_personalities: 'Keine Persönlichkeiten gefunden (füge sie in ~/.hermes/personalities/ hinzu)', available_personalities: 'Verfügbare Persönlichkeiten:', personality_switch_hint: '\n\nNutze `/personality ` zum Wechseln, oder `/personality none` zum Löschen.', personalities_load_failed: 'Fehler beim Laden der Persönlichkeiten', personality_cleared: 'Persönlichkeit gelöscht', personality_set: 'Persönlichkeit: ', failed_colon: 'Fehlgeschlagen: ', // ui.js no_workspace: 'Kein Workspace', // workspace.js unsaved_confirm: 'Sie haben ungespeicherte Änderungen in der Vorschau. Verwerfen und fortfahren?', save: 'Speichern', edit: 'Bearbeiten', save_title: 'Änderungen speichern', edit_title: 'Diese Datei bearbeiten', saved: 'Gespeichert', save_failed: 'Speichern fehlgeschlagen: ', image_load_failed: 'Bild konnte nicht geladen werden', file_open_failed: 'Datei konnte nicht geöffnet werden', downloading: (name) => `Lade ${name} herunter\u2026`, double_click_rename: 'Doppelklick zum Umbenennen', renamed_to: 'Umbenannt in ', rename_failed: 'Umbenennen fehlgeschlagen: ', delete_title: 'Löschen', delete_confirm: (name) => `${name} löschen?`, deleted: 'Gelöscht ', delete_failed: 'Löschen fehlgeschlagen: ', new_file_prompt: 'Neuer Dateiname (z.B. notes.md):', created: 'Erstellt ', create_failed: 'Erstellen fehlgeschlagen: ', new_folder_prompt: 'Neuer Ordnername:', folder_created: 'Ordner erstellt ', folder_create_failed: 'Ordner erstellen fehlgeschlagen: ', remove_title: 'Entfernen', empty_dir: '(leer)', upload_failed: 'Upload fehlgeschlagen: ', all_uploads_failed: (n) => `Alle ${n} Upload(s) fehlgeschlagen`, // settings panel settings_title: 'Einstellungen', settings_save_btn: 'Einstellungen speichern', settings_label_model: 'Standard-Modell', settings_label_send_key: 'Sende-Taste', settings_label_theme: 'Theme', settings_label_language: 'Sprache', settings_label_token_usage: 'Token-Verbrauch anzeigen', settings_label_cli_sessions: 'CLI-Sitzungen anzeigen', settings_label_sync_insights: 'Mit Insights synchronisieren', settings_label_check_updates: 'Nach Updates suchen', settings_label_bot_name: 'Assistenten-Name', settings_label_password: 'Zugangspasswort', settings_saved: 'Einstellungen gespeichert', settings_save_failed: 'Speichern fehlgeschlagen: ', settings_load_failed: 'Laden der Einstellungen fehlgeschlagen: ', settings_saved_pw: 'Einstellungen gespeichert (Passwort gesetzt \u2014 Login jetzt erforderlich)', // login page login_title: 'Anmelden', login_subtitle: 'Geben Sie Ihr Passwort ein, um fortzufahren', login_placeholder: 'Passwort', login_btn: 'Anmelden', login_invalid_pw: 'Ungültiges Passwort', login_conn_failed: 'Verbindung fehlgeschlagen', dialog_confirm_title: 'Aktion bestätigen', dialog_prompt_title: 'Wert eingeben', dialog_confirm_btn: 'Bestätigen', discard: 'Verwerfen', clear: 'Leeren', create: 'Erstellen', remove: 'Entfernen', project_name_prompt: 'Projektname:', // Sidebar & Tabs tab_chat: 'Chat', tab_tasks: 'Aufgaben', tab_skills: 'Skills', tab_memory: 'Gedächtnis', tab_workspaces: 'Spaces', tab_profiles: 'Profile', tab_todos: 'Todos', new_conversation: 'Neuer Chat', filter_conversations: 'Chats filtern...', scheduled_jobs: 'Geplante Aufgaben', new_job: 'Neuer Job', loading: 'Lädt...', search_skills: 'Skills suchen...', new_skill: 'Neuer Skill', personal_memory: 'Persönliches Gedächtnis', current_task_list: 'Aktuelle Aufgabenliste', workspace_desc: 'Workspaces hinzufügen und wechseln.', new_profile: 'Neues Profil', transcript: 'Protokoll', download_transcript: 'Als Markdown herunterladen', import: 'Importieren', // Settings detail settings_label_sound: 'Benachrichtigungston', settings_desc_sound: 'Spielt einen Ton ab, wenn der Assistent eine Antwort beendet.', settings_label_notifications: 'Browser-Benachrichtigungen', settings_desc_notifications: 'Zeigt eine Systembenachrichtigung an, wenn eine Antwort fertiggestellt wird, während der Tab im Hintergrund ist.', settings_desc_token_usage: 'Zeigt die Anzahl der Input/Output-Token unter jeder Antwort des Assistenten an. Auch umschaltbar mit /usage.', settings_desc_cli_sessions: 'Fügt Sitzungen aus der Hermes CLI (state.db) in die Sitzungsliste ein. Klicken Sie auf eine CLI-Sitzung, um sie zu importieren und das Gespräch fortzusetzen.', settings_desc_sync_insights: 'Spiegelt den WebUI-Token-Verbrauch in die state.db, sodass hermes /insights Browser-Sitzungsdaten enthält. Standardmäßig aus.', settings_desc_check_updates: 'Zeigt ein Banner an, wenn neuere Versionen der WebUI oder des Agenten verfügbar sind. Führt regelmäßig einen Git-Fetch im Hintergrund aus.', settings_desc_bot_name: 'Anzeigename für den Assistenten in der UI. Standardmäßig Hermes.', settings_desc_password: 'Geben Sie ein neues Passwort ein, um es zu setzen oder zu ändern. Leer lassen, um die aktuelle Einstellung beizubehalten.', password_placeholder: 'Neues Passwort eingeben…', disable_auth: 'Authentifizierung deaktivieren', sign_out: 'Abmelden', cancel: 'Abbrechen', create_job: 'Job erstellen', save_skill: 'Skill speichern', editing: 'Bearbeitung', // Empty state empty_title: 'Wie kann ich helfen?', empty_subtitle: 'Frage mich alles, führe Befehle aus, erkunde Dateien oder verwalte deine Aufgaben.', suggest_files: 'Welche Dateien sind in diesem Workspace?', suggest_schedule: 'Was steht heute auf meinem Plan?', suggest_plan: 'Hilf mir, ein kleines Projekt zu planen.', }, zh: { _lang: 'zh', _label: '\u7b80\u4f53\u4e2d\u6587', _speech: 'zh-CN', // boot.js cancelling: '\u6b63\u5728\u53d6\u6d88...', cancel_failed: '\u53d6\u6d88\u5931\u8d25\uff1a', mic_denied: '\u9ea6\u514b\u98ce\u8bbf\u95ee\u88ab\u62d2\u7edd\uff0c\u8bf7\u68c0\u67e5\u6d4f\u89c8\u5668\u6743\u9650\u3002', mic_no_speech: '\u6ca1\u6709\u68c0\u6d4b\u5230\u8bed\u97f3\uff0c\u8bf7\u518d\u8bd5\u4e00\u6b21\u3002', mic_network: '\u8bed\u97f3\u8bc6\u522b\u5f53\u524d\u4e0d\u53ef\u7528\u3002', mic_error: '\u8bed\u97f3\u8f93\u5165\u51fa\u9519\uff1a', session_imported: '\u4f1a\u8bdd\u5df2\u5bfc\u5165', import_failed: '\u5bfc\u5165\u5931\u8d25\uff1a', import_invalid_json: 'JSON \u65e0\u6548', image_pasted: '\u5df2\u7c98\u8d34\u56fe\u7247\uff1a', // messages.js edit_message: '\u7f16\u8f91\u6d88\u606f', regenerate: '\u91cd\u65b0\u751f\u6210\u56de\u590d', copy: '\u590d\u5236', copied: '\u5df2\u590d\u5236', you: '\u4f60', thinking: '\u601d\u8003\u8fc7\u7a0b', expand_all: '\u5168\u90e8\u5c55\u5f00', collapse_all: '\u5168\u90e8\u6298\u53e0', edit_failed: '\u7f16\u8f91\u5931\u8d25\uff1a', regen_failed: '\u91cd\u65b0\u751f\u6210\u5931\u8d25\uff1a', reconnect_active: '\u56de\u590d\u4ecd\u5728\u751f\u6210\u4e2d\uff0c\u51c6\u5907\u597d\u540e\u8981\u91cd\u65b0\u52a0\u8f7d\u5417\uff1f', reconnect_finished: '\u4f60\u79bb\u5f00\u65f6\u6709\u56de\u590d\u6b63\u5728\u751f\u6210\uff0c\u6d88\u606f\u5185\u5bb9\u53ef\u80fd\u5df2\u7ecf\u66f4\u65b0\u3002', // approval card approval_heading: '需要审批', approval_desc_prefix: '检测到危险命令', approval_btn_once: '允许一次', approval_btn_once_title: '允许执行此命令一次(Enter)', approval_btn_session: '本次允许', approval_btn_session_title: '本次会话期间允许', approval_btn_always: '始终允许', approval_btn_always_title: '始终允许此命令模式', approval_btn_deny: '拒绝', approval_btn_deny_title: '拒绝 — 不执行此命令', approval_responding: '处理中…', untitled: '\u672a\u547d\u540d', n_messages: (n) => `${n} \u6761\u6d88\u606f`, model_unavailable: '\uff08\u4e0d\u53ef\u7528\uff09', model_unavailable_title: '\u8fd9\u4e2a\u6a21\u578b\u5df2\u7ecf\u4e0d\u5728\u5f53\u524d provider \u5217\u8868\u4e2d', // commands.js cmd_help: '\u67e5\u770b\u53ef\u7528\u547d\u4ee4', cmd_clear: '\u6e05\u7a7a\u5f53\u524d\u5bf9\u8bdd\u6d88\u606f', cmd_compact: '\u538b\u7f29\u5bf9\u8bdd\u4e0a\u4e0b\u6587', cmd_model: '\u5207\u6362\u6a21\u578b\uff08\u4f8b\u5982 /model gpt-4o\uff09', cmd_workspace: '\u6309\u540d\u79f0\u5207\u6362\u5de5\u4f5c\u533a', cmd_new: '\u65b0\u5efa\u804a\u5929\u4f1a\u8bdd', cmd_usage: '\u5207\u6362 token \u7528\u91cf\u663e\u793a', cmd_theme: '\u5207\u6362\u4e3b\u9898\uff08dark/light/slate/solarized/monokai/nord/oled\uff09', cmd_personality: '\u5207\u6362 Agent \u4eba\u8bbe', cmd_skills: '\u5217\u51fa\u53ef\u7528\u7684 Hermes \u6280\u80fd', available_commands: '\u53ef\u7528\u547d\u4ee4\uff1a', type_slash: '\u8f93\u5165 / \u53ef\u67e5\u770b\u547d\u4ee4', conversation_cleared: '\u5bf9\u8bdd\u5df2\u6e05\u7a7a', model_usage: '\u7528\u6cd5\uff1a/model ', no_model_match: '\u6ca1\u6709\u5339\u914d\u201c', switched_to: '\u5df2\u5207\u6362\u5230 ', workspace_usage: '\u7528\u6cd5\uff1a/workspace ', no_workspace_match: '\u6ca1\u6709\u5339\u914d\u201c', switched_workspace: '\u5df2\u5207\u6362\u5de5\u4f5c\u533a\uff1a', workspace_switch_failed: '\u5de5\u4f5c\u533a\u5207\u6362\u5931\u8d25\uff1a', new_session: '\u5df2\u65b0\u5efa\u4f1a\u8bdd', compressing: '\u6b63\u5728\u8bf7\u6c42\u538b\u7f29\u4e0a\u4e0b\u6587...', token_usage_on: 'Token \u7528\u91cf\u663e\u793a\u5df2\u5f00\u542f', token_usage_off: 'Token \u7528\u91cf\u663e\u793a\u5df2\u5173\u95ed', theme_usage: '\u7528\u6cd5\uff1a/theme ', theme_set: '\u4e3b\u9898\uff1a', no_active_session: '\u5f53\u524d\u6ca1\u6709\u6d3b\u52a8\u4f1a\u8bdd', no_personalities: '\u6ca1\u6709\u627e\u5230\u4eba\u8bbe\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09', available_personalities: '\u53ef\u7528\u4eba\u8bbe\uff1a', personality_switch_hint: '\n\n\u4f7f\u7528 `/personality ` \u5207\u6362\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002', personalities_load_failed: '\u52a0\u8f7d\u4eba\u8bbe\u5931\u8d25', personality_cleared: '\u4eba\u8bbe\u5df2\u6e05\u7a7a', personality_set: '\u5f53\u524d\u4eba\u8bbe\uff1a', failed_colon: '\u5931\u8d25\uff1a', // ui.js no_workspace: '\u672a\u9009\u62e9\u5de5\u4f5c\u533a', // workspace.js unsaved_confirm: '\u9884\u89c8\u533a\u6709\u672a\u4fdd\u5b58\u4fee\u6539\uff0c\u8981\u653e\u5f03\u66f4\u6539\u5e76\u7ee7\u7eed\u8df3\u8f6c\u5417\uff1f', save: '\u4fdd\u5b58', edit: '\u7f16\u8f91', save_title: '\u4fdd\u5b58\u4fee\u6539', edit_title: '\u7f16\u8f91\u6b64\u6587\u4ef6', saved: '\u5df2\u4fdd\u5b58', save_failed: '\u4fdd\u5b58\u5931\u8d25\uff1a', image_load_failed: '\u56fe\u7247\u52a0\u8f7d\u5931\u8d25', file_open_failed: '\u65e0\u6cd5\u6253\u5f00\u6587\u4ef6', downloading: (name) => `\u6b63\u5728\u4e0b\u8f7d ${name}...`, double_click_rename: '\u53cc\u51fb\u91cd\u547d\u540d', renamed_to: '\u5df2\u91cd\u547d\u540d\u4e3a ', rename_failed: '\u91cd\u547d\u540d\u5931\u8d25\uff1a', delete_title: '\u5220\u9664', delete_confirm: (name) => `\u8981\u5220\u9664 ${name} \u5417\uff1f`, deleted: '\u5df2\u5220\u9664 ', delete_failed: '\u5220\u9664\u5931\u8d25\uff1a', new_file_prompt: '\u65b0\u6587\u4ef6\u540d\uff08\u4f8b\u5982 notes.md\uff09\uff1a', created: '\u5df2\u521b\u5efa ', create_failed: '\u521b\u5efa\u5931\u8d25\uff1a', new_folder_prompt: '\u65b0\u6587\u4ef6\u5939\u540d\u79f0\uff1a', folder_created: '\u5df2\u521b\u5efa\u6587\u4ef6\u5939 ', folder_create_failed: '\u521b\u5efa\u6587\u4ef6\u5939\u5931\u8d25\uff1a', remove_title: '\u79fb\u9664', empty_dir: '(\u7a7a)', upload_failed: '\u4e0a\u4f20\u5931\u8d25\uff1a', all_uploads_failed: (n) => `${n} \u4e2a\u6587\u4ef6\u5168\u90e8\u4e0a\u4f20\u5931\u8d25`, // settings panel settings_title: '\u8bbe\u7f6e', settings_save_btn: '\u4fdd\u5b58\u8bbe\u7f6e', settings_label_model: '\u9ed8\u8ba4\u6a21\u578b', settings_label_send_key: '\u53d1\u9001\u5feb\u6377\u952e', settings_label_theme: '\u4e3b\u9898', settings_label_language: '\u8bed\u8a00', settings_label_token_usage: '\u663e\u793a token \u7528\u91cf', settings_label_cli_sessions: '\u663e\u793a CLI \u4f1a\u8bdd', settings_label_sync_insights: '\u540c\u6b65\u5230 insights', settings_label_check_updates: '\u68c0\u67e5\u66f4\u65b0', settings_label_bot_name: '\u52a9\u624b\u540d\u79f0', settings_label_password: '\u8bbf\u95ee\u5bc6\u7801', settings_saved: '\u8bbe\u7f6e\u5df2\u4fdd\u5b58', settings_save_failed: '\u4fdd\u5b58\u5931\u8d25\uff1a', settings_load_failed: '\u8bbe\u7f6e\u52a0\u8f7d\u5931\u8d25\uff1a', settings_saved_pw: '\u8bbe\u7f6e\u5df2\u4fdd\u5b58\uff08\u5bc6\u7801\u5df2\u8bbe\u7f6e\u2014\u73b0\u5728\u9700\u8981\u767b\u5f55\uff09', // login page login_title: '\u767b\u5f55', login_subtitle: '\u8f93\u5165\u5bc6\u7801\u7ee7\u7eed\u4f7f\u7528', login_placeholder: '\u5bc6\u7801', login_btn: '\u767b\u5f55', login_invalid_pw: '\u5bc6\u7801\u9519\u8bef', login_conn_failed: '\u8fde\u63a5\u5931\u8d25', dialog_confirm_title: '确认操作', dialog_prompt_title: '输入内容', dialog_confirm_btn: '确认', discard: '放弃', clear: '清空', create: '创建', remove: '移除', project_name_prompt: '项目名称:', // missing keys from English tab_chat: '\u804a\u5929', tab_memory: '\u8a18\u61b6', tab_skills: '\u6280\u80fd', tab_tasks: '\u4efb\u52d9', tab_todos: '\u5f85\u8e29', tab_workspaces: '\u5de5\u4f5c\u5340', new_conversation: '\u65b0\u5b58\u5c0d\u8a71', filter_conversations: '\u7b5c\u9078\u5b58\u5c0d\u8a71', scheduled_jobs: '\u5b58\u5287\u4efb\u52d9', new_job: '\u65b0\u4efb\u52d9', search_skills: '\u641c\u5c0b\u6280\u80fd', new_skill: '\u65b0\u6280\u80fd', save_skill: '\u5132\u5b58\u6280\u80fd', personal_memory: '\u500b\u4eba\u8a18\u61b6', current_task_list: '\u76ee\u524d\u4efb\u52d9\u6e05\u55ae', new_profile: '\u65b0\u914d\u7f6e\u6a94', transcript: '\u8a18\u9304', download_transcript: '\u4e0b\u8f09\u8a18\u9304', import: '\u5c0e\u5165', editing: '\u7de8\u8f2f\u4e2d', empty_title: '\u7a7a\u767c\u5b58\u7a7a\u9593', empty_subtitle: '\u9ede\u64ca\u4e0a\u65b9\u6309\u9215\u958b\u59cb\u5c0d\u8a71', cancel: '\u53d6\u6d88', loading: '\u52a0\u8f09\u4e2d', create_job: '\u5efa\u7acb\u4efb\u52d9', suggest_plan: '\u5efa\u8b70\u8a08\u5287', suggest_schedule: '\u5efa\u8b70\u6642\u7a0b', suggest_files: '\u5efa\u8b70\u6a94\u6848', sign_out: '\u767b\u51fa', password_placeholder: '\u5bc6\u7801', disable_auth: '\u505c\u7528\u9a57\u8b49', settings_label_sound: '\u901a\u77e5\u8072\u97f3', settings_label_notifications: '\u700f\u89bd\u901a\u77e5', settings_desc_sound: '\u52a9\u624b\u5b8c\u6210\u56de\u7b54\u6642\u64a9\u653e\u8072\u97f3\u3002', settings_desc_notifications: '\u7576\u5206\u9801\u5728\u5f8c\u53f0\u6642\uff0c\u6709\u56de\u7b54\u5b8c\u6210\u6e05\u55ae\u6703\u986f\u793a\u7cfb\u7d71\u901a\u77e5\u3002', settings_desc_token_usage: '\u5728\u52a9\u624b\u6bcf\u6b21\u56de\u7b54\u4e0b\u65b9\u986f\u793a Input/Output token \u6578\u91cf\u3002\u4e5f\u53ef\u4ee5\u7528 /usage \u5207\u63db\u3002', settings_desc_cli_sessions: '\u5c07 Hermes CLI (\u7684 state.db) \u4e2d\u7684\u4f1a\u8a71\u6dfb\u52a0\u5230\u4f1a\u8a71\u6e05\u55ae\u3002\u9ede\u64ca\u4e00\u500b CLI \u4f1a\u8a71\u5c07\u5c0e\u5165\u5b83\u7a0b\u5f0f\u4e26\u7e7c\u7e8c\u5b58\u5c0d\u8a71\u3002', settings_desc_sync_insights: '\u5c07 WebUI token \u4f7f\u7528\u60c5\u6cc1\u540c\u6b65\u5230 state.db\uff0c\u8a93 hermes /insights \u5305\u542b\u700f\u89bd\u5668\u4f1a\u8a71\u6578\u64da\u3002\u9810\u8a2d\u70b8\u555f\u7528\u3002', settings_desc_check_updates: '\u7576\u6709\u66f4\u65b0\u7684 WebUI \u6216\u52a9\u624b\u7248\u672c\u6642\u986f\u793a\u6a19\u8a18\u3002\u5c07\u5728\u5f8c\u81ea\u6b63\u5e38\u57f7\u884c Git-Fetch\u3002', settings_desc_bot_name: '\u52a9\u624b\u5728 UI \u4e2d\u7684\u986f\u793a\u540d\u7a31\u3002\u9810\u8a2d\u70b8\u7528\u6539\u3002', settings_desc_password: '\u8a2d\u5b9a WebUI \u767b\u5165\u5bc6\u7801\u3002\u5047\u5982\u5df2\u8a2d\u7f6e\uff0c\u6bcf\u6b21\u52a0\u8f09\u90fd\u9700\u8981\u767b\u5165\u3002', settings_label_sound: '\u901a\u77e5\u8072\u97f3', }, // Traditional Chinese (zh-Hant) 'zh-Hant': { _lang: 'zh-Hant', _label: '\u7e41\u9ad4\u4e2d\u6587', _speech: 'zh-TW', // boot.js cancelling: '\u6b63\u5728\u53d6\u6d88...', cancel_failed: '\u53d6\u6d88\u5931\u6557\uff1a', mic_denied: '\u9ea6\u514b\u98a8\u8a2a\u554f\u88ab\u62d2\u7d75\uff0c\u8acb\u6aa2\u67e5\u700f\u89bd\u5668\u6b0a\u9650\u3002', mic_no_speech: '\u6c92\u6709\u6aa2\u6e2c\u5230\u8a71\u97f3\uff0c\u8acb\u518d\u5617\u4e00\u6b21\u3002', mic_network: '\u8a71\u97f3\u8b58\u5225\u76ee\u524d\u4e0d\u53ef\u7528\u3002', mic_error: '\u8a71\u97f3\u8f38\u5165\u51fa\u932f\uff1a', session_imported: '\u6703\u8a71\u5df2\u5c0e\u5165', import_failed: '\u5c0e\u5165\u5931\u6557\uff1a', import_invalid_json: 'JSON \u7121\u6548', image_pasted: '\u5df2\u7c98\u8cbc\u5716\u7247\uff1a', // messages.js edit_message: '\u7de8\u8f2f\u8a0a\u606f', regenerate: '\u91cd\u65b0\u751f\u6210\u56de\u8986', copy: '\u8907\u88fd', copied: '\u5df2\u8907\u88fd', you: '\u4f60', thinking: '\u601d\u8003\u904e\u7a0b', expand_all: '\u5168\u90e8\u5c55\u958b', collapse_all: '\u5168\u90e8\u6298\u758a', edit_failed: '\u7de8\u8f2f\u5931\u6557\uff1a', regen_failed: '\u91cd\u65b0\u751f\u6210\u5931\u6557\uff1a', reconnect_active: '\u56de\u8986\u4ecd\u5728\u751f\u6210\u4e2d\uff0c\u6e96\u5099\u597d\u5f8c\u8981\u91cd\u65b0\u52a0\u8f09\u55ce\uff1f', reconnect_finished: '\u4f60\u96e2\u958b\u6642\u6709\u56de\u8986\u6b63\u5728\u751f\u6210\uff0c\u8a0a\u606f\u5167\u5bb9\u53ef\u80fd\u5df2\u7d93\u66f4\u65b0\u3002', // approval card approval_heading: '\u9700\u8981\u5ba1\u6838', approval_desc_prefix: '\u6aa2\u6e2c\u5230\u5371\u96aa\u547d\u4ee4', approval_btn_once: '\u5141\u8a31\u4e00\u6b21', approval_btn_once_title: '\u5141\u8a31\u57f7\u884c\u6b64\u547d\u4ee4\u4e00\u6b21\uff08Enter\uff09', approval_btn_session: '\u672c\u6b21\u5141\u8a31', approval_btn_session_title: '\u672c\u6b21\u6703\u8a71\u671f\u9593\u5141\u8a31', approval_btn_always: '\u59c4\u59b9\u5141\u8a31', approval_btn_always_title: '\u59c4\u59b9\u5141\u8a31\u6b64\u547d\u4ee4\u6a21\u5f0f', approval_btn_deny: '\u62d2\u7edd', approval_btn_deny_title: '\u62d2\u7edd — \u4e0d\u57f7\u884c\u6b64\u547d\u4ee4', approval_responding: '\u8655\u7406\u4e2d\u2026', untitled: '\u672a\u547d\u540d', n_messages: (n) => `${n} \u689d\u8a0a\u606f`, model_unavailable: '\uff08\u4e0d\u53ef\u7528\uff09', model_unavailable_title: '\u6b64\u6a21\u578b\u5df2\u7d93\u4e0d\u5728\u7576\u524d provider \u5217\u8868\u4e2d', // commands.js cmd_help: '\u67e5\u770b\u53ef\u7528\u547d\u4ee4', cmd_clear: '\u6e05\u7a7a\u7576\u524d\u5c0d\u8a71\u8a0a\u606f', cmd_compact: '\u58d3\u7e2e\u5c0d\u8a71\u4e0a\u4e0b\u6587', cmd_model: '\u5207\u63db\u6a21\u578b\uff08\u4f8b\u5982 /model gpt-4o\uff09', cmd_workspace: '\u6309\u540d\u7a31\u5207\u63db\u5de5\u4f5c\u5340', cmd_new: '\u65b0\u5efa\u804a\u5929\u6703\u8a71', cmd_usage: '\u5207\u63db token \u7528\u91cf\u986f\u793a', cmd_theme: '\u5207\u63db\u4e3b\u984c\uff08dark/light/slate/solarized/monokai/nord/oled\uff09', cmd_personality: '\u5207\u63db Agent \u4eba\u8a2d', cmd_skills: '\u5217\u51fa\u53ef\u7528\u7684 Hermes \u6280\u80fd', available_commands: '\u53ef\u7528\u547d\u4ee4\uff1a', type_slash: '\u8f38\u5165 / \u53ef\u67e5\u770b\u547d\u4ee4', conversation_cleared: '\u5c0d\u8a71\u5df2\u6e05\u7a7a', model_usage: '\u7528\u6cd5\uff1a/model ', no_model_match: '\u6c92\u6709\u5339\u914d\u201c', switched_to: '\u5df2\u5207\u63db\u5230 ', workspace_usage: '\u7528\u6cd5\uff1a/workspace ', no_workspace_match: '\u6c92\u6709\u5339\u914d\u201c', switched_workspace: '\u5df2\u5207\u63db\u5de5\u4f5c\u5340\uff1a', workspace_switch_failed: '\u5de5\u4f5c\u5340\u5207\u63db\u5931\u6557\uff1a', new_session: '\u5df2\u65b0\u5efa\u6703\u8a71', compressing: '\u6b63\u5728\u8981\u6c42\u58d3\u7e2e\u4e0a\u4e0b\u6587...', token_usage_on: 'Token \u7528\u91cf\u986f\u793a\u5df2\u958b\u555f', token_usage_off: 'Token \u7528\u91cf\u986f\u793a\u5df2\u95dc\u9589', theme_usage: '\u7528\u6cd5\uff1a/theme ', theme_set: '\u4e3b\u984c\uff1a', no_active_session: '\u7576\u524d\u6c92\u6709\u6d3b\u52d5\u6703\u8a71', no_personalities: '\u6c92\u6709\u627e\u5230\u4eba\u8a2d\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09', available_personalities: '\u53ef\u7528\u4eba\u8a2d\uff1a', personality_switch_hint: '\n\n\u4f7f\u7528 `/personality ` \u5207\u63db\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002', personalities_load_failed: '\u52a0\u8f7d\u4eba\u8a2d\u5931\u6557', personality_cleared: '\u4eba\u8a2d\u5df2\u6e05\u7a7a', personality_set: '\u7576\u524d\u4eba\u8a2d\uff1a', failed_colon: '\u5931\u6557\uff1a', // ui.js no_workspace: '\u672a\u9078\u64c7\u5de5\u4f5c\u5340', // workspace.js unsaved_confirm: '\u9810\u89bd\u5340\u6709\u672a\u5132\u5b58\u4fee\u6539\uff0c\u8981\u653e\u68c4\u66f4\u6539\u5e76\u7e7c\u7e8c\u8df3\u8ee2\u55ce\uff1f', save: '\u5132\u5b58', edit: '\u7de8\u8f2f', save_title: '\u5132\u5b58\u4fee\u6539', edit_title: '\u7de8\u8f2f\u6b64\u6587\u4ef6', saved: '\u5df2\u5132\u5b58', save_failed: '\u5132\u5b58\u5931\u6557\uff1a', image_load_failed: '\u5716\u7247\u52a0\u8f09\u5931\u6557', file_open_failed: '\u7121\u6cd5\u6253\u958b\u6587\u4ef6', downloading: (name) => `\u6b63\u5728\u4e0b\u8f09 ${name}...`, double_click_rename: '\u96d9\u64ca\u91cd\u547d\u540d', renamed_to: '\u5df2\u91cd\u547d\u540d\u70ba ', rename_failed: '\u91cd\u547d\u540d\u5931\u6557\uff1a', delete_title: '\u522a\u9664', delete_confirm: (name) => `\u8981\u522a\u9664 ${name} \u55ce\uff1f`, deleted: '\u5df2\u522a\u9664 ', delete_failed: '\u522a\u9664\u5931\u6557\uff1a', new_file_prompt: '\u65b0\u6587\u4ef6\u540d\uff08\u4f8b\u5982 notes.md\uff09\uff1a', created: '\u5df2\u5275\u5efa ', create_failed: '\u5275\u5efa\u5931\u6557\uff1a', new_folder_prompt: '\u65b0\u6587\u4ef6\u593e\u540d\u7a31\uff1a', folder_created: '\u5df2\u5275\u5efa\u6587\u4ef6\u593e ', folder_create_failed: '\u5275\u5efa\u6587\u4ef6\u593e\u5931\u6557\uff1a', remove_title: '\u79fb\u9664', empty_dir: '(\u7a7a)', upload_failed: '\u4e0a\u50b3\u5931\u6557\uff1a', all_uploads_failed: (n) => `${n} \u500b\u6587\u4ef6\u5168\u90e8\u4e0a\u50b3\u5931\u6557`, // settings panel settings_title: '\u8a2d\u5b9a', settings_save_btn: '\u5132\u5b58\u8a2d\u5b9a', settings_label_model: '\u9ed8\u8a8d\u6a21\u578b', settings_label_send_key: '\u767c\u9001\u5feb\u6377\u9375', settings_label_theme: '\u4e3b\u984c', settings_label_language: '\u8a9d\u8a00', settings_label_token_usage: '\u986f\u793a token \u7528\u91cf', settings_label_cli_sessions: '\u986f\u793a CLI \u6703\u8a71', settings_label_sync_insights: '\u540c\u6b65\u5230 insights', settings_label_check_updates: '\u6aa2\u67e5\u66f4\u65b0', settings_label_bot_name: '\u52a9\u624b\u540d\u7a31', settings_label_password: '\u8a2a\u8aad\u5bc6\u78bc', settings_saved: '\u8a2d\u5b9a\u5df2\u5132\u5b58', settings_save_failed: '\u5132\u5b58\u5931\u6557\uff1a', settings_load_failed: '\u8a2d\u5b9a\u52a0\u8f09\u5931\u6557\uff1a', settings_saved_pw: '\u8a2d\u5b9a\u5df2\u5132\u5b58\uff08\u5bc6\u78bc\u5df2\u8a2d\u5b9a\u2014\u73fe\u5728\u9700\u8981\u767b\u5f55\uff09', // login page login_title: '\u767b\u5f55', login_subtitle: '\u8f38\u5165\u5bc6\u78bc\u7e7c\u7e8c\u4f7f\u7528', login_placeholder: '\u5bc6\u78bc', login_btn: '\u767b\u5f55', login_invalid_pw: '\u5bc6\u78bc\u932f\u8aa4', login_conn_failed: '\u9023\u63a5\u5931\u6557', // missing keys from English dialog_confirm_title: '確認操作', dialog_prompt_title: '輸入內容', dialog_confirm_btn: '確認', discard: '放棄', clear: '清空', create: '建立', remove: '移除', project_name_prompt: '專案名稱:', tab_chat: '\u804a\u5929', tab_memory: '\u8a18\u61b6', tab_skills: '\u6280\u80fd', tab_tasks: '\u4efb\u52d9', tab_todos: '\u5f85\u8e29', tab_workspaces: '\u5de5\u4f5c\u5340', new_conversation: '\u65b0\u5b58\u5c0d\u8a71', filter_conversations: '\u7b5c\u9078\u5b58\u5c0d\u8a71', scheduled_jobs: '\u5b58\u5287\u4efb\u52d9', new_job: '\u65b0\u4efb\u52d9', search_skills: '\u641c\u5c0b\u6280\u80fd', new_skill: '\u65b0\u6280\u80fd', save_skill: '\u5132\u5b58\u6280\u80fd', personal_memory: '\u500b\u4eba\u8a18\u61b6', current_task_list: '\u76ee\u524d\u4efb\u52d9\u6e05\u55ae', new_profile: '\u65b0\u914d\u7f6e\u6a94', transcript: '\u8a18\u9304', download_transcript: '\u4e0b\u8f09\u8a18\u9304', import: '\u5c0e\u5165', editing: '\u7de8\u8f2f\u4e2d', empty_title: '\u7a7a\u767c\u5b58\u7a7a\u9593', empty_subtitle: '\u9ede\u64ca\u4e0a\u65b9\u6309\u9215\u958b\u59cb\u5c0d\u8a71', cancel: '\u53d6\u6d88', loading: '\u52a0\u8f09\u4e2d', create_job: '\u5efa\u7acb\u4efb\u52d9', suggest_plan: '\u5efa\u8b70\u8a08\u5287', suggest_schedule: '\u5efa\u8b70\u6642\u7a0b', suggest_files: '\u5efa\u8b70\u6a94\u6848', sign_out: '\u767b\u51fa', password_placeholder: '\u5bc6\u78bc', disable_auth: '\u505c\u7528\u9a57\u8b49', settings_label_sound: '\u901a\u77e5\u8072\u97f3', settings_label_notifications: '\u700f\u89bd\u901a\u77e5', settings_desc_sound: '\u52a9\u624b\u5b8c\u6210\u56de\u7b54\u6642\u64a9\u653e\u8072\u97f3\u3002', settings_desc_notifications: '\u7576\u5206\u9801\u5728\u5f8c\u81ea\u6642\uff0c\u6709\u56de\u7b54\u5b8c\u6210\u6e05\u55ae\u6703\u986f\u793a\u7cfb\u7d71\u901a\u77e5\u3002', settings_desc_token_usage: '\u5728\u52a9\u624b\u6bcf\u6b21\u56de\u7b54\u4e0b\u65b9\u986f\u793a Input/Output token \u6578\u91cf\u3002\u4e5f\u53ef\u4ee5\u7528 /usage \u5207\u63db\u3002', settings_desc_cli_sessions: '\u5c07 Hermes CLI (\u7684 state.db) \u4e2d\u7684\u6703\u8a71\u6dfb\u52a0\u5230\u6703\u8a71\u6e05\u55ae\u3002\u9ede\u64ca\u4e00\u500b CLI \u6703\u8a71\u5c07\u5c0e\u5165\u5b83\u7a0b\u5f0f\u4e26\u7e7c\u7e8c\u5b58\u5c0d\u8a71\u3002', settings_desc_sync_insights: '\u5c07 WebUI token \u4f7f\u7528\u60c5\u6cc1\u540c\u6b65\u5230 state.db\uff0c\u8a93 hermes /insights \u5305\u542b\u700f\u89bd\u5668\u6703\u8a71\u6578\u64da\u3002\u9810\u8a2d\u70b8\u555f\u7528\u3002', settings_desc_check_updates: '\u7576\u6709\u66f4\u65b0\u7684 WebUI \u6216\u52a9\u624b\u7248\u672c\u6642\u986f\u793a\u6a19\u8a18\u3002\u5c07\u5728\u5f8c\u81ea\u6b63\u5e38\u57f7\u884c Git-Fetch\u3002', settings_desc_bot_name: '\u52a9\u624b\u5728 UI \u4e2d\u7684\u986f\u793a\u540d\u7a31\u3002\u9810\u8a2d\u70b8\u7528\u6539\u3002', settings_desc_password: '\u8a2d\u5b9a WebUI \u767b\u5165\u5bc6\u78bc\u3002\u5047\u5982\u5df2\u8a2d\u7f6e\uff0c\u6bcf\u6b21\u52a0\u8f09\u90fd\u9700\u8981\u767b\u5165\u3002', settings_label_sound: '\u901a\u77e5\u8072\u97f3', // boot.js cancelling: '\u6b63\u5728\u53d6\u6d88...', cancel_failed: '\u53d6\u6d88\u5931\u6557\uff1a', mic_denied: '\u9ea6\u514b\u98a8\u8a2a\u554f\u88ab\u62d2\u7d75\uff0c\u8acb\u6aa2\u67e5\u700f\u89bd\u5668\u6b0a\u9650\u3002', mic_no_speech: '\u6c92\u6709\u6aa2\u6e2c\u5230\u8a71\u97f3\uff0c\u8acb\u518d\u5617\u4e00\u6b21\u3002', mic_network: '\u8a71\u97f3\u8b58\u5225\u76ee\u524d\u4e0d\u53ef\u7528\u3002', mic_error: '\u8a71\u97f3\u8f38\u5165\u51fa\u932f\uff1a', session_imported: '\u6703\u8a71\u5df2\u5c0e\u5165', import_failed: '\u5c0e\u5165\u5931\u6557\uff1a', import_invalid_json: 'JSON \u7121\u6548', image_pasted: '\u5df2\u7c98\u8cbc\u5716\u7247\uff1a', // messages.js edit_message: '\u7de8\u8f2f\u8a0a\u606f', regenerate: '\u91cd\u65b0\u751f\u6210\u56de\u8986', copy: '\u8907\u88fd', copied: '\u5df2\u8907\u88fd', // ui.js workspace_desc: '\u8acb\u9078\u64c7\u5de5\u4f5c\u5340\uff0c\u6216\u8f09\u5165\u65b0\u540d\u7a31\u5beb\u4e00\u500b', tab_profiles: '\u914d\u7f6e', }, }; // Active locale — defaults to English; overridden by loadLocale() at boot. let _locale = LOCALES.en; /** * Translate a key. Falls back to English if the key is missing in the active locale. * Supports function values (for interpolated strings): call t('key', arg). * @param {string} key * @param {...*} args - forwarded to function-valued translations * @returns {string} */ function t(key, ...args) { const val = _locale[key] ?? LOCALES.en[key]; if (val === undefined) return key; // final fallback: return key itself return typeof val === 'function' ? val(...args) : val; } /** * Switch locale by language code (e.g. 'en', 'zh'). * Persists to localStorage and updates the attribute. * @param {string} lang */ function setLocale(lang) { const resolved = LOCALES[lang] ? lang : 'en'; _locale = LOCALES[resolved]; localStorage.setItem('hermes-lang', resolved); document.documentElement.lang = _locale._speech || resolved; } /** * Load locale from localStorage (called once at boot, before DOMContentLoaded). * Server-persisted preference is applied later in loadSettingsPanel(). */ function loadLocale() { const saved = localStorage.getItem('hermes-lang'); setLocale(saved && LOCALES[saved] ? saved : 'en'); } /** * Re-stamp all [data-i18n] elements in the DOM with the current locale. * Safe to call at any time — missing keys fall back to English. * Call after setLocale() to make static HTML text update without a reload. */ function applyLocaleToDOM() { document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); const val = t(key); if (val && val !== key) el.textContent = val; }); document.querySelectorAll('[data-i18n-title]').forEach(el => { const key = el.getAttribute('data-i18n-title'); const val = t(key); if (val && val !== key) el.title = val; }); document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { const key = el.getAttribute('data-i18n-placeholder'); const val = t(key); if (val && val !== key) el.placeholder = val; }); } // Apply saved locale immediately so there's no flash of English on reload. loadLocale();