mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
957 lines
51 KiB
TypeScript
957 lines
51 KiB
TypeScript
export default {
|
||
// Loading states
|
||
'loading': 'Loading…',
|
||
'authorizing': 'Authorizing…',
|
||
'loading.session': 'Loading session…',
|
||
'loading.git': 'Loading Git status…',
|
||
'loading.file': 'Loading file…',
|
||
'loading.files': 'Loading files…',
|
||
'loading.messages': 'Loading messages…',
|
||
'loading.machines': 'Loading machines…',
|
||
'message.copy': 'Copy',
|
||
'message.copied': 'Copied',
|
||
'message.info': 'Message details',
|
||
'message.shareTurn': 'Share turn as image',
|
||
'shareTurn.title': 'Share turn as image',
|
||
'shareTurn.badge': 'Shared turn',
|
||
'shareTurn.generated': 'Generated by HAPI',
|
||
'shareTurn.cancel': 'Cancel',
|
||
'shareTurn.copy': 'Copy',
|
||
'shareTurn.copying': 'Copying…',
|
||
'shareTurn.copied': 'Copied',
|
||
'shareTurn.share': 'Share',
|
||
'shareTurn.sharing': 'Sharing…',
|
||
'shareTurn.download': 'Download',
|
||
'shareTurn.saving': 'Saving…',
|
||
|
||
// Login / Auth
|
||
'login.title': 'HAPI',
|
||
'login.subtitle': 'Vibe Coding Anytime, Anywhere',
|
||
'login.bind.title': 'Bind Telegram',
|
||
'login.submit': 'Sign In',
|
||
'login.bind.submit': 'Bind',
|
||
'login.submitting': 'Signing in…',
|
||
'login.bind.submitting': 'Binding…',
|
||
'login.placeholder': 'Access token',
|
||
'login.error.enterToken': 'Please enter an access token',
|
||
'login.error.bindingUnavailable': 'Binding is unavailable.',
|
||
'login.error.loginUnavailable': 'Login is unavailable.',
|
||
'login.error.bindFailed': 'Binding failed',
|
||
'login.error.authFailed': 'Authentication failed',
|
||
'login.help': 'Needs help?',
|
||
'login.server.default': '(Default)',
|
||
'login.server.custom': '(Custom)',
|
||
'login.server.title': 'Hub URL',
|
||
'login.server.description': 'Set hapi hub origin for API and live updates.',
|
||
'login.server.current': 'Current:',
|
||
'login.server.origin': 'Hub origin',
|
||
'login.server.placeholder': 'https://hapi.example.com',
|
||
'login.server.hint': 'Use http(s) only. Any path is ignored.',
|
||
'login.server.required': 'Hub URL required. Please set it before signing in.',
|
||
'login.server.useSameOrigin': 'Use same origin',
|
||
'login.server.save': 'Save hub',
|
||
'login.footer': 'Designed with',
|
||
'login.footer.for': 'for Vibe Coding',
|
||
'login.footer.copyright': '©',
|
||
|
||
// Sessions page
|
||
'sessions.new': 'New Session',
|
||
'sessions.empty.title': 'No sessions yet',
|
||
'sessions.empty.hint': 'Start a coding session in any folder under your workspace, or browse the tree first.',
|
||
'sessions.empty.startSession': 'Start a session',
|
||
'sessions.empty.browse': 'Browse workspace',
|
||
'sessions.search.placeholder': 'Search sessions…',
|
||
'sessions.search.open': 'Search sessions',
|
||
'sessions.search.clear': 'Clear search',
|
||
'sessions.search.noResults': 'No sessions match your filters.',
|
||
'sessions.timeFilter.label': 'Filter sessions by last activity',
|
||
'sessions.timeFilter.pickStart': 'Select start date',
|
||
'sessions.timeFilter.pickEnd': 'Select end date',
|
||
'sessions.timeFilter.clear': 'Clear',
|
||
'sessions.timeFilter.close': 'Close date picker',
|
||
'sessions.timeFilter.previousMonth': 'Previous month',
|
||
'sessions.timeFilter.nextMonth': 'Next month',
|
||
'sessions.timeFilter.dayWithActivity': '{date}, has session activity',
|
||
'sessions.group.expand': 'Expand {n}',
|
||
'sessions.group.collapse': 'Collapse {n}',
|
||
'sessions.runningSection': 'In progress',
|
||
'sessions.group.new': 'New session in this directory',
|
||
'sessions.machineFilter.label': 'Filter sessions by machine',
|
||
'sessions.machineFilter.all': 'All',
|
||
'codexSync.tooltip': 'Import sessions from Codex into Hapi',
|
||
'codexSync.newSessionAction': 'Import Codex history',
|
||
'codexSync.confirm.title': 'Import Codex sessions',
|
||
'codexSync.confirm.description': 'Choose Codex sessions to import into Hapi',
|
||
// 中文注释:以下文案支撑导入弹窗的多选列表、加载态,以及右上角重启 Codex 客户端按钮提示。
|
||
'codexSync.confirm.selectAll': 'Select all',
|
||
'codexSync.confirm.clearAll': 'Clear all',
|
||
'codexSync.confirm.selectedCount': '{n} sessions selected',
|
||
'codexSync.confirm.empty': 'No local Codex sessions found',
|
||
'codexSync.confirm.current': 'Linked',
|
||
'codexSync.confirm.cwd': 'Working directory',
|
||
'codexSync.confirm.cwdFilter': 'Work directory',
|
||
'codexSync.confirm.cwdFilterAll': 'All work directories',
|
||
'codexSync.confirm.emptyForWorkdir': 'No Codex sessions in this work directory',
|
||
'codexSync.confirm.confirm': 'Import',
|
||
'codexSync.confirm.confirming': 'Importing…',
|
||
'codexSync.confirm.loading': 'Loading local Codex sessions…',
|
||
'codexSync.success.title': 'Import complete',
|
||
'codexSync.success.body': 'Imported {n} Codex session(s) into Hapi.',
|
||
'codexSync.manual.success.title': 'Codex sync complete',
|
||
'codexSync.manual.success.body': 'Synced {n} Codex session(s).',
|
||
'codexSync.manual.failed.title': 'Failed to sync Codex session',
|
||
'codexSync.error.timeout': 'Operation timed out',
|
||
'codexSync.error.active': 'This session is still active. Please wait until it ends and try again.',
|
||
'codexSync.failed.title': 'Failed to import Codex sessions',
|
||
'codexSync.failed.body': 'Failed to import Codex sessions.',
|
||
'codexSync.failed.bodyWithReason': 'Import failed: {reason}',
|
||
'codexSync.restart.tooltip': 'Restart Codex client',
|
||
'codexSync.restart.title': 'Restart Codex client',
|
||
'codexSync.restart.description': 'Restart Codex client now to refresh sessions?',
|
||
'codexSync.restart.confirm': 'Restart',
|
||
'codexSync.restart.confirming': 'Restarting…',
|
||
'codexSync.restart.failed.title': 'Failed to restart Codex client',
|
||
'codexSync.restart.failed.notFound': 'Failed to restart Codex client: not installed or not found',
|
||
'codexSync.restart.failed.body': 'Failed to run the restart script.',
|
||
'codexSync.restart.started.title': 'Restart started',
|
||
'codexSync.restart.started.body': 'Codex client is restarting to refresh sessions.',
|
||
'codexSync.duplicates.confirm.title': 'Duplicate sessions detected',
|
||
'codexSync.duplicates.confirm.description': 'Duplicate sessions were detected. Merge them?',
|
||
'codexSync.duplicates.confirm.confirm': 'Merge',
|
||
'codexSync.duplicates.confirm.confirming': 'Merging…',
|
||
'codexSync.duplicates.detect.failed.title': 'Failed to detect duplicate sessions',
|
||
'codexSync.duplicates.detect.failed.body': 'Failed to detect duplicate sessions.',
|
||
'codexSync.duplicates.merge.success.title': 'Duplicate sessions merged',
|
||
'codexSync.duplicates.merge.success.body': 'Merged duplicates for the selected Codex sessions.',
|
||
'codexSync.duplicates.merge.failed.title': 'Failed to merge duplicate sessions',
|
||
'codexSync.duplicates.merge.failed.body': 'Failed to merge duplicate sessions.',
|
||
|
||
// Session list
|
||
'session.item.path': 'path',
|
||
'session.item.agent': 'agent',
|
||
'session.item.model': 'model',
|
||
'session.item.machine': 'machine',
|
||
'session.item.worktree': 'worktree',
|
||
'session.item.pending': 'pending',
|
||
'session.item.thinking': 'thinking',
|
||
'session.item.running': 'Running',
|
||
'session.item.permission': 'Permission required',
|
||
'session.item.needsInput': 'Needs input',
|
||
'session.item.background': 'Background tasks running',
|
||
'session.item.newActivity': 'New activity',
|
||
'session.item.scheduledMessage': 'Scheduled message pending',
|
||
'session.item.scheduledMessages': '{count} scheduled messages pending',
|
||
'session.tooltip.permission.body': 'Approve:',
|
||
'session.tooltip.input.body': 'Reply to:',
|
||
'session.tooltip.background.count.one': '1 task running',
|
||
'session.tooltip.background.count.other': '{count} tasks running',
|
||
'session.tooltip.scheduled.body': 'Will fire when due.',
|
||
'session.tooltip.scheduled.fires': 'Fires {when}',
|
||
'session.tooltip.scheduled.next': 'Next {when} · +{more} more',
|
||
'session.tooltip.moreCount': '+{count} more',
|
||
'session.time.justNow': 'just now',
|
||
'session.time.minutesAgo': '{n}m ago',
|
||
'session.time.hoursAgo': '{n}h ago',
|
||
'session.time.daysAgo': '{n}d ago',
|
||
'session.time.inLessThanMinute': 'in <1m',
|
||
'session.time.inMinutes': 'in {n}m',
|
||
'session.time.inHours': 'in {n}h',
|
||
'session.time.inDays': 'in {n}d',
|
||
'session.time.soon': 'soon',
|
||
'session.time.importedFromCodex.justNow': 'just imported from Codex',
|
||
'session.time.importedFromCodex.minutesAgo': 'imported from Codex {n}m ago',
|
||
'session.time.importedFromCodex.hoursAgo': 'imported from Codex {n}h ago',
|
||
'session.time.importedFromCodex.daysAgo': 'imported from Codex {n}d ago',
|
||
'session.cursorMigration.banner.title': 'Upgrading Cursor session',
|
||
'session.cursorMigration.banner.body': 'Switching this legacy chat to the safer ACP protocol. This takes 15-20 seconds for sessions with long history; your conversation will resume automatically and any draft text will survive.',
|
||
'session.cursorMigration.bannerAmbiguous.title': 'Cursor session upgrade needs manual review',
|
||
'session.cursorMigration.bannerAmbiguous.body': 'This chat exists on disk in multiple workspace folders or the on-disk size does not match the synced history, so HAPI refused to transplant it automatically. Check hub logs for the candidate list (search for "[migrator] ambiguous legacy store" or "[migrator] size sanity check refused"), delete the stale drawers under ~/.cursor/chats/, and reopen the session.',
|
||
|
||
// Session inactive
|
||
'session.inactive.autoResume': 'This session is inactive. Send a message to resume.',
|
||
'session.inactive.cannotResume': 'This session is inactive and cannot be resumed.',
|
||
|
||
// Session header
|
||
'session.title': 'Files',
|
||
'session.view.returnToChat': 'Return to conversation',
|
||
'session.more': 'More actions',
|
||
'session.outline.open': 'Conversation outline',
|
||
'session.outline.close': 'Close outline',
|
||
'session.outline.title': 'Outline',
|
||
'session.outline.loadOlder': 'Load earlier',
|
||
'session.outline.empty': 'No outline items in loaded messages',
|
||
'session.outline.searchPlaceholder': 'Search outline...',
|
||
'session.outline.searchLabel': 'Search outline items',
|
||
'session.outline.searchResults': '{matched} of {total} items',
|
||
'session.outline.noSearchResults': 'No matching outline items',
|
||
|
||
// Session actions
|
||
'session.action.rename': 'Rename',
|
||
'session.action.export': 'Export conversation',
|
||
'session.action.syncCodex': 'Sync from Codex',
|
||
'session.action.archive': 'Archive',
|
||
'session.action.reopen': 'Reopen',
|
||
'session.action.reopenCursorChecking': 'Checking whether Cursor chat data is still available on the recorded machine.',
|
||
'session.action.reopenCursorMissing': 'Cursor chat data is no longer available on the recorded machine.',
|
||
'session.action.reopenCursorCheckFailed': 'Could not verify Cursor chat data on the recorded machine.',
|
||
'session.action.delete': 'Delete',
|
||
'session.action.copy': 'Copy',
|
||
'session.action.copyReference': 'Copy reference',
|
||
|
||
// Dialogs
|
||
'dialog.uri.title': 'Open this link?',
|
||
'dialog.uri.description': 'This link uses a custom URI scheme. Make sure you trust this link before opening it.',
|
||
'dialog.uri.open': 'Open',
|
||
'dialog.uri.alwaysAllow': 'Always allow {scheme}:',
|
||
'dialog.rename.title': 'Rename Session',
|
||
'dialog.rename.placeholder': 'Session name',
|
||
'dialog.rename.save': 'Save',
|
||
'dialog.rename.saving': 'Saving…',
|
||
'dialog.rename.error': 'Failed to rename. Please try again.',
|
||
'dialog.archive.title': 'Archive Session',
|
||
'dialog.archive.description': 'Are you sure you want to archive "{name}"? This will disconnect active session.',
|
||
'dialog.archive.confirm': 'Archive',
|
||
'dialog.archive.confirming': 'Archiving…',
|
||
'dialog.reopen.errorTitle': 'Could not reopen session',
|
||
'dialog.reopen.dismiss': 'Dismiss',
|
||
'dialog.delete.title': 'Delete Session',
|
||
'dialog.delete.description': 'Are you sure you want to delete "{name}"? This action cannot be undone.',
|
||
'dialog.delete.scratchlist.one': 'This will also delete 1 scratchlist entry.',
|
||
'dialog.delete.scratchlist.other': 'This will also delete {n} scratchlist entries.',
|
||
'dialog.delete.confirm': 'Delete',
|
||
'dialog.delete.confirming': 'Deleting…',
|
||
'dialog.error.default': 'Operation failed. Please try again.',
|
||
|
||
// Session export
|
||
'session.export.title': 'Export conversation',
|
||
'session.export.description': 'Choose a format, then download the full visible conversation and scratchlist notes.',
|
||
'session.export.format.json': 'JSON',
|
||
'session.export.format.json.description': 'Lossless payload with session metadata, messages, and scratchlist notes (attachment metadata only).',
|
||
'session.export.format.markdown': 'Markdown',
|
||
'session.export.format.markdown.description': 'Readable view generated from the same export payload.',
|
||
'session.export.download': 'Download',
|
||
'session.export.downloading': 'Exporting…',
|
||
'session.export.error.noApi': 'Not connected to server',
|
||
'session.export.error.default': 'Failed to export conversation',
|
||
'session.export.toast.success.title': 'Conversation exported',
|
||
'session.export.toast.success.body': 'Downloaded {filename}',
|
||
'session.export.toast.error.title': 'Export failed',
|
||
|
||
// Mermaid diagrams
|
||
'mermaid.openFullscreen': 'Open diagram full screen',
|
||
'mermaid.viewerTitle': 'Diagram',
|
||
'mermaid.loading': 'Loading diagram…',
|
||
'mermaid.renderError': 'Could not render diagram.',
|
||
|
||
// Common buttons
|
||
'button.cancel': 'Cancel',
|
||
'button.save': 'Save',
|
||
'button.confirm': 'Confirm',
|
||
'button.close': 'Close',
|
||
'button.dismiss': 'Dismiss',
|
||
'button.copy': 'Copy',
|
||
'button.paste': 'Paste',
|
||
|
||
// New session form
|
||
'newSession.title': 'Create Session',
|
||
'newSession.machine': 'Machine',
|
||
'newSession.directory': 'Directory',
|
||
'newSession.placeholder': '/path/to/project',
|
||
'newSession.browse': 'Browse',
|
||
'newSession.recent': 'Recent paths',
|
||
'newSession.type': 'Session type',
|
||
'newSession.type.simple': 'Simple',
|
||
'newSession.type.simple.desc': 'Use selected directory as-is',
|
||
'newSession.type.worktree': 'Worktree',
|
||
'newSession.type.worktree.desc': 'Create a new worktree next to repo (Cursor uses native ~/.cursor/worktrees)',
|
||
'newSession.type.worktree.placeholder': 'feature-x (default 1228-xxxx)',
|
||
'newSession.agent': 'Agent',
|
||
'newSession.model': 'Model',
|
||
'newSession.effort': 'Effort',
|
||
'newSession.model.optional': 'optional',
|
||
'newSession.model.loadFailed': 'Failed to load models',
|
||
'newSession.model.selectVariant': 'Select variant',
|
||
'newSession.model.cursorUnavailable': 'No Cursor models yet. Start a Cursor session once, or use Auto.',
|
||
'newSession.opencodeModel.loading': 'Discovering OpenCode models…',
|
||
'newSession.opencodeModel.loadFailed': 'Failed to load OpenCode models',
|
||
'newSession.opencodeModel.retry': 'Retry',
|
||
'newSession.opencodeModel.empty': 'No OpenCode models discovered for this directory',
|
||
'newSession.opencodeModel.default': 'Default',
|
||
'newSession.reasoningEffort': 'Reasoning effort',
|
||
'newSession.collaborationMode': 'Collaboration mode',
|
||
'newSession.fastMode': 'Fast mode',
|
||
'newSession.yolo': 'YOLO mode',
|
||
'newSession.yolo.title': 'Bypass approvals and sandbox',
|
||
'newSession.yolo.desc': 'Uses dangerous agent flags when spawning.',
|
||
'newSession.grokAutoUnavailable': 'unavailable',
|
||
'newSession.grokAutoUnavailableDesc': 'Grok did not enable Auto permissions for this account or CLI build.',
|
||
'newSession.create': 'Create',
|
||
'newSession.creating': 'Creating…',
|
||
|
||
'session.directoryMissingSimple': 'Directory does not exist. Creating the session will create it automatically.',
|
||
'session.directoryMissingSimpleConfirm': 'Directory does not exist. Click again to create it automatically.',
|
||
'session.directoryMissingWorktree': 'Worktree sessions require an existing repository directory.',
|
||
'session.codexModelsLoadFailed': 'Failed to load Codex models',
|
||
'session.createAndCreateDirectory': 'Create and make directory',
|
||
|
||
// Machine
|
||
'machine.unknown': 'Unknown platform',
|
||
'machine.os.windows': 'Windows',
|
||
'machine.os.linux': 'Linux',
|
||
'machine.os.macos': 'macOS',
|
||
'machine.os.unknown': 'Unknown OS',
|
||
'machine.header.sessionCount': '{n} sessions',
|
||
'machine.health.tooltip.title': 'Machine capacity',
|
||
'machine.health.status.healthy': 'Healthy — room for more agents',
|
||
'machine.health.status.elevated': 'Elevated — new agents may run slower',
|
||
'machine.health.status.high': 'High pressure — avoid spawning more here',
|
||
'machine.health.status.unknown': 'Metrics unavailable',
|
||
'machine.health.metric.cpu': 'CPU',
|
||
'machine.health.metric.ram': 'RAM',
|
||
'machine.health.tooltip.load': 'Run queue (1 min): {value}',
|
||
'machine.health.tooltip.loadShort': 'Load (1m)',
|
||
'machine.health.tooltip.uptimeShort': 'Uptime',
|
||
'machine.health.uptimeCompact': 'up {value}',
|
||
'machine.health.tooltip.hint': 'Updated every ~20s from the runner on this machine',
|
||
'machine.health.aria.cpu': 'CPU {n} percent',
|
||
'machine.health.aria.ram': 'RAM {n} percent',
|
||
'machine.health.aria.unknown': 'Machine health unavailable',
|
||
|
||
// Chat
|
||
'chat.placeholder': 'Type a message…',
|
||
'chat.send': 'Send',
|
||
'chat.abort': 'Abort',
|
||
'chat.settings': 'Settings',
|
||
'chat.terminal': 'Terminal',
|
||
'chat.switchRemote': 'Switch to remote mode',
|
||
'chat.sendError.fallback': "Couldn't send your message. Edit and try again.",
|
||
'chat.sendError.sessionInactive': 'This session is archived. Reopen it to send your message.',
|
||
'chat.sendError.sessionInactive.action': 'Reopen',
|
||
|
||
// Codex review
|
||
'codexReview.title': 'Codex review',
|
||
'codexReview.findings': '{count} findings',
|
||
'codexReview.confidence': 'Confidence {value}',
|
||
'codexReview.location.missing': 'No location',
|
||
|
||
// Terminal
|
||
'terminal.commandName': 'Command',
|
||
'terminal.commandMessage': 'Command message',
|
||
'terminal.commandArgs': 'Command args',
|
||
'terminal.stdout': 'Stdout',
|
||
'terminal.stderr': 'Stderr',
|
||
'terminal.unsupportedWindows': 'Remote terminal is unavailable on this host.',
|
||
'terminal.paste.fallbackTitle': 'Paste input',
|
||
'terminal.paste.fallbackDescription': 'Clipboard read is unavailable. Paste your text below.',
|
||
'terminal.paste.placeholder': 'Paste terminal input here…',
|
||
|
||
// Code block
|
||
'code.copy': 'Copy',
|
||
'code.truncated': 'Preview truncated — open details for full output',
|
||
'code.wrap.enable': 'Enable word wrap',
|
||
'code.wrap.disable': 'Disable word wrap',
|
||
|
||
// Diff view
|
||
'diff.title': 'Diff',
|
||
'diff.view': 'View',
|
||
|
||
// Files page
|
||
'files.page.title': 'Files',
|
||
'files.page.refresh': 'Refresh',
|
||
'files.page.refreshFilesystem': 'Refresh filesystem view',
|
||
'files.page.searchPlaceholder': 'Search files',
|
||
'files.projectRoot': 'project root',
|
||
'files.branch.detached': 'detached HEAD',
|
||
'files.branch.summary': '{staged} staged, {unstaged} unstaged',
|
||
'files.tab.changes': 'Changes',
|
||
'files.tab.directories': 'Directories',
|
||
'files.search.empty': 'No files match your search.',
|
||
'files.search.error.failed': 'Failed to search files.',
|
||
'files.search.error.failedWithDetail': 'Failed to search files: {error}',
|
||
'files.changes.section.staged': 'Staged Changes ({n})',
|
||
'files.changes.section.unstaged': 'Unstaged Changes ({n})',
|
||
'files.changes.empty.unavailable': 'Git status unavailable. Use Directories to browse all files, or search.',
|
||
'files.changes.empty.none': 'No changes detected. Use Directories to browse all files, or search.',
|
||
'files.changes.error.gitStatusUnavailable': 'Git status unavailable.',
|
||
'files.changes.error.gitStatusUnavailableWithDetail': 'Git status unavailable: {error}',
|
||
'files.changes.error.unstagedDiffUnavailableWithDetail': 'Unstaged diff unavailable: {error}',
|
||
'files.changes.error.stagedDiffUnavailableWithDetail': 'Staged diff unavailable: {error}',
|
||
'files.directories.empty': 'Empty directory.',
|
||
'files.directories.error.listFailed': 'Failed to list directory.',
|
||
'files.directories.error.listFailedWithDetail': 'Failed to list directory: {error}',
|
||
'files.directories.download': 'Download file',
|
||
'files.directories.downloadNamed': 'Download {name}',
|
||
'files.directories.download.error.title': 'Download failed',
|
||
'files.directories.download.error.default': 'Failed to download file.',
|
||
'files.sort.title': 'Sort files',
|
||
'files.sort.by': 'Sort by',
|
||
'files.sort.name': 'Name',
|
||
'files.sort.modified': 'Modified time',
|
||
'files.sort.size': 'File size',
|
||
'files.sort.nameAsc': 'A to Z',
|
||
'files.sort.nameDesc': 'Z to A',
|
||
'files.sort.oldest': 'Oldest first',
|
||
'files.sort.newest': 'Newest first',
|
||
'files.sort.smallest': 'Smallest first',
|
||
'files.sort.largest': 'Largest first',
|
||
|
||
// File page
|
||
'file.page.fallbackName': 'File',
|
||
'file.page.unknownPath': 'Unknown path',
|
||
'file.page.copyPath': 'Copy path',
|
||
'file.page.copyContent': 'Copy file content',
|
||
'file.page.download': 'Download file',
|
||
'file.page.tab.diff': 'Diff',
|
||
'file.page.tab.file': 'File',
|
||
'file.page.tab.source': 'Source',
|
||
'file.page.tab.preview': 'Preview',
|
||
'file.page.missingPath': 'No file path provided.',
|
||
'file.page.binary': 'This looks like a binary file. It cannot be displayed.',
|
||
'file.page.imagePreviewAlt': 'Image preview for {name}',
|
||
'file.page.empty': 'File is empty.',
|
||
'file.page.noChanges': 'No changes to display.',
|
||
'file.error.readFailed': 'Failed to read file.',
|
||
'file.error.readFailedWithDetail': 'Failed to read file: {error}',
|
||
'file.error.diffUnavailable': 'Diff unavailable.',
|
||
'file.error.diffUnavailableWithDetail': 'Diff unavailable: {error}',
|
||
|
||
// Tool card
|
||
'tool.askQuestion': 'Other',
|
||
'tool.edit': 'Edit',
|
||
'tool.multiedit': 'Multi-edit',
|
||
'tool.write': 'Write',
|
||
'tool.todo': 'Todo',
|
||
'tool.confirm': 'Confirm',
|
||
'tool.deny': 'Deny',
|
||
'tool.abort': 'Abort',
|
||
'tool.allow': 'Allow',
|
||
'tool.allowAll': 'Allow All Edits',
|
||
'tool.denyAll': 'Deny All Edits',
|
||
'tool.approved': 'Approved',
|
||
'tool.rejected': 'Rejected',
|
||
'tool.cancel': 'Cancel',
|
||
'tool.exitPlan': 'Exit Plan Mode',
|
||
'tool.patch': 'Patch',
|
||
'tool.input': 'Input',
|
||
'tool.trace': 'Trace',
|
||
'tool.trace.callsSuffix': 'calls',
|
||
'tool.result': 'Result',
|
||
'tool.duration': 'Duration',
|
||
'tool.startedAt': 'Started',
|
||
'tool.completedAt': 'Finished',
|
||
'tool.semanticTitle.readFile': 'Read file',
|
||
'tool.semanticTitle.runShell': 'Run shell',
|
||
'tool.semanticTitle.search': 'Search',
|
||
'tool.semanticTitle.openUrl': 'Open URL',
|
||
'tool.semanticTitle.query': 'Query',
|
||
'tool.questionsAnswers': 'Questions & Answers',
|
||
'tool.submit': 'Submit',
|
||
'tool.submitting': 'Submitting…',
|
||
'tool.prev': '← Prev',
|
||
'tool.next': 'Next →',
|
||
'tool.yes': 'Yes',
|
||
'tool.yesForSession': 'Yes For Session',
|
||
'tool.allowForSession': 'Allow For Session',
|
||
'tool.abortLabel': 'Abort',
|
||
'tool.other': 'Other',
|
||
'tool.otherDescription': 'Type your own answer',
|
||
'tool.question': 'Question',
|
||
'tool.selectOption': 'Please select at least one option or type an answer.',
|
||
'tool.waitingForApproval': 'Waiting for approval…',
|
||
'tool.canceled': 'Canceled',
|
||
'tool.approvedForSession': 'Approved For Session',
|
||
'tool.aborted': 'Aborted',
|
||
'tool.approvedAllowAllEdits': 'Approved: Allow All Edits',
|
||
'tool.requestFailed': 'Request failed',
|
||
'tool.askUserQuestion.fallback': 'AskUserQuestion payload is not in the expected format. Type your answer:',
|
||
'tool.askUserQuestion.placeholder': 'Type your answer…',
|
||
'tool.askUserQuestion.otherPlaceholder': 'Or type your own answer…',
|
||
'tool.requestUserInput.textPlaceholder': 'Type your answer…',
|
||
'tool.requestUserInput.noteLabel': 'Additional note (optional)',
|
||
'tool.requestUserInput.notePlaceholder': 'Add a note…',
|
||
'tool.requestUserInput.popupBlocked': 'Could not open the sign-in page. Allow popups and try again.',
|
||
'toolGroup.title': 'Tool activity',
|
||
'toolGroup.friendly.inspectTarget': 'Inspect {target}',
|
||
'toolGroup.friendly.searchTarget': 'Search “{target}”',
|
||
'toolGroup.friendly.runTarget': 'Run {target}',
|
||
'toolGroup.friendly.editTarget': 'Edit {target}',
|
||
'toolGroup.primary.fileTargets': '{target} +{n}',
|
||
'toolGroup.primary.commandTargets': '{target} +{n}',
|
||
'toolGroup.primary.searchTargets': '{target} +{n}',
|
||
'toolGroup.primary.urlTargets': '{target} +{n}',
|
||
'toolGroup.primary.otherTargets': '{target} +{n}',
|
||
'toolGroup.summary.read': 'Read {n}',
|
||
'toolGroup.summary.mutation': 'Edit {n}',
|
||
'toolGroup.summary.command': 'Run {n}',
|
||
'toolGroup.summary.search': 'Search {n}',
|
||
'toolGroup.summary.web': 'Web {n}',
|
||
'toolGroup.summary.other': 'Tool {n}',
|
||
'toolGroup.friendly.inspectFiles': 'Inspect project files',
|
||
'toolGroup.friendly.searchContent': 'Search project content',
|
||
'toolGroup.friendly.runCommands': 'Run project commands',
|
||
'toolGroup.friendly.editFiles': 'Edit project files',
|
||
'toolGroup.friendly.openWeb': 'Browse web content',
|
||
'toolGroup.friendly.genericCommand': 'Run command',
|
||
'toolGroup.friendly.genericTool': 'Use tool',
|
||
'toolGroup.badge.running': '{n} running',
|
||
'toolGroup.badge.pending': '{n} pending',
|
||
'toolGroup.badge.error': '{n} error',
|
||
'toolGroup.badge.fileTargets': '{n} files',
|
||
'toolGroup.toolCount': '{n} actions',
|
||
'toolGroup.loadingOlderHistory': 'Loading earlier tool activity…',
|
||
'toolGroup.historyUnavailable': 'Earlier tool activity is unavailable.',
|
||
'toolGroup.rowStatus.running': 'Running',
|
||
'toolGroup.rowStatus.pending': 'Pending',
|
||
'toolGroup.rowStatus.error': 'Error',
|
||
'toolGroup.codex.exploring': 'Exploring',
|
||
'toolGroup.codex.explored': 'Explored',
|
||
'toolGroup.codex.read': 'Read',
|
||
'toolGroup.codex.list': 'List',
|
||
'toolGroup.codex.search': 'Search',
|
||
'toolGroup.codex.searchIn': '{query} in {path}',
|
||
|
||
// Composer buttons
|
||
'composer.settings': 'Settings',
|
||
'composer.terminal': 'Terminal',
|
||
'composer.abort': 'Abort',
|
||
'composer.switchRemote': 'Switch to remote mode',
|
||
'composer.attach': 'Attach file',
|
||
'composer.dropToAttach': 'Drop to attach',
|
||
'composer.send': 'Send',
|
||
'composer.stop': 'Stop',
|
||
'composer.voice': 'Voice assistant',
|
||
'composer.scheduleSend': 'Schedule send',
|
||
'composer.scheduleRelativeTab': 'Relative',
|
||
'composer.scheduleSpecificTab': 'Specific',
|
||
'composer.scheduleSpecificHint': 'Max 7 days. Requires the hub running; the CLI catches up the next time it connects after that time.',
|
||
'composer.scheduleErrorPast': 'Scheduled time must be in the future.',
|
||
'composer.scheduleErrorTooFar': 'Maximum schedule time is 7 days.',
|
||
'queuedMessages.scheduledFor': 'Scheduled for {time}',
|
||
'queuedMessages.editAlreadyInvoked': "Message already sent — it can't be edited",
|
||
|
||
// Scratchlist (per-session workbench, issue #11)
|
||
'scratchlist.title': 'Scratchlist',
|
||
'scratchlist.heldLabel': 'held — not sent',
|
||
'scratchlist.empty': 'empty',
|
||
'scratchlist.count.one': '1 item',
|
||
'scratchlist.count.other': '{n} items',
|
||
'scratchlist.attachmentOnly': '(attachment)',
|
||
'scratchlist.emptyHint': 'Park notes, drafts, or ideas here. Nothing is sent until you promote it.',
|
||
'scratchlist.drawerHint': 'Type below — Send adds the next message to the scratchlist instead of the chat. Click the note icon again to leave.',
|
||
'scratchlist.toggleAriaLabel': 'Scratchlist drawer',
|
||
'scratchlist.toggleTooltip': 'Scratchlist — park notes & drafts (Ctrl/Cmd+Shift+S)',
|
||
'scratchlist.sendToScratchlist': 'Send to scratchlist',
|
||
'scratchlist.fueTitle': 'New: Scratchlist',
|
||
'scratchlist.fueBody': 'Park notes & drafts here without sending. The Send button glows amber while you stash; click the icon (or Ctrl/Cmd+Shift+S) again to leave.',
|
||
'scratchlist.addPlaceholder': 'Note, draft, or idea — Enter to add',
|
||
'scratchlist.addAriaLabel': 'Add scratchlist entry',
|
||
'scratchlist.add': 'Add',
|
||
'scratchlist.atCap': 'Scratchlist is at its {n}-entry cap. Delete an old entry to add more.',
|
||
'scratchlist.confirmDelete': 'Delete this scratchlist entry? This cannot be undone.',
|
||
'scratchlist.listAriaLabel': 'Scratchlist entries',
|
||
'scratchlist.action.moveUp': 'Move entry up',
|
||
'scratchlist.action.moveDown': 'Move entry down',
|
||
'scratchlist.action.promoteToComposer': 'Copy into composer',
|
||
'scratchlist.action.promoteToQueue': 'Send to queue',
|
||
'scratchlist.action.copy': 'Copy text to clipboard (not images)',
|
||
'scratchlist.action.copied': 'Copied!',
|
||
'scratchlist.action.delete': 'Delete entry',
|
||
'scratchlist.entry.lastSaved': 'Saved {time}',
|
||
'scratchlist.entry.lastSavedAriaLabel': 'Entry saved {time}',
|
||
'scratchlist.migrationBanner.title': 'Scratchlist now syncs across devices',
|
||
'scratchlist.migrationBanner.body': 'Your existing notes were copied to the hub - nothing was lost. From now on, edits in this session will appear on every device that you use.',
|
||
'scratchlist.migrationBanner.dismiss': 'Got it',
|
||
'fue.newFeatureDot': 'New feature available',
|
||
'fue.gotIt': 'Got it',
|
||
'fue.closeAriaLabel': 'Close explainer',
|
||
'composer.codexSlashUnsupported.title': 'Codex command unavailable',
|
||
'composer.codexSlashUnsupported.body': 'HAPI remote mode does not yet run built-in Codex slash commands like {command}. Use natural language instead, or run it in the local Codex TUI.',
|
||
|
||
// Voice assistant
|
||
'voice.connecting': 'Connecting...',
|
||
'voice.active': 'Active',
|
||
'voice.muted': 'Muted',
|
||
'voice.error': 'Error',
|
||
'voice.mute': 'Mute',
|
||
'voice.unmute': 'Unmute',
|
||
'voice.end': 'End',
|
||
'voice.error.micPermission': 'Microphone permission denied',
|
||
'voice.error.network': 'Network error',
|
||
'voice.error.notInitialized': 'Voice session not initialized',
|
||
'voice.error.startFailed': 'Failed to start voice session',
|
||
'voice.error.notAllowed': 'Voice not allowed',
|
||
'voice.error.connection': 'Connection error',
|
||
'voice.dismiss': 'Dismiss',
|
||
|
||
// Banners
|
||
'offline.title': 'Offline',
|
||
'offline.message': 'You are currently offline. Some features may be limited.',
|
||
'syncing.title': 'Syncing…',
|
||
'syncing.message': 'Your data is being synchronized.',
|
||
'reconnecting.message': 'Reconnecting...',
|
||
'reconnecting.reason.error': 'stream error',
|
||
'reconnecting.reason.closed': 'stream closed',
|
||
'reconnecting.reason.heartbeatTimeout': 'heartbeat timeout',
|
||
'reconnecting.reason.visibilityRecovery': 'resuming after background',
|
||
'pwa.update.title': 'New version available',
|
||
'pwa.update.body': 'Reload to get the latest HAPI',
|
||
'pwa.update.reload': 'Reload',
|
||
'pwa.update.whyToggle': "Why can't I dismiss this?",
|
||
'pwa.update.whyBody': 'HAPI will not reload your tab automatically while you may have an agent running, a permission waiting, or a message in progress. Running an old web build against the current server can cause sync bugs and failed actions. This banner stays visible until you reload so you are not stuck on a stale version by accident — but you choose when to tap Reload and finish what you are doing first.',
|
||
|
||
// Send blocked
|
||
'send.blocked.title': 'Cannot send message',
|
||
'send.blocked.noConnection': 'Not connected to server',
|
||
'resume.failed.title': 'Resume failed',
|
||
'toast.ready.title': 'Ready for input',
|
||
'toast.ready.body': '{agent} is waiting in {session}',
|
||
'toast.permission.title': 'Permission Request',
|
||
'toast.task.completed': 'Task completed',
|
||
'toast.task.failed': 'Task failed',
|
||
|
||
// Install prompt
|
||
'install.title': 'Install HAPI',
|
||
'install.description': 'Add HAPI to your home screen for quick access.',
|
||
'install.button': 'Install',
|
||
|
||
// Language switcher
|
||
'language.title': 'Language',
|
||
'language.english': 'English',
|
||
'language.chinese': '简体中文',
|
||
|
||
// Settings page
|
||
'common.back': 'Back',
|
||
'settings.title': 'Settings',
|
||
'settings.hub.description': 'Choose a category to adjust HAPI to your workflow.',
|
||
'settings.hub.voice.summary': 'Voice, language, and behavior',
|
||
'settings.general.title': 'General',
|
||
'settings.storage.title': 'Storage',
|
||
'settings.storage.summary': 'Hub database usage',
|
||
'settings.storage.description': 'Current on-disk size of the Hub SQLite database.',
|
||
'settings.storage.loading': 'Loading storage usage…',
|
||
'settings.storage.error': 'Unable to load storage usage',
|
||
'settings.storage.total': 'Total',
|
||
'settings.storage.database': 'Database',
|
||
'settings.storage.wal': 'Write-ahead log',
|
||
'settings.storage.shm': 'Shared memory',
|
||
'settings.storage.path': 'Path',
|
||
'settings.storage.refresh': 'Refresh',
|
||
'settings.storage.refreshing': 'Refreshing…',
|
||
'settings.general.description': 'Language, companion pairing, and general application preferences.',
|
||
'settings.language.title': 'Language',
|
||
'settings.language.label': 'Language',
|
||
'settings.display.title': 'Display',
|
||
'settings.display.description': 'Appearance, typography, and session list preferences.',
|
||
'settings.display.typography': 'Typography',
|
||
'settings.display.sessions': 'Session list',
|
||
'settings.display.appearance': 'Appearance',
|
||
'settings.display.appearanceMode': 'Appearance mode',
|
||
'settings.display.appearance.system': 'Follow System',
|
||
'settings.display.appearance.dark': 'Dark',
|
||
'settings.display.appearance.oled': 'OLED Black',
|
||
'settings.display.appearance.light': 'Light',
|
||
'settings.display.colorTheme': 'Color theme',
|
||
'settings.display.colorTheme.default': 'Default',
|
||
'settings.display.colorTheme.notion': 'Notion',
|
||
'settings.display.colorTheme.one': 'One',
|
||
'settings.display.colorTheme.proof': 'Proof',
|
||
'settings.display.colorTheme.raycast': 'Raycast',
|
||
'settings.display.colorTheme.rosePine': 'Rose Pine',
|
||
'settings.display.colorTheme.solarized': 'Solarized',
|
||
'settings.display.colorTheme.vercel': 'Vercel',
|
||
'settings.display.colorTheme.vsCodePlus': 'VS Code Plus',
|
||
'settings.display.colorTheme.xcode': 'Xcode',
|
||
'settings.display.colorTheme.linear': 'Linear',
|
||
'settings.display.colorTheme.lobster': 'Lobster',
|
||
'settings.display.colorTheme.material': 'Material',
|
||
'settings.display.colorTheme.matrix': 'Matrix',
|
||
'settings.display.colorTheme.monokai': 'Monokai',
|
||
'settings.display.colorTheme.nightOwl': 'Night Owl',
|
||
'settings.display.colorTheme.nord': 'Nord',
|
||
'settings.display.themeColors.title': 'Custom colors',
|
||
'settings.display.themeColors.description': 'Applies to the current appearance. Switch appearance to customize each one separately.',
|
||
'settings.display.themeColors.reset': 'Reset',
|
||
'settings.display.themeColors.resetAll': 'Reset all',
|
||
'settings.display.themeColors.expand': 'Customize',
|
||
'settings.display.themeColors.key.background': 'Background',
|
||
'settings.display.themeColors.key.surface': 'Cards & surfaces',
|
||
'settings.display.themeColors.key.text': 'Text',
|
||
'settings.display.themeColors.key.hint': 'Muted text',
|
||
'settings.display.themeColors.key.accent': 'Accent & links',
|
||
'settings.display.themeColors.key.border': 'Borders',
|
||
'settings.display.themeColors.key.userBubble': 'Your message bubble',
|
||
'settings.display.fontSize': 'Font Size',
|
||
'settings.display.terminalFontSize': 'Terminal Font Size',
|
||
'settings.display.sessionPreviewLimit': 'Sessions Before Folding',
|
||
'settings.display.sessionPreviewLimit.decrease': 'Show fewer sessions before folding',
|
||
'settings.display.sessionPreviewLimit.increase': 'Show more sessions before folding',
|
||
'settings.display.activeSessionsOnly': 'Active sessions only',
|
||
'settings.display.activeSessionsOnly.desc': 'Hide inactive sessions in the sidebar. The session you have open stays visible.',
|
||
'settings.display.sessionListStatus': 'Session list status',
|
||
'settings.display.sessionListStatus.standard': 'Standard',
|
||
'settings.display.sessionListStatus.detailed': 'Detailed',
|
||
'settings.display.sessionListStatus.detailedDescription': 'Shows why a session stopped: permission, input, background work, new activity, or a scheduled message (clock icon).',
|
||
'settings.display.sessionHeader': 'Session header',
|
||
'settings.display.sessionHeader.description': 'Choose which details appear below the session title. Mobile shows the agent plus the highest-priority available detail.',
|
||
'settings.display.sessionHeader.showLabels': 'Show field labels',
|
||
'settings.display.sessionHeader.agent': 'Agent',
|
||
'settings.display.sessionHeader.model': 'Model',
|
||
'settings.display.sessionHeader.reasoning': 'Reasoning effort',
|
||
'settings.display.sessionHeader.fastMode': 'Fast mode',
|
||
'settings.display.sessionHeader.machine': 'Machine',
|
||
'settings.display.sessionHeader.lastActive': 'Active time',
|
||
'settings.display.sessionHeader.createdAt': 'Created time',
|
||
'settings.display.sessionHeader.updatedAt': 'Updated time',
|
||
'settings.display.sessionHeader.worktree': 'Worktree branch',
|
||
'session.header.createdAt': 'Created',
|
||
'session.header.updatedAt': 'Updated',
|
||
'settings.chat.title': 'Chat',
|
||
'settings.chat.description': 'Message input, tool cards, and conversation colors.',
|
||
'settings.chat.input': 'Input',
|
||
'settings.chat.tools': 'Tool cards',
|
||
'settings.chat.colors': 'Conversation colors',
|
||
'settings.chat.enterBehavior': 'Enter Key',
|
||
'settings.chat.enterBehavior.send': 'Send message',
|
||
'settings.chat.enterBehavior.newline': 'Insert newline',
|
||
'settings.chat.terminalToolDisplay': 'Terminal Tool Cards',
|
||
'settings.chat.terminalToolDisplay.compact': 'Compact (command only)',
|
||
'settings.chat.terminalToolDisplay.detailed': 'Detailed (show output preview)',
|
||
'settings.chat.groupedToolBackground': 'Grouped Tool Use Background',
|
||
'settings.chat.userMessageBackground': 'User Message Background',
|
||
'settings.chat.surfaceColor.default': 'Default color',
|
||
'settings.chat.surfaceColor.softBlue': 'Soft blue',
|
||
'settings.chat.surfaceColor.softGreen': 'Soft green',
|
||
'settings.chat.surfaceColor.softYellow': 'Soft yellow',
|
||
'settings.chat.surfaceColor.custom': 'Custom color',
|
||
'settings.chat.composerToolbar.title': 'Customize composer toolbar layout',
|
||
'settings.chat.composerToolbar.description': 'Choose how toolbar actions are arranged below the message input.',
|
||
'settings.chat.composerToolbar.layout': 'Alignment',
|
||
'settings.chat.composerToolbar.order': 'Tool order',
|
||
'settings.chat.composerToolbar.hidden': 'Hidden tools',
|
||
'settings.chat.composerToolbar.emptyHidden': 'Drag tools here to hide them',
|
||
'settings.chat.composerToolbar.hide': 'Hide',
|
||
'settings.chat.composerToolbar.show': 'Show',
|
||
'settings.chat.composerToolbar.layout.left': 'Left',
|
||
'settings.chat.composerToolbar.layout.center': 'Center',
|
||
'settings.chat.composerToolbar.layout.right': 'Right',
|
||
'settings.chat.composerToolbar.layout.split': 'Split',
|
||
'settings.chat.composerToolbar.dragHint': 'Drag actions to reorder them. In split layout, drag between the two groups.',
|
||
'settings.chat.composerToolbar.previewHint': 'Drag the actual toolbar icons below to reorder them, or drag them to Hidden tools. Changes appear immediately; hover an icon to see its action.',
|
||
'settings.chat.composerToolbar.reset': 'Reset',
|
||
'settings.chat.composerToolbar.leftGroup': 'Left group',
|
||
'settings.chat.composerToolbar.rightGroup': 'Right group',
|
||
'settings.chat.composerToolbar.emptyGroup': 'Drop actions here',
|
||
'settings.chat.composerToolbar.moveLeft': 'Move to left group',
|
||
'settings.chat.composerToolbar.moveRight': 'Move to right group',
|
||
'settings.chat.composerToolbar.moveEarlier': 'Move earlier',
|
||
'settings.chat.composerToolbar.moveLater': 'Move later',
|
||
'settings.chat.composerToolbar.item.attachment': 'Add attachment',
|
||
'settings.chat.composerToolbar.item.settings': 'Session settings',
|
||
'settings.chat.composerToolbar.item.piModel': 'Pi model',
|
||
'settings.chat.composerToolbar.item.piThinking': 'Pi thinking level',
|
||
'settings.chat.composerToolbar.item.terminal': 'Terminal',
|
||
'settings.chat.composerToolbar.item.abort': 'Abort',
|
||
'settings.chat.composerToolbar.item.switch': 'Switch to remote',
|
||
'settings.chat.composerToolbar.item.voiceMic': 'Voice microphone',
|
||
'settings.chat.composerToolbar.item.scratchlist': 'Scratchlist',
|
||
'settings.chat.composerToolbar.item.schedule': 'Schedule send',
|
||
'settings.voice.title': 'Voice Assistant',
|
||
'settings.voice.description': 'Voice connection, language, and everyday behavior.',
|
||
'settings.voice.voices.description': 'Choose the voice used by the selected backend.',
|
||
'settings.voice.sounds.title': 'How It Sounds',
|
||
'settings.voice.responds.title': 'How It Responds',
|
||
'settings.voice.connection.title': 'Connection & provider',
|
||
'settings.voice.behaves.title': 'How it behaves',
|
||
'settings.voice.persona.title': 'Persona & instructions',
|
||
'settings.voice.advanced.section.title': 'Advanced',
|
||
'settings.voice.responseLength.label': 'Response length',
|
||
'settings.voice.responseLength.brief': 'Brief',
|
||
'settings.voice.responseLength.balanced': 'Balanced',
|
||
'settings.voice.responseLength.detailed': 'Detailed',
|
||
'settings.voice.responseLength.brief.hint': 'Keep answers to 1–2 sentences.',
|
||
'settings.voice.responseLength.balanced.hint': 'Default — the assistant judges length by context.',
|
||
'settings.voice.responseLength.detailed.hint': 'Give thorough answers when depth is useful.',
|
||
'settings.voice.opening.label': 'Opening',
|
||
'settings.voice.opening.greet': 'Greet me',
|
||
'settings.voice.opening.brief': 'Brief me',
|
||
'settings.voice.opening.greet.hint': 'Assistant says hello and waits for you to speak.',
|
||
'settings.voice.opening.brief.hint': 'Assistant summarises agent activity when you connect.',
|
||
'settings.voice.group.label': 'Voice settings',
|
||
'settings.voice.group.hint': 'Language and voice options apply to the selected backend.',
|
||
'settings.voice.backend': 'Voice backend',
|
||
'settings.voice.language': 'Voice Language',
|
||
'settings.voice.autoDetect': 'Auto-detect',
|
||
'settings.voice.voice': 'Voice',
|
||
'settings.voice.voiceDefault': 'Default',
|
||
'settings.voice.advanced.title': 'Advanced voice settings',
|
||
'settings.voice.advanced.hint': 'Identity, character, fixtures, sliders. Defaults are fine — open only if you want to retune.',
|
||
'settings.voice.advanced.customizedBadge': 'customized',
|
||
'settings.voice.fixtures.title': 'Platform rules (read-only)',
|
||
'settings.voice.fixtures.hint': 'Shipped with the app: tools, routing, TTS rules. Updated when you upgrade HAPI — not editable here.',
|
||
'settings.voice.identity.title': 'Identity & brand',
|
||
'settings.voice.identity.hint': 'Who the voice assistant is. HAPI is the app; rename or rebrand the persona here (e.g. overseer). Provider guardrails are always applied separately.',
|
||
'settings.voice.identity.reset': 'Reset identity',
|
||
'settings.voice.identity.default': 'Bundled default identity',
|
||
'settings.voice.identity.customized': 'Custom identity',
|
||
'settings.voice.character.promptTitle': 'Character & speaking style',
|
||
'settings.voice.character.promptHint': 'Tone and style. Preset delivery text merges here when character is empty. Composed with platform rules at runtime on all voice backends.',
|
||
'settings.voice.character.reset': 'Reset character',
|
||
'settings.voice.character.appendPreset': 'Append preset delivery text',
|
||
'settings.voice.layers.resetAll': 'Reset identity & character',
|
||
'settings.voice.layers.composedNote': 'Runtime prompt = platform rules + your identity + character (all backends).',
|
||
'settings.voice.wireBudget.elevenlabs': 'ElevenLabs sends composed instructions on connect; session history is bootstrapped then streamed.',
|
||
'settings.voice.wireBudget.gemini': 'Gemini Live uses the same composed instructions via the hub proxy.',
|
||
'settings.voice.wireBudget.qwen': 'Qwen Realtime uses the same composed instructions in session.update.',
|
||
'settings.voice.wireBudget.storage': 'Local storage budget (chars):',
|
||
'settings.voice.wireBudget.elevenlabsLimits': 'Wire caps: ~{msg} B per WebRTC message; composed prompt budget ~{prompt} B.',
|
||
'settings.voice.wireBudget.promptTruncated': 'Your composed prompt was shortened to fit the ElevenLabs wire budget. Shorten identity/character or we will trim further on connect.',
|
||
'settings.voice.wireBudget.composedSize': 'Last composed prompt size',
|
||
'settings.voice.contextNotice': 'Last voice session',
|
||
'settings.voice.character.title': 'Speaking style preset',
|
||
'settings.voice.character.presetSlidersHint': 'Presets adjust ElevenLabs TTS sliders and optional delivery wording (character layer).',
|
||
'settings.voice.character.preset.label': 'Preset',
|
||
'settings.voice.character.preset.balanced': 'Balanced',
|
||
'settings.voice.character.preset.balancedHint': 'Warm and focused — good for everyday task work.',
|
||
'settings.voice.character.preset.warm': 'Warm & expressive',
|
||
'settings.voice.character.preset.warmHint': 'More animated delivery; ElevenLabs audio tags when available.',
|
||
'settings.voice.character.preset.calm': 'Calm',
|
||
'settings.voice.character.preset.calmHint': 'Slow, measured tone for long or late sessions.',
|
||
'settings.voice.character.preset.direct': 'Direct',
|
||
'settings.voice.character.preset.directHint': 'Terse and efficient — minimal filler.',
|
||
'settings.voice.character.preset.custom': 'Custom',
|
||
'settings.voice.character.preset.customHint': 'You control sliders and the system prompt above.',
|
||
'settings.voice.tuning.title': 'Acoustic tuning',
|
||
'settings.voice.tuning.elevenlabs': 'ElevenLabs',
|
||
'settings.voice.tuning.gemini': 'Gemini Live',
|
||
'settings.voice.tuning.stability': 'Stability',
|
||
'settings.voice.tuning.stabilityHint': 'Lower = more dynamic; higher = steadier.',
|
||
'settings.voice.tuning.expressiveness': 'Expressiveness',
|
||
'settings.voice.tuning.expressivenessHint': 'Style exaggeration (ElevenLabs style).',
|
||
'settings.voice.tuning.speakingRate': 'Speaking rate',
|
||
'settings.voice.tuning.speakingRateHint': '0.7–1.2× (ElevenLabs API).',
|
||
'settings.voice.tuning.similarity': 'Similarity boost',
|
||
'settings.voice.tuning.similarityHint': 'Closer match to the trained voice.',
|
||
'settings.voice.tuning.speakerBoost': 'Speaker boost',
|
||
'settings.voice.tuning.affectiveDialog': 'Affective dialog',
|
||
'settings.voice.tuning.affectiveDialogHint': 'Adapt delivery to your tone (Gemini v1alpha).',
|
||
'settings.voice.tuning.qwenHint': 'Qwen has no TTS sliders here — edit the voice system prompt above; emotion adapts from your speech.',
|
||
'settings.voice.tuning.selectBackend': 'Per-backend sliders appear when a voice backend is selected.',
|
||
'settings.voice.preview.elevenlabsOnly': 'Voice preview is only available for ElevenLabs',
|
||
'settings.voice.preview.unavailable': 'Preview unavailable without an ElevenLabs API key',
|
||
'settings.voice.preview.play': 'Preview voice',
|
||
'settings.voice.preview.stop': 'Stop preview',
|
||
'settings.voice.staticCatalogHint': 'Open the list to see voice character notes. Audio preview is ElevenLabs only.',
|
||
'settings.voice.session.label': 'Session behavior',
|
||
'settings.voice.proactive': 'Start voice session with summary',
|
||
'settings.voice.proactive.description': 'When on, starting a voice session opens with a spoken summary of current agent activity. When off, the assistant greets you and waits for you to speak.',
|
||
'settings.companion.title': 'Companion',
|
||
'settings.companion.noToken': 'Pairing requires the original access token (CLI_API_TOKEN). It looks like you signed in via Telegram or another flow that did not store one — paste the token manually in the companion app instead.',
|
||
'settings.companion.description': 'Scan from the HAPI companion app on Android (phone or Wear OS) to bind it to this hub. The pairing code is your access token — treat it like a password.',
|
||
'settings.companion.showQr': 'Show pairing QR',
|
||
'settings.companion.qrAriaLabel': 'Companion pairing QR code',
|
||
'settings.companion.copyLink': 'Copy link',
|
||
'settings.companion.copied': 'Copied!',
|
||
'settings.companion.hide': 'Hide',
|
||
'settings.machines.title': 'Machines',
|
||
'settings.machines.description': 'Give your machines names of your own. Only machines that are currently online are listed.',
|
||
'settings.machines.section': 'Your machines',
|
||
'settings.machines.rename': 'Rename {name}',
|
||
'settings.machines.namePlaceholder': 'Use hostname',
|
||
'settings.machines.empty': 'No machines online.',
|
||
'settings.machines.error': 'Could not rename this machine.',
|
||
'settings.hub.machines.summary': 'Custom names for your machines',
|
||
'settings.about.title': 'About',
|
||
'settings.about.description': 'HAPI links and version information.',
|
||
'settings.about.website': 'Website',
|
||
'settings.about.appVersion': 'App Version',
|
||
'settings.about.protocolVersion': 'Protocol Version',
|
||
|
||
// Browse / Workspace
|
||
'browse.title': 'Browse',
|
||
'browse.goUp': 'Go up',
|
||
'browse.empty': 'No subdirectories found',
|
||
'browse.refresh': 'Refresh',
|
||
'browse.startSession': 'Start Session',
|
||
'browse.nav': 'Browse',
|
||
'browse.noRootTitle': 'Workspace browsing is off',
|
||
'browse.noRootHint': 'Browsing is opt-in. Restart the runner with one or more workspace roots to enable file-tree navigation and scoped session spawning.',
|
||
'browse.noRootFooter': 'You can still create sessions from the “New Session” page.',
|
||
'browse.noMachinesConnected': 'No CLI connected. Run `hapi runner start --workspace-root /path/a --workspace-root /path/b` on a machine to get started.',
|
||
|
||
// Misc
|
||
'misc.noMachines': 'No machines available',
|
||
'misc.machine': 'Machine',
|
||
'misc.collaborationMode': 'Collaboration Mode',
|
||
'misc.permissionMode': 'Permission Mode',
|
||
'misc.model': 'Model',
|
||
'misc.reasoningEffort': 'Reasoning Effort',
|
||
'misc.effort': 'Effort',
|
||
'misc.fastMode': 'Fast Mode',
|
||
'misc.fastModeStandard': 'Standard',
|
||
'misc.fastModeFast': 'Fast',
|
||
'misc.variant': 'Variant',
|
||
'misc.loading': 'Loading…',
|
||
'misc.newMessage': '{n} new message{s}',
|
||
'misc.loadingMessages': 'Loading messages…',
|
||
'misc.pullToLoadOlder': 'Keep pulling to load earlier messages',
|
||
'misc.releaseToLoadOlder': 'Release to load earlier messages',
|
||
'misc.typeMessage': "Type 'continue' to resume...",
|
||
'misc.typeAMessage': 'Type a message...',
|
||
'misc.offline': 'offline',
|
||
'misc.permissionRequired': 'permission required',
|
||
'misc.online': 'online',
|
||
'misc.contextDetails': 'Context details',
|
||
'misc.contextCache': 'Cache: {value}',
|
||
'misc.contextUsed': 'Used: {value} ({percent}%)',
|
||
'misc.contextUsedTokens': 'Used: {value}',
|
||
'misc.contextRemaining': 'Remaining: {value} ({percent}%)',
|
||
|
||
// Web Share Target picker
|
||
'share.title': 'Share to HAPI',
|
||
'share.subtitle': 'Pick a session to attach this to.',
|
||
'share.recentSessions': 'Recent active sessions',
|
||
'share.newSession': 'New session',
|
||
'share.discard': 'Discard',
|
||
'share.loading': 'Loading shared content…',
|
||
'share.notFound.title': 'Shared content not found',
|
||
'share.notFound.body': 'This share link expired or was opened directly without a transfer.',
|
||
'share.error.ingest': "We couldn't read the shared content. Try again from the source app.",
|
||
'share.error.noId': 'No share id was provided. Open this page from the system share sheet.',
|
||
'share.backToSessions': 'Back to sessions',
|
||
'share.preview.text': 'Shared text',
|
||
'share.preview.empty': '(empty share)',
|
||
'share.preview.files': '{n} files',
|
||
'share.noActiveSessions': 'No active sessions. Pick "New session" below.',
|
||
'sessions.refresh.pull': 'Pull to refresh',
|
||
'sessions.refresh.release': 'Release to refresh',
|
||
'sessions.refresh.refreshing': 'Refreshing…',
|
||
'sessions.refresh.failed.title': 'Refresh failed',
|
||
'codexSync.newSessionInline.title': 'Import Codex history',
|
||
'codexSync.newSessionInline.description': 'Optional: choose one local Codex session, then pick model/settings and Create to import.',
|
||
'codexSync.newSessionInline.clear': 'Clear selected history',
|
||
'codexSync.newSessionInline.choose': 'Choose history',
|
||
'codexSync.confirm.search': 'Search',
|
||
'codexSync.confirm.searchPlaceholder': 'Search title, message, directory, session id',
|
||
'codexSync.confirm.useSelected': 'Use selected',
|
||
'codexSync.confirm.imported': 'Imported',
|
||
'codexSync.confirm.original': 'Original',
|
||
'codexSync.confirm.fork': 'Fork',
|
||
'codexSync.confirm.archiveAction': 'Archive in Codex',
|
||
'session.action.syncCodexDisabledActive': 'Stop or archive the running session before syncing Codex history.',
|
||
'codexSync.manual.success.noNewMessages': 'No new Codex messages.',
|
||
'session.status.title': 'Session status',
|
||
'session.status.goal': 'Goal',
|
||
'session.status.tasks': 'Tasks',
|
||
'session.status.subagents': 'Subagents',
|
||
'session.status.terminals': 'Background terminals',
|
||
'session.status.terminalsUnavailable': '{count} running · exact command unavailable',
|
||
'session.status.terminalsPossible': 'Possible commands',
|
||
'session.status.goal.active': 'Active',
|
||
'session.status.goal.paused': 'Paused',
|
||
'session.status.goal.budgetLimited': 'Limited',
|
||
'session.status.goal.complete': 'Complete',
|
||
'session.status.subagent.running': 'Running',
|
||
'session.status.subagent.waiting': 'Waiting',
|
||
'session.status.subagent.error': 'Error',
|
||
} as const
|