// ── 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. // Type definitions for locale entries type LocaleString = string; type LocaleStringFn = (n: number) => string; type LocaleStringFn2 = (m: string, p: string) => string; type LocaleStringFnPath = (path: string) => string; type LocaleStringFnName = (name: string) => string; type LocaleStringFnStatus = (name: string, status: string) => string; type LocaleStringFnTitle = (title: string) => string; type LocaleStringFnCount = (count: number) => string; type LocaleStringFnTitleCount = (title: string, count: number) => string; interface LocaleEntry { _lang: string; _label: string; _speech: string; // boot.js cancelling?: LocaleString; cancel_failed?: LocaleString; mic_denied?: LocaleString; mic_no_speech?: LocaleString; mic_network?: LocaleString; mic_error?: LocaleString; session_imported?: LocaleString; import_failed?: LocaleString; import_invalid_json?: LocaleString; image_pasted?: LocaleString; // messages.js edit_message?: LocaleString; regenerate?: LocaleString; copy?: LocaleString; copied?: LocaleString; you?: LocaleString; thinking?: LocaleString; expand_all?: LocaleString; collapse_all?: LocaleString; edit_failed?: LocaleString; regen_failed?: LocaleString; reconnect_active?: LocaleString; reconnect_finished?: LocaleString; // approval card approval_heading?: LocaleString; approval_desc_prefix?: LocaleString; approval_btn_once?: LocaleString; approval_btn_once_title?: LocaleString; approval_btn_session?: LocaleString; approval_btn_session_title?: LocaleString; approval_btn_always?: LocaleString; approval_btn_always_title?: LocaleString; approval_btn_deny?: LocaleString; approval_btn_deny_title?: LocaleString; approval_responding?: LocaleString; clarify_heading?: LocaleString; clarify_hint?: LocaleString; clarify_other?: LocaleString; clarify_send?: LocaleString; clarify_input_placeholder?: LocaleString; clarify_responding?: LocaleString; untitled?: LocaleString; n_messages?: LocaleStringFn; model_unavailable?: LocaleString; model_unavailable_title?: LocaleString; provider_mismatch_warning?: LocaleStringFn2; provider_mismatch_label?: LocaleString; model_custom_label?: LocaleString; model_custom_placeholder?: LocaleString; model_search_placeholder?: LocaleString; model_search_no_results?: LocaleString; // commands.js cmd_clear?: LocaleString; cmd_compress?: LocaleString; cmd_compact_alias?: LocaleString; cmd_model?: LocaleString; cmd_workspace?: LocaleString; cmd_new?: LocaleString; cmd_usage?: LocaleString; cmd_theme?: LocaleString; cmd_personality?: LocaleString; cmd_skills?: LocaleString; available_commands?: LocaleString; type_slash?: LocaleString; conversation_cleared?: LocaleString; command_label?: LocaleString; context_compaction_label?: LocaleString; reference_only_label?: LocaleString; model_usage?: LocaleString; no_model_match?: LocaleString; switched_to?: LocaleString; workspace_usage?: LocaleString; no_workspace_match?: LocaleString; switched_workspace?: LocaleString; workspace_switch_failed?: LocaleString; new_session?: LocaleString; compressing?: LocaleString; compress_running_label?: LocaleString; compress_complete_label?: LocaleString; compress_failed_label?: LocaleString; focus_label?: LocaleString; token_usage_on?: LocaleString; token_usage_off?: LocaleString; theme_usage?: LocaleString; theme_set?: LocaleString; no_active_session?: LocaleString; slash_skill_badge?: LocaleString; slash_skill_desc?: LocaleString; cmd_stop?: LocaleString; cmd_title?: LocaleString; cmd_retry?: LocaleString; cmd_undo?: LocaleString; cmd_status?: LocaleString; cmd_voice?: LocaleString; stream_stopped?: LocaleString; no_active_task?: LocaleString; cancel_unavailable?: LocaleString; retry_failed?: LocaleString; undo_failed?: LocaleString; undid_n_messages?: LocaleString; undid_messages_suffix?: LocaleString; status_heading?: LocaleString; status_session_id?: LocaleString; status_title?: LocaleString; status_model?: LocaleString; status_workspace?: LocaleString; status_personality?: LocaleString; status_messages?: LocaleString; status_agent_running?: LocaleString; status_yes?: LocaleString; status_no?: LocaleString; status_load_failed?: LocaleString; title_current?: LocaleString; title_change_hint?: LocaleString; title_set?: LocaleString; cmd_webui_only_session?: LocaleString; cmd_voice_use_mic?: LocaleString; usage_heading?: LocaleString; usage_default_model?: LocaleString; usage_unknown?: LocaleString; usage_input_tokens?: LocaleString; usage_output_tokens?: LocaleString; usage_total?: LocaleString; usage_estimated_cost?: LocaleString; usage_settings_tip?: LocaleString; usage_load_failed?: LocaleString; usage_personality_none?: LocaleString; no_personalities?: LocaleString; available_personalities?: LocaleString; personality_switch_hint?: LocaleString; personalities_load_failed?: LocaleString; personality_cleared?: LocaleString; personality_set?: LocaleString; failed_colon?: LocaleString; // ui.js no_workspace?: LocaleString; workspace_empty_no_path?: LocaleString; workspace_empty_dir?: LocaleString; dialog_confirm_title?: LocaleString; dialog_prompt_title?: LocaleString; dialog_confirm_btn?: LocaleString; // workspace.js unsaved_confirm?: LocaleString; discard?: LocaleString; save?: LocaleString; edit?: LocaleString; clear?: LocaleString; create?: LocaleString; remove?: LocaleString; save_title?: LocaleString; edit_title?: LocaleString; saved?: LocaleString; save_failed?: LocaleString; image_load_failed?: LocaleString; file_open_failed?: LocaleString; downloading?: LocaleStringFnPath; double_click_rename?: LocaleString; renamed_to?: LocaleString; rename_failed?: LocaleString; delete_title?: LocaleString; delete_confirm?: LocaleStringFnPath; deleted?: LocaleString; delete_failed?: LocaleString; new_file_prompt?: LocaleString; project_name_prompt?: LocaleString; created?: LocaleString; create_failed?: LocaleString; new_folder_prompt?: LocaleString; folder_created?: LocaleString; folder_create_failed?: LocaleString; remove_title?: LocaleString; empty_dir?: LocaleString; upload_failed?: LocaleString; all_uploads_failed?: LocaleStringFn; // settings panel settings_title?: LocaleString; settings_save_btn?: LocaleString; settings_label_model?: LocaleString; settings_label_send_key?: LocaleString; settings_label_theme?: LocaleString; settings_label_skin?: LocaleString; settings_label_language?: LocaleString; settings_label_token_usage?: LocaleString; settings_label_bubble_layout?: LocaleString; settings_label_cli_sessions?: LocaleString; settings_label_sync_insights?: LocaleString; settings_label_check_updates?: LocaleString; settings_label_bot_name?: LocaleString; settings_label_password?: LocaleString; settings_saved?: LocaleString; settings_save_failed?: LocaleString; settings_load_failed?: LocaleString; settings_saved_pw?: LocaleString; settings_saved_pw_updated?: LocaleString; // login page login_title?: LocaleString; login_subtitle?: LocaleString; login_placeholder?: LocaleString; login_btn?: LocaleString; login_invalid_pw?: LocaleString; login_conn_failed?: LocaleString; // Sidebar & Tabs tab_chat?: LocaleString; tab_tasks?: LocaleString; tab_skills?: LocaleString; tab_memory?: LocaleString; tab_workspaces?: LocaleString; tab_profiles?: LocaleString; new_conversation?: LocaleString; filter_conversations?: LocaleString; session_time_unknown?: LocaleString; session_time_just_now?: LocaleString; session_time_minutes_ago?: LocaleStringFn; session_time_hours_ago?: LocaleStringFn; session_time_days_ago?: LocaleStringFn; session_time_last_week?: LocaleString; session_time_bucket_today?: LocaleString; session_time_bucket_yesterday?: LocaleString; session_time_bucket_this_week?: LocaleString; session_time_bucket_last_week?: LocaleString; session_time_bucket_older?: LocaleString; scheduled_jobs?: LocaleString; new_job?: LocaleString; loading?: LocaleString; search_skills?: LocaleString; new_skill?: LocaleString; personal_memory?: LocaleString; workspace_desc?: LocaleString; new_profile?: LocaleString; transcript?: LocaleString; download_transcript?: LocaleString; import?: LocaleString; // Settings detail settings_label_sound?: LocaleString; settings_desc_sound?: LocaleString; settings_label_notifications?: LocaleString; settings_desc_notifications?: LocaleString; settings_desc_token_usage?: LocaleString; settings_desc_bubble_layout?: LocaleString; settings_desc_cli_sessions?: LocaleString; settings_desc_sync_insights?: LocaleString; settings_desc_check_updates?: LocaleString; settings_desc_bot_name?: LocaleString; settings_desc_password?: LocaleString; password_placeholder?: LocaleString; disable_auth?: LocaleString; sign_out?: LocaleString; cancel?: LocaleString; create_job?: LocaleString; save_skill?: LocaleString; editing?: LocaleString; // Empty state empty_title?: LocaleString; empty_subtitle?: LocaleString; suggest_files?: LocaleString; suggest_schedule?: LocaleString; suggest_plan?: LocaleString; // onboarding onboarding_badge?: LocaleString; onboarding_title?: LocaleString; onboarding_lead?: LocaleString; onboarding_back?: LocaleString; onboarding_continue?: LocaleString; onboarding_skip?: LocaleString; onboarding_skipped?: LocaleString; onboarding_open?: LocaleString; onboarding_step_system_title?: LocaleString; onboarding_step_system_desc?: LocaleString; onboarding_step_setup_title?: LocaleString; onboarding_step_setup_desc?: LocaleString; onboarding_step_workspace_title?: LocaleString; onboarding_step_workspace_desc?: LocaleString; onboarding_step_password_title?: LocaleString; onboarding_step_password_desc?: LocaleString; onboarding_step_finish_title?: LocaleString; onboarding_step_finish_desc?: LocaleString; onboarding_notice_system_ready?: LocaleString; onboarding_notice_system_unavailable?: LocaleString; onboarding_check_agent?: LocaleString; onboarding_check_agent_ready?: LocaleString; onboarding_check_agent_missing?: LocaleString; onboarding_check_password?: LocaleString; onboarding_check_password_enabled?: LocaleString; onboarding_check_password_disabled?: LocaleString; onboarding_check_provider?: LocaleString; onboarding_check_provider_ready?: LocaleString; onboarding_check_provider_partial?: LocaleString; onboarding_check_provider_pending?: LocaleString; onboarding_config_file?: LocaleString; onboarding_env_file?: LocaleString; onboarding_unknown?: LocaleString; onboarding_current_provider?: LocaleString; onboarding_missing_imports?: LocaleString; onboarding_notice_setup_required?: LocaleString; onboarding_notice_setup_already_ready?: LocaleString; onboarding_oauth_provider_ready_title?: LocaleString; onboarding_oauth_provider_ready_body?: LocaleString; onboarding_oauth_provider_not_ready_title?: LocaleString; onboarding_oauth_provider_not_ready_body?: LocaleString; onboarding_oauth_switch_hint?: LocaleString; onboarding_notice_workspace?: LocaleString; onboarding_workspace_label?: LocaleString; onboarding_workspace_or_path?: LocaleString; onboarding_workspace_placeholder?: LocaleString; onboarding_provider_label?: LocaleString; onboarding_quick_setup_badge?: LocaleString; onboarding_api_key_label?: LocaleString; onboarding_api_key_placeholder?: LocaleString; onboarding_api_key_help_prefix?: LocaleString; onboarding_base_url_label?: LocaleString; onboarding_base_url_placeholder?: LocaleString; onboarding_base_url_help?: LocaleString; onboarding_model_label?: LocaleString; onboarding_workspace_help?: LocaleString; onboarding_custom_model_placeholder?: LocaleString; onboarding_custom_model_help?: LocaleString; onboarding_notice_password_enabled?: LocaleString; onboarding_notice_password_recommended?: LocaleString; onboarding_password_label?: LocaleString; onboarding_password_placeholder?: LocaleString; onboarding_password_help?: LocaleString; onboarding_notice_finish?: LocaleString; onboarding_not_set?: LocaleString; onboarding_password_will_enable?: LocaleString; onboarding_password_will_replace?: LocaleString; onboarding_password_keep_existing?: LocaleString; onboarding_password_remains_disabled?: LocaleString; onboarding_password_skipped?: LocaleString; onboarding_finish_help?: LocaleString; onboarding_error_choose_workspace?: LocaleString; onboarding_error_choose_model?: LocaleString; onboarding_error_provider_required?: LocaleString; onboarding_error_base_url_required?: LocaleString; onboarding_error_workspace_required?: LocaleString; onboarding_error_model_required?: LocaleString; onboarding_complete?: LocaleString; // panel/runtime i18n error_prefix?: LocaleString; not_available?: LocaleString; never?: LocaleString; add?: LocaleString; add_failed?: LocaleString; remove_failed?: LocaleString; switch_failed?: LocaleString; name_required?: LocaleString; content_required?: LocaleString; view?: LocaleString; dismiss?: LocaleString; disable?: LocaleString; cron_no_jobs?: LocaleString; cron_status_off?: LocaleString; cron_status_paused?: LocaleString; cron_status_error?: LocaleString; cron_status_active?: LocaleString; cron_next?: LocaleString; cron_last?: LocaleString; cron_run_now?: LocaleString; cron_pause?: LocaleString; cron_resume?: LocaleString; cron_job_name_placeholder?: LocaleString; cron_schedule_placeholder?: LocaleString; cron_prompt_placeholder?: LocaleString; cron_last_output?: LocaleString; cron_all_runs?: LocaleString; cron_hide_runs?: LocaleString; cron_no_runs_yet?: LocaleString; cron_schedule_required_example?: LocaleString; cron_schedule_required?: LocaleString; cron_prompt_required?: LocaleString; cron_job_created?: LocaleString; cron_job_triggered?: LocaleString; cron_job_paused?: LocaleString; cron_job_resumed?: LocaleString; cron_job_updated?: LocaleString; cron_delete_confirm_title?: LocaleString; cron_delete_confirm_message?: LocaleString; cron_job_deleted?: LocaleString; cron_completion_status?: LocaleStringFnStatus; status_failed?: LocaleString; status_completed?: LocaleString; clear_conversation_title?: LocaleString; clear_conversation_message?: LocaleString; clear_failed?: LocaleString; // inline cron labels cron_label_schedule?: LocaleString; cron_label_next_run?: LocaleString; cron_label_last_ran?: LocaleString; cron_label_prompt?: LocaleString; cron_label_edit?: LocaleString; cron_label_delete?: LocaleString; cron_label_never?: LocaleString; cron_running?: LocaleString; skills_no_match?: LocaleString; linked_files?: LocaleString; skill_load_failed?: LocaleString; skill_file_load_failed?: LocaleString; skill_name_required?: LocaleString; skill_updated?: LocaleString; skill_created?: LocaleString; memory_notes_label?: LocaleString; memory_saved?: LocaleString; my_notes?: LocaleString; user_profile?: LocaleString; no_notes_yet?: LocaleString; no_profile_yet?: LocaleString; workspace_choose_path?: LocaleString; workspace_choose_path_meta?: LocaleString; workspace_manage?: LocaleString; workspace_manage_meta?: LocaleString; workspace_use_title?: LocaleString; workspace_use?: LocaleString; workspace_add_path_placeholder?: LocaleString; workspace_paths_validated_hint?: LocaleString; workspace_added?: LocaleString; workspace_remove_confirm_title?: LocaleString; workspace_remove_confirm_message?: LocaleStringFnPath; workspace_removed?: LocaleString; workspace_switch_prompt_title?: LocaleString; workspace_switch_prompt_message?: LocaleString; workspace_switch_prompt_confirm?: LocaleString; workspace_switch_prompt_placeholder?: LocaleString; workspace_not_added?: LocaleString; workspace_already_saved?: LocaleString; workspace_busy_switch?: LocaleString; discard_file_edits_title?: LocaleString; discard_file_edits_message?: LocaleString; workspace_switched_to?: LocaleStringFnPath; profiles_no_profiles?: LocaleString; profile_api_keys_configured?: LocaleString; profile_gateway_running?: LocaleString; profile_gateway_stopped?: LocaleString; profile_active?: LocaleString; profile_no_configuration?: LocaleString; profile_skill_count?: LocaleStringFnCount; profile_use?: LocaleString; profile_switch_title?: LocaleString; profile_delete_title?: LocaleString; profile_default_label?: LocaleString; profile_name_placeholder?: LocaleString; profile_clone_label?: LocaleString; profile_base_url_placeholder?: LocaleString; profile_api_key_placeholder?: LocaleString; manage_profiles?: LocaleString; profiles_load_failed?: LocaleString; profiles_busy_switch?: LocaleString; profile_switched_new_conversation?: LocaleStringFnPath; profile_switched?: LocaleStringFnPath; profile_name_rule?: LocaleString; profile_base_url_rule?: LocaleString; profile_created?: LocaleStringFnPath; profile_delete_confirm_title?: LocaleStringFnPath; profile_delete_confirm_message?: LocaleString; profile_deleted?: LocaleStringFnPath; gateways_no_gateways?: LocaleString; gateway_running?: LocaleString; gateway_stopped?: LocaleString; gateway_stop?: LocaleString; gateway_start?: LocaleString; gateway_restart?: LocaleString; gateway_stop_title?: LocaleString; gateway_start_title?: LocaleString; gateway_restart_title?: LocaleString; gateway_started?: LocaleStringFnPath; gateway_stopped_msg?: LocaleStringFnPath; gateway_restarted?: LocaleStringFnPath; gateway_start_failed?: LocaleString; gateway_stop_failed?: LocaleString; gateway_restart_failed?: LocaleString; gateway_add?: LocaleString; gateway_add_title?: LocaleString; gateway_add_message?: LocaleString; gateway_added?: LocaleStringFnPath; gateway_add_failed?: LocaleString; active_conversation_none?: LocaleString; active_conversation_meta?: LocaleStringFnTitleCount; settings_unsaved_changes?: LocaleString; sign_out_failed?: LocaleString; disable_auth_confirm_title?: LocaleString; disable_auth_confirm_message?: LocaleString; auth_disabled?: LocaleString; disable_auth_failed?: LocaleString; bg_error_single?: LocaleStringFnTitle; bg_error_multi?: LocaleStringFnCount; // Fallback keys [key: string]: LocaleString | LocaleStringFn | LocaleStringFn2 | LocaleStringFnPath | LocaleStringFnName | LocaleStringFnStatus | LocaleStringFnTitle | LocaleStringFnCount | LocaleStringFnTitleCount | undefined; } const LOCALES: Record = { 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', clarify_heading: 'Clarification needed', clarify_hint: 'Pick a choice, or type your own answer below.', clarify_other: 'Other', clarify_send: 'Send', clarify_input_placeholder: 'Type your response\u2026', clarify_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', provider_mismatch_warning: (m, p) => `"${m}" may not work with your configured provider (${p}). Send anyway, or run \`hermes model\` in your terminal to switch.`, provider_mismatch_label: 'Provider mismatch', model_custom_label: 'Custom model ID', model_custom_placeholder: 'e.g. openai/gpt-5.4', model_search_placeholder: 'Search models\u2026', model_search_no_results: 'No models found', // commands.js cmd_clear: 'Clear conversation messages', cmd_compress: 'Manually compress conversation context (usage: /compress [focus topic])', cmd_compact_alias: 'Legacy alias for /compress', 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 appearance (theme: system/dark/light, skin: default/ares/mono/slate/poseidon/sisyphus/charizard)', 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', command_label: 'Command', context_compaction_label: 'Context compaction', reference_only_label: 'Reference only', 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...', compress_running_label: 'Compressing', compress_complete_label: 'Compression complete', compress_failed_label: 'Compression failed', focus_label: 'Focus', 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', slash_skill_badge: 'Skill', slash_skill_desc: 'Invoke this skill', cmd_stop: 'Stop the current response', cmd_title: 'Get or set the session title', cmd_retry: 'Resend the last message', cmd_undo: 'Remove the last exchange', cmd_status: 'Show session info', cmd_voice: 'Toggle microphone input', stream_stopped: 'Response stopped.', no_active_task: 'No active task to stop.', cancel_unavailable: 'Cancel not available.', retry_failed: 'Retry failed: ', undo_failed: 'Undo failed: ', undid_n_messages: 'Removed', undid_messages_suffix: 'message(s).', status_heading: 'Session Status', status_session_id: 'Session ID', status_title: 'Title', status_model: 'Model', status_workspace: 'Workspace', status_personality: 'Personality', status_messages: 'Messages', status_agent_running: 'Agent running', status_yes: 'Yes', status_no: 'No', status_load_failed: 'Failed to load status: ', title_current: 'Current title', title_change_hint: 'Use `/title ` to rename.', title_set: 'Title set to', cmd_webui_only_session: 'This command is not available for CLI-imported sessions.', cmd_voice_use_mic: 'Click the mic button in the composer.', usage_heading: 'Token Usage', usage_default_model: 'default', usage_unknown: 'unknown', usage_input_tokens: 'Input tokens', usage_output_tokens: 'Output tokens', usage_total: 'Total tokens', usage_estimated_cost: 'Estimated cost', usage_settings_tip: 'Note: cost estimates are approximate.', usage_load_failed: 'Failed to load usage: ', usage_personality_none: 'none', 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_empty_no_path: 'No workspace selected. Set a workspace in Settings \u2192 Workspace to browse files.', workspace_empty_dir: 'This workspace is empty.', dialog_confirm_title: 'Confirm action', dialog_prompt_title: 'Enter a value', dialog_confirm_btn: 'Confirm', // workspace.js unsaved_confirm: 'You have unsaved changes in the preview. Discard and navigate?', discard: 'Discard', save: 'Save', edit: 'Edit', clear: 'Clear', create: 'Create', remove: 'Remove', 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):', project_name_prompt: 'Project name:', 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_skin: 'Skin', settings_label_language: 'Language', settings_label_token_usage: 'Show token usage', settings_label_bubble_layout: 'Chat bubble layout', settings_label_cli_sessions: 'Show agent 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 protection enabled and this browser stays signed in', settings_saved_pw_updated: 'Settings saved — password updated', // login page 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', // Sidebar & Tabs tab_chat: 'Chat', tab_tasks: 'Tasks', tab_skills: 'Skills', tab_memory: 'Memory', tab_workspaces: 'Spaces', tab_profiles: 'Profiles', new_conversation: 'New conversation', filter_conversations: 'Filter conversations...', session_time_unknown: 'Unknown', session_time_just_now: 'gerade eben', session_time_minutes_ago: (n) => `vor ${n} Min.`, session_time_hours_ago: (n) => `vor ${n} Std.`, session_time_days_ago: (n) => `vor ${n} Tag${n === 1 ? '' : 'en'}`, session_time_last_week: 'letzte Woche', session_time_bucket_today: 'Heute', session_time_bucket_yesterday: 'Gestern', session_time_bucket_this_week: 'Diese Woche', session_time_bucket_last_week: 'Letzte Woche', session_time_bucket_older: 'Älter', scheduled_jobs: 'Geplante Aufgaben', new_job: '+ Neue Aufgabe', loading: 'Wird geladen...', search_skills: 'Search skills...', new_skill: 'New skill', personal_memory: 'Personal memory', 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 app is in the background.', settings_desc_token_usage: 'Displays input/output token count below each assistant reply. Also toggled with /usage.', settings_desc_bubble_layout: 'Right-align user messages and left-align assistant replies. Off by default to keep code blocks and tool output full-width.', 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\u2026', 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.', // onboarding onboarding_badge: 'FIRST RUN', onboarding_title: 'Welcome to Hermes Web UI', onboarding_lead: 'A quick guided setup will verify Hermes, save a real provider configuration, choose a workspace and model, and optionally protect the app with a password.', onboarding_back: 'Back', onboarding_continue: 'Continue', onboarding_skip: 'Skip setup', onboarding_skipped: 'Setup skipped — using existing config.', onboarding_open: 'Open Hermes', onboarding_step_system_title: 'System check', onboarding_step_system_desc: 'Verify Hermes Agent and config visibility.', onboarding_step_setup_title: 'Provider setup', onboarding_step_setup_desc: 'Save the minimum Hermes provider config.', onboarding_step_workspace_title: 'Workspace + model', onboarding_step_workspace_desc: 'Pick defaults for new sessions and chat.', onboarding_step_password_title: 'Optional password', onboarding_step_password_desc: 'Protect the Web UI before sharing it.', onboarding_step_finish_title: 'Finish', onboarding_step_finish_desc: 'Review and enter the app.', onboarding_notice_system_ready: 'Hermes Agent looks reachable from the Web UI.', onboarding_notice_system_unavailable: 'Hermes Agent is not fully available yet. Bootstrap can install it, but provider setup may still require a terminal.', onboarding_check_agent: 'Hermes Agent', onboarding_check_agent_ready: 'Detected and importable', onboarding_check_agent_missing: 'Missing or partially importable', onboarding_check_password: 'Password', onboarding_check_password_enabled: 'Already enabled', onboarding_check_password_disabled: 'Not enabled yet', onboarding_check_provider: 'Provider config', onboarding_check_provider_ready: 'Ready to chat', onboarding_check_provider_partial: 'Saved but incomplete', onboarding_check_provider_pending: 'Needs verification', onboarding_config_file: 'Config file:', onboarding_env_file: '.env file:', onboarding_unknown: 'Unknown', onboarding_current_provider: 'Current setup:', onboarding_missing_imports: 'Missing imports:', onboarding_notice_setup_required: 'Choose a simple provider path here. Advanced OAuth flows still belong in the Hermes CLI for now.', onboarding_notice_setup_already_ready: 'A working Hermes provider setup is already detected. You can keep it or replace it here.', onboarding_oauth_provider_ready_title: 'Provider already authenticated', onboarding_oauth_provider_ready_body: 'This instance is configured to use an OAuth provider ({provider}) that was set up via the Hermes CLI. No API key is needed here — click Continue to finish setup.', onboarding_oauth_provider_not_ready_title: 'OAuth provider not yet authenticated', onboarding_oauth_provider_not_ready_body: 'This instance is configured to use {provider}, which uses OAuth rather than an API key. Run hermes auth or hermes model in a terminal to authenticate, then reload the Web UI.', onboarding_oauth_switch_hint: 'Or choose a different provider below to switch to an API-key setup:', onboarding_notice_workspace: 'These values reuse the same settings APIs as the normal app.', onboarding_workspace_label: 'Workspace', onboarding_workspace_or_path: 'Or enter a workspace path', onboarding_workspace_placeholder: '/home/you/workspace', onboarding_provider_label: 'Setup mode', onboarding_quick_setup_badge: 'quick setup', onboarding_api_key_label: 'API key', onboarding_api_key_placeholder: 'Leave blank to keep an existing saved key', onboarding_api_key_help_prefix: 'Saved as a secret in your Hermes .env file using', onboarding_base_url_label: 'Base URL', onboarding_base_url_placeholder: 'https://your-endpoint.example/v1', onboarding_base_url_help: 'Use this for OpenAI-compatible routers, self-hosted servers, LiteLLM, Ollama, LM Studio, vLLM, or similar endpoints.', onboarding_model_label: 'Default model', onboarding_workspace_help: 'Pick the model Hermes should use for new chats after setup completes.', onboarding_custom_model_placeholder: 'your-model-name', onboarding_custom_model_help: 'For custom endpoints, enter the exact model ID your server expects.', onboarding_notice_password_enabled: 'A password is already configured. Enter a new one only if you want to replace it.', onboarding_notice_password_recommended: 'Optional but recommended if you will expose the UI beyond localhost.', onboarding_password_label: 'Password (optional)', onboarding_password_placeholder: 'Leave blank to skip', onboarding_password_help: 'Passwords are stored through the existing settings API and hashed server-side.', onboarding_notice_finish: 'You can reopen Settings later to change any of this.', onboarding_not_set: 'Not set', onboarding_password_will_enable: 'Will be enabled', onboarding_password_will_replace: 'Will be replaced', onboarding_password_keep_existing: 'Keep current password', onboarding_password_remains_disabled: 'Will remain disabled', onboarding_password_skipped: 'Skipped for now', onboarding_finish_help: 'Finishing stores onboarding_completed in settings and drops you into the normal app.', onboarding_error_choose_workspace: 'Choose a workspace before continuing.', onboarding_error_choose_model: 'Choose a model before continuing.', onboarding_error_provider_required: 'Choose a setup mode before continuing.', onboarding_error_base_url_required: 'Base URL is required for custom endpoints.', onboarding_error_workspace_required: 'Workspace is required.', onboarding_error_model_required: 'Model is required.', onboarding_complete: 'Onboarding complete', // panel/runtime i18n error_prefix: 'Error: ', not_available: 'N/A', never: 'never', add: 'Add', add_failed: 'Add failed: ', remove_failed: 'Remove failed: ', switch_failed: 'Switch failed: ', name_required: 'Name is required', content_required: 'Content is required', view: 'View', dismiss: 'Dismiss', disable: 'Disable', cron_no_jobs: 'Keine geplanten Aufgaben gefunden.', cron_status_off: 'AUS', cron_status_paused: 'PAUSIERT', cron_status_error: 'FEHLER', cron_status_active: 'AKTIV', cron_next: 'N\u00e4chste', cron_last: 'Letzte', cron_run_now: 'Jetzt starten', cron_pause: 'Pausieren', cron_resume: 'Fortsetzen', cron_job_name_placeholder: 'Aufgabenname', cron_schedule_placeholder: 'Zeitplan', cron_prompt_placeholder: 'Prompt', cron_last_output: 'Letzte Ausgabe', cron_all_runs: 'Alle Ausf\u00fchrungen', cron_hide_runs: 'Ausf\u00fchrungen verbergen', cron_no_runs_yet: '(noch keine Ausf\u00fchrung)', cron_schedule_required_example: 'Zeitplan erforderlich (z.B. "0 9 * * *" oder "every 1h")', cron_schedule_required: 'Zeitplan erforderlich', cron_prompt_required: 'Prompt erforderlich', cron_loading: 'Laden...', cron_add_skills_placeholder: 'Skills hinzufügen (optional)...', cron_deliver_local: 'Lokal (nur speichern)', cron_deliver_discord: 'Discord', cron_deliver_telegram: 'Telegram', cron_job_created: 'Aufgabe erstellt', cron_job_triggered: 'Aufgabe gestartet', cron_job_paused: 'Aufgabe pausiert', cron_job_resumed: 'Aufgabe fortgesetzt', cron_job_updated: 'Aufgabe aktualisiert', cron_delete_confirm_title: 'Aufgabe l\u00f6schen', cron_delete_confirm_message: 'Dies kann nicht r\u00fcckg\u00e4ngig gemacht werden.', cron_job_deleted: 'Aufgabe gel\u00f6scht', cron_completion_status: (name, status) => `Aufgabe "${name}" ${status}`, status_failed: 'fehlgeschlagen', status_completed: 'abgeschlossen', clear_conversation_title: 'Chat leeren', clear_conversation_message: 'Alle Nachrichten löschen? Dies kann nicht rückgängig gemacht werden.', clear_failed: 'Leeren fehlgeschlagen: ', cron_running: 'L\u00e4uft...', // inline cron labels (used directly in HTML templates) cron_label_schedule: 'Zeitplan', cron_label_next_run: 'N\u00e4chste Ausf\u00fchrung', cron_label_last_ran: 'Zuletzt gelaufen', cron_label_prompt: 'Prompt', cron_label_edit: 'Bearbeiten', cron_label_delete: 'L\u00f6schen', cron_label_never: 'nie', skills_no_match: 'Keine Skills gefunden.', linked_files: 'Linked Files', skill_load_failed: 'Could not load skill: ', skill_file_load_failed: 'Could not load file: ', skill_name_required: 'Skill name is required', skill_updated: 'Skill updated', skill_created: 'Skill created', memory_notes_label: 'memory (notes)', memory_saved: 'Memory saved', my_notes: 'My Notes', user_profile: 'User Profile', no_notes_yet: 'No notes yet.', no_profile_yet: 'No profile yet.', workspace_choose_path: 'Choose workspace path', workspace_choose_path_meta: 'Add a validated path and switch this conversation', workspace_manage: 'Manage workspaces', workspace_manage_meta: 'Open the Spaces panel', workspace_use_title: 'Use in current session', workspace_use: 'Use', workspace_add_path_placeholder: 'Add workspace path (e.g. /home/user/my-project)', workspace_paths_validated_hint: 'Paths are validated as existing directories before saving.', workspace_added: 'Workspace added', workspace_remove_confirm_title: 'Remove workspace', workspace_remove_confirm_message: (path) => `Remove "${path}"?`, workspace_removed: 'Workspace removed', workspace_switch_prompt_title: 'Switch workspace', workspace_switch_prompt_message: 'Enter an absolute workspace path to add and switch this conversation to.', workspace_switch_prompt_confirm: 'Switch', workspace_switch_prompt_placeholder: '/Users/you/project', workspace_not_added: 'Workspace was not added', workspace_already_saved: 'Workspace already saved — choose it from the list', workspace_busy_switch: 'Cannot switch workspace while agent is running', discard_file_edits_title: 'Discard file edits?', discard_file_edits_message: 'Switching workspaces will discard unsaved file edits in the preview.', workspace_switched_to: (name) => `Switched to ${name}`, profiles_no_profiles: 'No profiles found.', profile_api_keys_configured: 'API keys configured', profile_gateway_running: 'Gateway running', profile_gateway_stopped: 'Gateway stopped', profile_active: 'ACTIVE', profile_no_configuration: 'No configuration', profile_skill_count: (count) => `${count} skill${count === 1 ? '' : 's'}`, profile_use: 'Use', profile_switch_title: 'Switch to this profile', profile_delete_title: 'Delete this profile', profile_default_label: '(default)', profile_name_placeholder: 'Profile name (lowercase, a-z 0-9 hyphens)', profile_clone_label: 'Clone config from active profile', profile_base_url_placeholder: 'Base URL (optional, e.g. http://localhost:11434)', profile_api_key_placeholder: 'API key (optional)', manage_profiles: 'Manage profiles', profiles_load_failed: 'Failed to load profiles', profiles_busy_switch: 'Cannot switch profiles while agent is running', profile_switched_new_conversation: (name) => `Switched to profile: ${name} — new conversation started`, profile_switched: (name) => `Switched to profile: ${name}`, profile_name_rule: 'Lowercase letters, numbers, hyphens, underscores only', profile_base_url_rule: 'Base URL must start with http:// or https://', profile_created: (name) => `Profile created: ${name}`, profile_delete_confirm_title: (name) => `Delete profile "${name}"?`, profile_delete_confirm_message: 'This removes all config, skills, memory, and sessions for this profile.', profile_deleted: (name) => `Profile deleted: ${name}`, gateways_no_gateways: 'No gateways configured.', gateway_running: 'Running', gateway_stopped: 'Stopped', gateway_stop: 'Stop', gateway_start: 'Start', gateway_restart: 'Restart', gateway_stop_title: 'Stop this gateway', gateway_start_title: 'Start this gateway', gateway_restart_title: 'Restart this gateway', gateway_started: (name) => `Gateway started: ${name}`, gateway_stopped_msg: (name) => `Gateway stopped: ${name}`, gateway_restarted: (name) => `Gateway restarted: ${name}`, gateway_start_failed: 'Failed to start gateway: ', gateway_stop_failed: 'Failed to stop gateway: ', gateway_restart_failed: 'Failed to restart gateway: ', gateway_add: 'Add Gateway', gateway_add_title: 'Add New Gateway', gateway_add_message: 'Enter gateway name (e.g. telegram, openclaw):', gateway_added: (name) => `Gateway added: ${name}`, gateway_add_failed: 'Failed to add gateway: ', active_conversation_none: 'No active conversation selected.', active_conversation_meta: (title, count) => `${title} · ${count} message${count === 1 ? '' : 's'}`, settings_unsaved_changes: 'You have unsaved changes.', sign_out_failed: 'Sign out failed: ', disable_auth_confirm_title: 'Disable password protection', disable_auth_confirm_message: 'Anyone will be able to access this instance.', auth_disabled: 'Auth disabled — password protection removed', disable_auth_failed: 'Failed to disable auth: ', bg_error_single: (title) => `"${title}" has encountered an error`, bg_error_multi: (count) => `${count} sessions have encountered an error`, }, }; // Helper type for t() function arguments type TranslationValue = LocaleString | LocaleStringFn | LocaleStringFn2 | LocaleStringFnPath | LocaleStringFnName | LocaleStringFnStatus | LocaleStringFnTitle | LocaleStringFnCount | LocaleStringFnTitleCount | undefined; function t(key: string, ...args: unknown[]): string { const lang = (typeof _locale !== 'undefined' && _locale && _locale._lang) ? _locale._lang : 'en'; const locale = LOCALES[lang] || LOCALES['en'] || LOCALES.en; const en = LOCALES['en']; // Try current locale first, fall back to English let val: TranslationValue = locale[key]; if (val === undefined) val = en[key]; if (val === undefined) return key; // Handle function types (parameterized translations) if (typeof val === 'function') { try { return (val as (...args: unknown[]) => string)(...args); } catch { return key; } } // Handle string replacements for simple string values with {0}, {1} style placeholders if (typeof val === 'string' && args.length > 0) { return args.reduce((result, arg, i) => { return result.replace(new RegExp(`\\{${i}\\}`, 'g'), String(arg)); }, val); } return val as string; } // Declare the global _locale variable declare global { var _locale: LocaleEntry | undefined; } export { LOCALES, t }; export type { LocaleEntry };