/** * Tiny i18n layer. Keeps a flat key → string map per locale and exposes a * `useT()` hook that reads the active locale from the zustand store. Adding * a new locale = add a new dictionary at the bottom of this file and * register it in `DICTS`. The `Dict` interface keeps the build honest: * if you forget a key the TS check breaks rather than the UI silently * falling back to English. */ "use client"; import { useStore, type Locale } from "@/lib/store"; export interface Dict { // Brand "brand.subtitle": string; // Toolbar "toolbar.selectAgent": string; "toolbar.switchAgent": string; "toolbar.settings": string; "toolbar.stop": string; "toolbar.convert": string; "toolbar.firstSelectAgent": string; "toolbar.unsupportedProtocol": string; "toolbar.enterContent": string; "toolbar.shortcutHint": string; "convertChip.label": string; "convertChip.tooltip": string; "aiPrompt.placeholder": string; "aiPrompt.submit": string; "aiPrompt.stop": string; "aiPrompt.needAgent": string; "aiPrompt.hint": string; // Layout mode toggle "layout.aria.group": string; "layout.label.editor": string; "layout.label.split": string; "layout.label.preview": string; "layout.tip.editor": string; "layout.tip.split": string; "layout.tip.preview": string; // Welcome modal "welcome.eyebrow": string; "welcome.titlePart1": string; "welcome.titleAccent": string; "welcome.description": string; "welcome.rescan": string; "welcome.scanning": string; "welcome.rescanTitle": string; "welcome.detectionFailed": string; "welcome.installed": string; "welcome.notInstalled": string; "welcome.noAgentsTitle": string; "welcome.noAgentsBody": string; "welcome.later": string; "welcome.enter": string; "welcome.current": string; "welcome.unsupportedHint": string; "welcome.pickInstalled": string; "welcome.enterTooltip.noAgent": string; "welcome.enterTooltip.unsupported": string; "welcome.enterTooltip.ok": string; // Model picker "model.eyebrow": string; "model.label": string; "model.defaultHint.prefix": string; "model.defaultHint.suffix": string; "model.defaultLabel": string; // Agent card "agent.selected": string; "agent.notInstalled": string; "agent.customBin.eyebrow": string; "agent.customBin.subtitle": string; "agent.customBin.detected": string; "agent.customBin.hint": string; "agent.customBin.save": string; "agent.customBin.clear": string; "protocol.stdin": string; "protocol.argv": string; "protocol.argvMessage": string; "protocol.acp": string; "protocol.piRpc": string; // Settings modal "settings.eyebrow": string; "settings.titlePart1": string; "settings.titleAccent": string; "settings.close": string; "settings.done": string; "settings.section.agent.label": string; "settings.section.agent.hint": string; "settings.section.language.label": string; "settings.section.language.hint": string; "settings.agent.title": string; "settings.agent.subtitle": string; "settings.language.title": string; "settings.language.subtitle": string; "settings.language.active": string; "settings.language.default": string; "settings.language.note": string; "settings.section.deploy.label": string; "settings.section.deploy.hint": string; "settings.deploy.title": string; "settings.deploy.subtitle": string; "settings.deploy.vercel.title": string; "settings.deploy.vercel.tokenLabel": string; "settings.deploy.vercel.tokenPlaceholder": string; "settings.deploy.vercel.tokenHint": string; "settings.deploy.vercel.teamSlugLabel": string; "settings.deploy.vercel.teamSlugPlaceholder": string; "settings.deploy.save": string; "settings.deploy.clear": string; "settings.deploy.configured": string; "deploy.button": string; "deploy.button.disabled": string; "deploy.deploying": string; "deploy.success.label": string; "deploy.success.copy": string; "deploy.success.copied": string; "deploy.success.dismiss": string; "deploy.success.open": string; "deploy.protected.label": string; "deploy.protected.hint": string; "deploy.delayed.label": string; "deploy.delayed.hint": string; "deploy.error.label": string; "deploy.error.tokenMissing": string; "deploy.history.title": string; "deploy.history.delete": string; "deploy.provider.vercel": string; "deploy.provider.cloudflarePages": string; "deploy.provider.cloudflarePages.comingSoon": string; "deploy.menu.deployTo": string; // Marketplace "settings.section.marketplace.label": string; "settings.section.marketplace.hint": string; "settings.marketplace.title": string; "settings.marketplace.subtitle": string; "marketplace.installFromGithub": string; "marketplace.placeholder": string; "marketplace.install": string; "marketplace.installing": string; "marketplace.uninstall": string; "marketplace.hint": string; "marketplace.installed": string; "marketplace.installSucceeded": string; "marketplace.uninstalled": string; "marketplace.skillCount": string; "marketplace.empty.title": string; "marketplace.empty.body": string; // Editor pane "editor.tab.text": string; "editor.tab.samples": string; "editor.tab.formats": string; "editor.attach": string; "editor.attachTooltip": string; "editor.dropTitle": string; "editor.dropHint": string; "editor.backup": string; "editor.backupTooltip": string; "editor.restoring": string; "editor.placeholder": string; "editor.chars": string; "editor.saving": string; "editor.saveFailed": string; "editor.autosaved": string; "editor.autosaveTooltip": string; // Preview pane "preview.tab.preview": string; "preview.tab.deck": string; "preview.tab.code": string; "preview.tab.log": string; // Deck viewer "deck.empty": string; "deck.prev": string; "deck.next": string; "deck.present": string; "deck.exitPresent": string; "deck.presentTooltip": string; "deck.notes": string; "deck.notesTooltip": string; "deck.slideN": string; "preview.status.idle": string; "preview.status.running": string; "preview.status.done": string; "preview.status.error": string; "preview.code.waiting": string; "preview.code.empty": string; "preview.code.editHint": string; "preview.code.lockedHint": string; "preview.placeholder.runningTitle.part1": string; "preview.placeholder.runningTitle.accent": string; "preview.placeholder.idleTitle.part1": string; "preview.placeholder.idleTitle.accent": string; "preview.placeholder.idleTitle.part2": string; "preview.placeholder.runningDescr": string; "preview.placeholder.idleDescr": string; "preview.placeholder.chip.article": string; "preview.placeholder.chip.deck": string; "preview.placeholder.chip.resume": string; "preview.placeholder.chip.poster": string; "preview.placeholder.chip.xiaohongshu": string; "preview.placeholder.chip.twitterCard": string; "preview.placeholder.chip.webProto": string; "preview.placeholder.chip.dataReport": string; "preview.log.empty": string; "preview.metric.ttfbHint": string; "preview.present": string; "preview.exitPresent": string; "preview.presentTooltip": string; "preview.refresh": string; "preview.refreshTooltip": string; // Tasks sidebar "tasks.heading": string; "tasks.expand": string; "tasks.collapse": string; "tasks.newTask": string; "tasks.search.placeholder": string; "tasks.search.clear": string; "tasks.empty.intro": string; "tasks.empty.clear": string; "tasks.footer": string; "tasks.status.idle": string; "tasks.status.running": string; "tasks.status.done": string; "tasks.status.error": string; "tasks.rename": string; "tasks.duplicate": string; "tasks.delete": string; "tasks.deleteConfirm": string; "tasks.renameDblHint": string; "tasks.emptyContent": string; "tasks.defaultName": string; "tasks.matchTooltip": string; // History pane "history.toggle": string; "history.heading": string; "history.close": string; "history.versionCap": string; "history.current": string; "history.compare": string; "history.restore": string; "history.delete": string; "history.loading": string; "history.empty.noTask": string; "history.empty.noVersions": string; "history.restoreConfirm": string; "history.deleteConfirm": string; "history.backToList": string; "history.diff.leftLabel": string; "history.diff.rightLabel": string; "history.diff.showSource": string; "history.diff.pickPair": string; "history.diff.before": string; "history.diff.after": string; // Community / upstream link entry points "community.starOnGitHub": string; "community.joinDiscord": string; "community.upstreamHint": string; "community.builtBy": string; // Export menu "export.button": string; "export.section.platform": string; "export.section.raw": string; "export.section.download": string; "export.section.deck": string; "export.section.hyperframes": string; "export.action.wechat": string; "export.action.zhihu": string; "export.action.twitterImg": string; "export.action.html": string; "export.action.text": string; "export.action.downloadHtml": string; "export.action.downloadPng": string; "export.action.deckPdf": string; "export.action.deckPngZip": string; "export.action.deckPptx": string; "export.action.remotionZip": string; "export.toast.wechat": string; "export.toast.zhihu": string; "export.toast.image": string; "export.toast.html": string; "export.toast.text": string; "export.toast.htmlSaved": string; "export.toast.imgSaved": string; "export.toast.deckPdf": string; "export.toast.deckPngZip": string; "export.toast.deckPptx": string; "export.toast.remotionZip": string; "export.error.previewNotReady": string; "export.error.generic": string; // Drafts menu "drafts.button": string; "drafts.tooltip": string; "drafts.heading": string; "drafts.count": string; "drafts.empty.title": string; "drafts.empty.hint": string; "drafts.restoreConfirm": string; "drafts.restoredLog": string; "drafts.delete": string; "drafts.chars": string; "drafts.emptyPreview": string; // Upload dropzone "upload.loadedLog": string; "upload.failedLog": string; // Formats gallery "formats.eyebrow": string; "formats.subtitle": string; "formats.loadButton": string; "formats.loadedLog": string; // Samples gallery "samples.eyebrow": string; "samples.subtitle.preRendered": string; "samples.subtitle.diff": string; "samples.subtitle.body": string; "samples.filter.all": string; "samples.loading": string; "samples.loaded": string; "samples.loadingButton": string; "samples.loadButton": string; "samples.loadedTooltip": string; "samples.sourceLink": string; // Template picker "template.loading": string; "template.heading": string; "template.search.placeholder": string; "template.search.clear": string; "template.filter.all": string; "template.filter.featured": string; "template.featured.symbol": string; "template.empty.loading": string; "template.empty.noMatch": string; "template.footer.hint.search": string; "template.footer.hint.filter": string; "template.footer.hint.hover": string; "template.footer.hint.preview": string; "template.footer.esc": string; "template.preview.button": string; "template.preview.loadingButton": string; "template.preview.tooltip": string; "template.popover.loading": string; "template.popover.loadInto": string; "template.popover.loadIntoTooltip": string; "template.source.tooltip": string; "template.scenario.marketing": string; "template.scenario.engineering": string; "template.scenario.operations": string; "template.scenario.product": string; "template.scenario.design": string; "template.scenario.finance": string; "template.scenario.sales": string; "template.scenario.hr": string; "template.scenario.personal": string; "template.scenario.education": string; "template.scenario.creator": string; "template.scenario.video": string; } const en: Dict = { "brand.subtitle": "the agentic HTML editor", "toolbar.selectAgent": "Select agent", "toolbar.switchAgent": "Switch agent", "toolbar.settings": "Settings", "toolbar.stop": "◼ Stop", "toolbar.convert": "⚡ Convert to HTML", "toolbar.firstSelectAgent": "Pick an agent first", "toolbar.unsupportedProtocol": "ACP / pi-rpc not wired yet — pick another agent", "toolbar.enterContent": "Type or upload content", "toolbar.shortcutHint": "⌘+Enter to convert", "convertChip.label": "Generate HTML", "convertChip.tooltip": "Generate HTML from the markdown on the left", "aiPrompt.placeholder": "Ask AI to write markdown — \"draft a tweet about X\", \"summarize the above\"…", "aiPrompt.submit": "✨ Draft", "aiPrompt.stop": "◼ Stop", "aiPrompt.needAgent": "Pick an agent first", "aiPrompt.hint": "Streams below your existing content. ⌘+Enter to send.", "layout.aria.group": "Workspace layout", "layout.label.editor": "Editor only", "layout.label.split": "Split (editor + preview)", "layout.label.preview": "Preview only", "layout.tip.editor": "Editor only — hide preview", "layout.tip.split": "Split — editor + preview side by side", "layout.tip.preview": "Preview only — hide editor", "welcome.eyebrow": "Choose your agent", "welcome.titlePart1": "Pick a local", "welcome.titleAccent": "code agent", "welcome.description": "HTML Anything reuses your already-logged-in CLI session — no API key required. You can switch agents anytime from the top bar.", "welcome.rescan": "↻ Rescan", "welcome.scanning": "Scanning…", "welcome.rescanTitle": "Run detection again", "welcome.detectionFailed": "Detection failed", "welcome.installed": "Installed ({n})", "welcome.notInstalled": "Not installed ({n})", "welcome.noAgentsTitle": "No local agents detected", "welcome.noAgentsBody": "Install one of the CLIs above and log in, then click ↻ Rescan.", "welcome.later": "Later", "welcome.enter": "Enter editor →", "welcome.current": "Active", "welcome.unsupportedHint": "Protocol not wired — cannot convert", "welcome.pickInstalled": "Pick an installed agent first", "welcome.enterTooltip.noAgent": "Pick an installed agent first", "welcome.enterTooltip.unsupported": "ACP / pi-rpc not wired yet — pick another agent", "welcome.enterTooltip.ok": "Open the editor", "model.eyebrow": "model", "model.label": "Pick a model for {agent}", "model.defaultHint.prefix": "Default omits ", "model.defaultHint.suffix": " and lets the CLI pick.", "model.defaultLabel": "Default (CLI config)", "agent.selected": "Selected", "agent.notInstalled": "Not installed", "agent.customBin.eyebrow": "custom path", "agent.customBin.subtitle": "Override the auto-detected binary for {agent}", "agent.customBin.detected": "Auto-detected:", "agent.customBin.hint": "Set this if auto-detection picked the wrong binary (e.g. Scoop on Windows, custom installs). Leave blank to use the detected path.", "agent.customBin.save": "Save", "agent.customBin.clear": "Clear", "protocol.stdin": "stdin · stream", "protocol.argv": "positional argv", "protocol.argvMessage": "argv · batch JSON", "protocol.acp": "ACP JSON-RPC · not wired", "protocol.piRpc": "pi-rpc · not wired", "settings.eyebrow": "Settings", "settings.titlePart1": "Configure", "settings.titleAccent": "HTML Anything", "settings.close": "Close (Esc)", "settings.done": "Done", "settings.section.agent.label": "Agent", "settings.section.agent.hint": "Local CLI · model", "settings.section.language.label": "Language", "settings.section.language.hint": "Interface language", "settings.agent.title": "Code agent", "settings.agent.subtitle": "HTML Anything reuses your already-logged-in CLI session — no API key required.", "settings.language.title": "Interface language", "settings.language.subtitle": "Sets the language the app surface uses. Default is English; your choice is saved locally.", "settings.language.active": "Active", "settings.language.default": "Default", "settings.language.note": "We ship English and Simplified Chinese today. Adding a locale means dropping a dictionary into src/lib/i18n.ts — contributors welcome.", "settings.section.deploy.label": "Deploy", "settings.section.deploy.hint": "One-click publishing", "settings.deploy.title": "One-click deploy", "settings.deploy.subtitle": "Publish the generated HTML to a public URL with one click. Tokens stay on your machine in ~/.html-anything (chmod 600).", "settings.deploy.vercel.title": "Vercel", "settings.deploy.vercel.tokenLabel": "API token", "settings.deploy.vercel.tokenPlaceholder": "vercel_xxx…", "settings.deploy.vercel.tokenHint": "Create a token at vercel.com/account/tokens. \"Full Account\" scope is enough.", "settings.deploy.vercel.teamSlugLabel": "Team slug (optional)", "settings.deploy.vercel.teamSlugPlaceholder": "my-team", "settings.deploy.save": "Save", "settings.deploy.clear": "Clear", "settings.deploy.configured": "Configured", "deploy.button": "Publish", "deploy.button.disabled": "Run Convert first", "deploy.deploying": "Deploying…", "deploy.success.label": "Live at", "deploy.success.copy": "Copy", "deploy.success.copied": "Copied", "deploy.success.dismiss": "Dismiss", "deploy.success.open": "Open", "deploy.protected.label": "Deployed (SSO-protected)", "deploy.protected.hint": "Vercel is protecting this preview behind SSO. Open it once to authenticate, or disable Deployment Protection in Vercel project settings.", "deploy.delayed.label": "Deployed (waiting for CDN)", "deploy.delayed.hint": "URL is live but CDN hasn't finished propagating yet. Try again in 30–60 s.", "deploy.error.label": "Deploy failed", "deploy.error.tokenMissing": "No deploy token. Open Settings → Deploy to add one.", "deploy.history.title": "Past deployments", "deploy.history.delete": "Forget", "deploy.provider.vercel": "Vercel", "deploy.provider.cloudflarePages": "Cloudflare Pages", "deploy.provider.cloudflarePages.comingSoon": "Coming soon", "deploy.menu.deployTo": "Deploy to…", "settings.section.marketplace.label": "Marketplace", "settings.section.marketplace.hint": "Install skills from GitHub", "settings.marketplace.title": "Skill marketplace", "settings.marketplace.subtitle": "Install community skill packs from public GitHub repos. Each pack ships one or more SKILL.md prompts that show up in the template picker.", "marketplace.installFromGithub": "Install from GitHub", "marketplace.placeholder": "owner/repo or owner/repo#branch", "marketplace.install": "Install", "marketplace.installing": "Installing…", "marketplace.uninstall": "Uninstall", "marketplace.hint": "Public GitHub repos only. Needs a SKILL.md at the repo root, or skills//SKILL.md for multi-skill packs.", "marketplace.installed": "Installed ({n})", "marketplace.installSucceeded": "Installed {n} skill(s) from {repo}.", "marketplace.uninstalled": "Package uninstalled.", "marketplace.skillCount": "{n} skill(s)", "marketplace.empty.title": "No packages installed yet", "marketplace.empty.body": "Paste a GitHub repo above to install a community skill pack.", "editor.tab.text": "✏️ Text", "editor.tab.samples": "✨ Samples", "editor.tab.formats": "📋 Formats", "editor.attach": "Attach", "editor.attachTooltip": "Drop a file or click to attach — appended to the editor below", "editor.dropTitle": "Drop to attach", "editor.dropHint": ".md .txt .pdf .csv .tsv .xlsx .json .sql .yaml .png .jpg — appended to your editor", "editor.backup": "⤓ Backup", "editor.backupTooltip": "Download the current content as a .md / .txt backup", "editor.restoring": "Restoring last content…", "editor.placeholder": "Paste anything here — Markdown / CSV / Excel paste / JSON / SQL / your draft…\n\n⌘+Enter to convert\n\nEverything is autosaved to localStorage and survives a reload; the ↶ history menu top-right shows the last 8 snapshots.", "editor.chars": "{n} chars", "editor.saving": "Saving…", "editor.saveFailed": "✗ Save failed", "editor.autosaved": "Autosaved", "editor.autosaveTooltip": "Content lives in browser localStorage and survives a reload", "preview.tab.preview": "👁 Preview", "preview.tab.deck": "🎬 Deck", "preview.tab.code": " Source", "preview.tab.log": "📋 Log", "preview.status.idle": "idle", "preview.status.running": "generating", "preview.status.done": "done", "preview.status.error": "error", "preview.code.waiting": "// waiting for the agent's first byte…", "preview.code.empty": "// no content yet", "preview.code.editHint": "Editable — changes save instantly. Switch to Preview (or hit Refresh) to see them.", "preview.code.lockedHint": "Read-only while the agent is streaming.", "preview.placeholder.runningTitle.part1": "agent is", "preview.placeholder.runningTitle.accent": "thinking", "preview.placeholder.idleTitle.part1": "Paste content on the left, then", "preview.placeholder.idleTitle.accent": "⚡", "preview.placeholder.idleTitle.part2": "convert", "preview.placeholder.runningDescr": "The agent is generating. Some CLIs, including OpenCode, return larger chunks instead of token streams. Waited {sec}s.", "preview.placeholder.idleDescr": "Preview · view source · one-click copy for WeChat / Twitter / Zhihu · PNG export.", "preview.placeholder.chip.article": "📖 Article", "preview.placeholder.chip.deck": "🎬 Slide deck", "preview.placeholder.chip.resume": "📄 Resume", "preview.placeholder.chip.poster": "🖼️ Poster", "preview.placeholder.chip.xiaohongshu": "📱 Xiaohongshu", "preview.placeholder.chip.twitterCard": "🐦 Twitter card", "preview.placeholder.chip.webProto": "🛠️ Web prototype", "preview.placeholder.chip.dataReport": "📊 Data report", "preview.log.empty": "Live agent events (start / delta / meta / stderr / done) and timings show up here.", "preview.metric.ttfbHint": "Time to first byte", "preview.present": "⛶ Present", "preview.exitPresent": "Exit", "preview.presentTooltip": "Fullscreen preview — F to toggle, ESC to exit", "preview.refresh": "Refresh", "preview.refreshTooltip": "Re-render the preview iframe with the current HTML", "deck.empty": "No slides found in this output.", "deck.prev": "Previous slide", "deck.next": "Next slide", "deck.present": "▶ Present", "deck.exitPresent": "Exit present", "deck.presentTooltip": "Fullscreen — press F to toggle, ESC to exit", "deck.notes": "Notes", "deck.notesTooltip": "Toggle speaker notes (N)", "deck.slideN": "{n} / {m}", "tasks.heading": "tasks", "tasks.expand": "Expand task list", "tasks.collapse": "Collapse task list", "tasks.newTask": "+ New task", "tasks.search.placeholder": "Search task name / content / template…", "tasks.search.clear": "Clear search", "tasks.empty.intro": "No tasks match “{query}”", "tasks.empty.clear": "Clear search", "tasks.footer": "Input and output of every task autosave to your browser and survive a reload.", "tasks.status.idle": "idle", "tasks.status.running": "generating", "tasks.status.done": "done", "tasks.status.error": "error", "tasks.rename": "Rename", "tasks.duplicate": "Duplicate", "tasks.delete": "Delete task", "tasks.deleteConfirm": "Delete task “{name}”? Its input and output will be lost.", "tasks.renameDblHint": "Double-click to rename", "tasks.emptyContent": "empty", "tasks.defaultName": "Task {n}", "tasks.matchTooltip": "{a} / {b} match", "history.toggle": "Version history", "history.heading": "history", "history.close": "Close history", "history.versionCap": "Up to {n} versions per task", "history.current": "current", "history.compare": "Compare", "history.restore": "Restore", "history.delete": "Delete version", "history.loading": "Loading versions…", "history.empty.noTask": "No active task.", "history.empty.noVersions": "No versions yet. Run Convert to start the history.", "history.restoreConfirm": "Restore version v{v}? The current preview will be replaced.", "history.deleteConfirm": "Delete version v{v}? This cannot be undone.", "history.backToList": "Back", "history.diff.leftLabel": "Before", "history.diff.rightLabel": "After", "history.diff.showSource": "Show DOM diff", "history.diff.pickPair": "Pick two versions to compare.", "history.diff.before": "Before", "history.diff.after": "After", "community.starOnGitHub": "Star on GitHub", "community.joinDiscord": "Join Discord", "community.upstreamHint": "The upstream design system this is built on", "community.builtBy": "Built by", "export.button": "⤓ Export / Copy ▾", "export.section.platform": "Copy to platform", "export.section.raw": "Copy raw", "export.section.download": "Download", "export.section.deck": "Deck · {n} slides", "export.section.hyperframes": "Hyperframes · {n} frames", "export.action.wechat": "WeChat (公众号)", "export.action.zhihu": "Zhihu", "export.action.twitterImg": "Twitter / Weibo (PNG)", "export.action.html": "HTML source", "export.action.text": "Plain text", "export.action.downloadHtml": ".html single file", "export.action.downloadPng": ".png hi-res image", "export.action.deckPdf": "PDF · all slides (print)", "export.action.deckPngZip": "PNG · per-slide (.zip)", "export.action.deckPptx": ".pptx · PowerPoint", "export.action.remotionZip": "Remotion project (.zip)", "export.toast.wechat": "WeChat format copied", "export.toast.zhihu": "Zhihu format copied", "export.toast.image": "Image copied", "export.toast.html": "HTML copied", "export.toast.text": "Text copied", "export.toast.htmlSaved": "HTML downloaded", "export.toast.imgSaved": "Image downloaded", "export.toast.deckPdf": "Print dialog opened — pick “Save as PDF”", "export.toast.deckPngZip": "Slide PNGs zipped", "export.toast.deckPptx": "PPTX downloaded", "export.toast.remotionZip": "Remotion project zipped — unzip & run `npx remotion render`", "export.error.previewNotReady": "Preview not ready", "export.error.generic": "failed", "drafts.button": "↶ History", "drafts.tooltip": "View autosaved version history", "drafts.heading": "Autosaved versions", "drafts.count": "{n}", "drafts.empty.title": "No history yet", "drafts.empty.hint": "Inputs over 20 chars get snapshotted every 30s", "drafts.restoreConfirm": "Restore the version from {when}? The current content will be replaced (the new version is autosaved too).", "drafts.restoredLog": "Restored the version from {when} ({n} chars)", "drafts.delete": "Delete this version", "drafts.chars": "{n} chars", "drafts.emptyPreview": "(empty)", "upload.loadedLog": "Loaded {name} ({fmt})", "upload.failedLog": "Parse failed: {err}", "formats.eyebrow": "Format examples", "formats.subtitle": "One-click snippets for every input shape we support. Pick a template up top, click Load, then ⌘+Enter — the template above decides the look.", "formats.loadButton": "✨ Load →", "formats.loadedLog": "Loaded {name} ({fmt})", "samples.eyebrow": "Sample gallery", "samples.subtitle.preRendered": "pre-rendered", "samples.subtitle.diff": "diff edit", "samples.subtitle.body": "Every sample ships {preRendered} world-class HTML — click to preview in a new task; edit the content and ⌘+Enter, the agent runs a {diff} only and saves tokens.", "samples.filter.all": "All", "samples.loading": "Loading samples…", "samples.loaded": "✓ Loaded", "samples.loadingButton": "Loading…", "samples.loadButton": "✨ Load & preview →", "samples.loadedTooltip": "This sample is already loaded in a task", "samples.sourceLink": "Source · {label}", "template.loading": "Loading templates…", "template.heading": "Pick an output shape", "template.search.placeholder": "Search template name / description / tag…", "template.search.clear": "Clear", "template.filter.all": "All", "template.filter.featured": "★ Featured", "template.featured.symbol": "★", "template.empty.loading": "Loading templates…", "template.empty.noMatch": "No matching templates. Try a different keyword or category.", "template.footer.hint.search": "⌕ Search", "template.footer.hint.filter": "category filter", "template.footer.hint.hover": "hover for thumbnail", "template.footer.hint.preview": "“Preview” loads into the editor", "template.footer.esc": "to close", "template.preview.button": "Preview", "template.preview.loadingButton": "…", "template.preview.tooltip": "Load the prerendered snapshot into a new task", "template.popover.loading": "Loading preview…", "template.popover.loadInto": "Load into editor →", "template.popover.loadIntoTooltip": "Load the prerendered snapshot into a new task", "template.source.tooltip": "Reference project: {label}", "template.scenario.marketing": "Marketing / Content", "template.scenario.engineering": "Engineering / Dev", "template.scenario.operations": "Operations", "template.scenario.product": "Product", "template.scenario.design": "Design / Explore", "template.scenario.finance": "Finance / Data", "template.scenario.sales": "Sales", "template.scenario.hr": "HR / Onboarding", "template.scenario.personal": "Personal", "template.scenario.education": "Education", "template.scenario.creator": "Creator", "template.scenario.video": "Video", }; const zhCN: Dict = { "brand.subtitle": "the agentic HTML editor", "toolbar.selectAgent": "选择 agent", "toolbar.switchAgent": "切换 agent", "toolbar.settings": "设置", "toolbar.stop": "◼ 停止", "toolbar.convert": "⚡ 转换为 HTML", "convertChip.label": "生成 HTML", "convertChip.tooltip": "把左侧 markdown 内容转成 HTML", "aiPrompt.placeholder": "让 AI 帮你写 markdown — 「写一条关于 X 的推文」「总结上面这段」…", "aiPrompt.submit": "✨ 生成", "aiPrompt.stop": "◼ 停止", "aiPrompt.needAgent": "先在右上角选个 agent", "aiPrompt.hint": "结果会追加到上方现有内容下方。⌘+Enter 发送。", "toolbar.firstSelectAgent": "先选择 agent", "toolbar.unsupportedProtocol": "ACP / pi-rpc 协议暂未接入, 请换其他 agent", "toolbar.enterContent": "输入或上传内容", "toolbar.shortcutHint": "⌘+Enter 转换", "layout.aria.group": "工作区布局", "layout.label.editor": "仅编辑器", "layout.label.split": "并排 (编辑器 + 预览)", "layout.label.preview": "仅预览", "layout.tip.editor": "仅编辑器 — 隐藏预览", "layout.tip.split": "并排 — 编辑器与预览同时显示", "layout.tip.preview": "仅预览 — 隐藏编辑器", "welcome.eyebrow": "Choose your agent", "welcome.titlePart1": "选一个本地", "welcome.titleAccent": "code agent", "welcome.description": "HTML Anything 复用你已经登录的 CLI session — 不要求你再贴一遍 API Key。进入主界面后,你随时可以在顶栏切换到别的 agent。", "welcome.rescan": "↻ 重新检测", "welcome.scanning": "扫描中…", "welcome.rescanTitle": "重新检测", "welcome.detectionFailed": "检测失败", "welcome.installed": "已安装 ({n})", "welcome.notInstalled": "未安装 ({n})", "welcome.noAgentsTitle": "没有检测到任何本地 agent", "welcome.noAgentsBody": "先安装上面任意一个 CLI 并登录, 再回来 ↻ 重新检测。", "welcome.later": "稍后再说", "welcome.enter": "进入编辑器 →", "welcome.current": "当前", "welcome.unsupportedHint": "协议暂未接入, 无法转换", "welcome.pickInstalled": "请先选一个已安装的 agent", "welcome.enterTooltip.noAgent": "先选一个已安装的 agent", "welcome.enterTooltip.unsupported": "ACP / pi-rpc 协议暂未接入, 请选其他 agent", "welcome.enterTooltip.ok": "进入编辑器", "model.eyebrow": "model", "model.label": "为 {agent} 选个模型", "model.defaultHint.prefix": "选 Default 时不传 ", "model.defaultHint.suffix": ", 由 CLI 自己挑", "model.defaultLabel": "Default (CLI config)", "agent.selected": "SELECTED", "agent.notInstalled": "未安装", "agent.customBin.eyebrow": "自定义路径", "agent.customBin.subtitle": "手动指定 {agent} 的二进制路径", "agent.customBin.detected": "自动检测到:", "agent.customBin.hint": "如果自动检测找错了 binary(比如 Windows Scoop / 自定义安装),在这里填绝对路径。留空走自动检测。", "agent.customBin.save": "保存", "agent.customBin.clear": "清除", "protocol.stdin": "stdin · stream", "protocol.argv": "positional argv", "protocol.argvMessage": "argv · 整段 JSON", "protocol.acp": "ACP JSON-RPC · 暂未接入", "protocol.piRpc": "pi-rpc · 暂未接入", "settings.eyebrow": "设置", "settings.titlePart1": "配置", "settings.titleAccent": "HTML Anything", "settings.close": "关闭 (Esc)", "settings.done": "完成", "settings.section.agent.label": "Agent", "settings.section.agent.hint": "本地 CLI · 模型", "settings.section.language.label": "语言", "settings.section.language.hint": "界面语言", "settings.agent.title": "Code agent", "settings.agent.subtitle": "HTML Anything 复用你已经登录的 CLI session — 不需要再贴 API Key。", "settings.language.title": "界面语言", "settings.language.subtitle": "选择 app 界面使用的语言。默认 English; 选择会保存到本地。", "settings.language.active": "当前", "settings.language.default": "默认", "settings.language.note": "目前发布的是 English 和简体中文。增加一种语言只需要在 src/lib/i18n.ts 里加一份字典 — 欢迎贡献。", "settings.section.deploy.label": "部署", "settings.section.deploy.hint": "一键发布", "settings.deploy.title": "一键部署", "settings.deploy.subtitle": "把生成好的 HTML 一键发布成公网链接。Token 仅本地保存于 ~/.html-anything(chmod 600)。", "settings.deploy.vercel.title": "Vercel", "settings.deploy.vercel.tokenLabel": "API Token", "settings.deploy.vercel.tokenPlaceholder": "vercel_xxx…", "settings.deploy.vercel.tokenHint": "去 vercel.com/account/tokens 创建一个 token,'Full Account' 范围即可。", "settings.deploy.vercel.teamSlugLabel": "Team slug(可选)", "settings.deploy.vercel.teamSlugPlaceholder": "my-team", "settings.deploy.save": "保存", "settings.deploy.clear": "清除", "settings.deploy.configured": "已配置", "deploy.button": "发布", "deploy.button.disabled": "请先生成 HTML", "deploy.deploying": "部署中…", "deploy.success.label": "已发布到", "deploy.success.copy": "复制", "deploy.success.copied": "已复制", "deploy.success.dismiss": "关闭", "deploy.success.open": "打开", "deploy.protected.label": "已部署(SSO 保护中)", "deploy.protected.hint": "Vercel 默认对预览部署启用 SSO 保护。先打开链接登录一次,或在 Vercel 项目设置 → Deployment Protection 关掉。", "deploy.delayed.label": "已部署(CDN 同步中)", "deploy.delayed.hint": "链接已生成但 CDN 还没完全同步,30–60 秒后再试。", "deploy.error.label": "部署失败", "deploy.error.tokenMissing": "未配置 Token。打开 Settings → Deploy 添加。", "deploy.history.title": "历史部署", "deploy.history.delete": "删除记录", "deploy.provider.vercel": "Vercel", "deploy.provider.cloudflarePages": "Cloudflare Pages", "deploy.provider.cloudflarePages.comingSoon": "敬请期待", "deploy.menu.deployTo": "部署到…", "settings.section.marketplace.label": "市场", "settings.section.marketplace.hint": "从 GitHub 安装 skill", "settings.marketplace.title": "Skill 市场", "settings.marketplace.subtitle": "从公开 GitHub 仓库安装社区 skill 包。每个包带一个或多个 SKILL.md 提示词, 安装后会出现在模板选择器里。", "marketplace.installFromGithub": "从 GitHub 安装", "marketplace.placeholder": "owner/repo 或 owner/repo#branch", "marketplace.install": "安装", "marketplace.installing": "安装中…", "marketplace.uninstall": "卸载", "marketplace.hint": "仅支持公开 GitHub 仓库。仓库根目录需有 SKILL.md, 或在 skills//SKILL.md 下放多个 skill。", "marketplace.installed": "已安装 ({n})", "marketplace.installSucceeded": "从 {repo} 安装了 {n} 个 skill。", "marketplace.uninstalled": "已卸载。", "marketplace.skillCount": "{n} 个 skill", "marketplace.empty.title": "尚未安装任何包", "marketplace.empty.body": "在上方粘贴一个 GitHub 仓库地址即可安装社区 skill 包。", "editor.tab.text": "✏️ 输入", "editor.tab.samples": "✨ 示例", "editor.tab.formats": "📋 格式", "editor.attach": "附加文件", "editor.attachTooltip": "拖文件进来或点这里上传 — 自动接到编辑器内容末尾", "editor.dropTitle": "松手附加", "editor.dropHint": ".md .txt .pdf .csv .tsv .xlsx .json .sql .yaml .png .jpg — 内容会接到编辑器末尾", "editor.backup": "⤓ 备份", "editor.backupTooltip": "把当前内容下载为 .md / .txt 备份文件", "editor.restoring": "恢复上次内容…", "editor.placeholder": "粘贴任何内容到这里 — Markdown / CSV / Excel 复制 / JSON / SQL / 你的草稿…\n\n按 ⌘+Enter 转换\n\n所有输入会自动保存到 localStorage, 刷新不丢; 点击右上角 ↶ 历史 可查看最近 8 个版本。", "editor.chars": "{n} 字", "editor.saving": "正在保存…", "editor.saveFailed": "✗ 保存失败", "editor.autosaved": "已自动保存", "editor.autosaveTooltip": "内容存到浏览器 localStorage, 刷新不丢", "preview.tab.preview": "👁 预览", "preview.tab.deck": "🎬 PPT", "preview.tab.code": " 源码", "preview.tab.log": "📋 日志", "preview.status.idle": "待机", "preview.status.running": "生成中", "preview.status.done": "完成", "preview.status.error": "错误", "preview.code.waiting": "// 等待 agent 第一个字节…", "preview.code.empty": "// 还没有内容", "preview.code.editHint": "可编辑 — 改动会即时保存,切到预览 (或点刷新) 即可看到效果。", "preview.code.lockedHint": "Agent 正在流式输出, 暂时只读。", "preview.placeholder.runningTitle.part1": "agent 正在", "preview.placeholder.runningTitle.accent": "思考", "preview.placeholder.idleTitle.part1": "把内容粘到左侧, 然后", "preview.placeholder.idleTitle.accent": "⚡", "preview.placeholder.idleTitle.part2": "转换", "preview.placeholder.runningDescr": "Agent 正在生成。有些 CLI(包括 OpenCode)会成块返回, 而不是逐字流式输出。当前已等 {sec}s。", "preview.placeholder.idleDescr": "支持预览 / 查看源码 / 一键复制公众号·推特·知乎 / 截图导出 PNG。", "preview.placeholder.chip.article": "📖 文章", "preview.placeholder.chip.deck": "🎬 PPT", "preview.placeholder.chip.resume": "📄 简历", "preview.placeholder.chip.poster": "🖼️ 海报", "preview.placeholder.chip.xiaohongshu": "📱 小红书", "preview.placeholder.chip.twitterCard": "🐦 推特卡", "preview.placeholder.chip.webProto": "🛠️ Web 原型", "preview.placeholder.chip.dataReport": "📊 数据报告", "preview.log.empty": "这里会实时显示 agent 的事件 (start / delta / meta / stderr / done) 和耗时统计。", "preview.metric.ttfbHint": "首字节延迟", "preview.present": "⛶ 全屏", "preview.exitPresent": "退出全屏", "preview.presentTooltip": "全屏预览 — F 切换 / ESC 退出", "preview.refresh": "刷新", "preview.refreshTooltip": "用当前 HTML 重新渲染预览 iframe", "deck.empty": "本次结果里没有检测到幻灯片。", "deck.prev": "上一页", "deck.next": "下一页", "deck.present": "▶ 放映", "deck.exitPresent": "退出放映", "deck.presentTooltip": "全屏放映 — F 切换 / ESC 退出", "deck.notes": "备注", "deck.notesTooltip": "切换演讲者备注 (N)", "deck.slideN": "{n} / {m}", "tasks.heading": "任务", "tasks.expand": "展开任务列表", "tasks.collapse": "折叠任务列表", "tasks.newTask": "+ 新建任务", "tasks.search.placeholder": "搜索任务名 / 内容 / 模板…", "tasks.search.clear": "清除搜索", "tasks.empty.intro": "没有匹配「{query}」的任务", "tasks.empty.clear": "清除搜索", "tasks.footer": "所有任务的输入 / 输出都会自动保存到浏览器, 刷新后保留。", "tasks.status.idle": "待机", "tasks.status.running": "生成中", "tasks.status.done": "完成", "tasks.status.error": "错误", "tasks.rename": "重命名", "tasks.duplicate": "复制", "tasks.delete": "删除任务", "tasks.deleteConfirm": "删除任务「{name}」? 该任务的输入与生成结果都会丢失。", "tasks.renameDblHint": "双击重命名", "tasks.emptyContent": "空白", "tasks.defaultName": "任务 {n}", "tasks.matchTooltip": "{a} / {b} 匹配", "history.toggle": "版本历史", "history.heading": "历史", "history.close": "关闭历史", "history.versionCap": "每个任务最多保留 {n} 个版本", "history.current": "当前", "history.compare": "对比", "history.restore": "恢复", "history.delete": "删除版本", "history.loading": "正在加载版本…", "history.empty.noTask": "暂无活动任务。", "history.empty.noVersions": "还没有版本。运行一次 Convert 即可生成历史。", "history.restoreConfirm": "恢复到版本 v{v}? 当前预览将被替换。", "history.deleteConfirm": "删除版本 v{v}? 该操作不可撤销。", "history.backToList": "返回", "history.diff.leftLabel": "旧", "history.diff.rightLabel": "新", "history.diff.showSource": "显示 DOM 差异", "history.diff.pickPair": "请选择两个要对比的版本。", "history.diff.before": "旧版本", "history.diff.after": "新版本", "community.starOnGitHub": "在 GitHub 上 Star", "community.joinDiscord": "加入 Discord", "community.upstreamHint": "本项目所基于的上游设计系统", "community.builtBy": "基于", "export.button": "⤓ 导出 / 复制 ▾", "export.section.platform": "复制到平台", "export.section.raw": "复制原始内容", "export.section.download": "下载", "export.section.deck": "Deck · 共 {n} 页", "export.section.hyperframes": "Hyperframes · 共 {n} 帧", "export.action.wechat": "微信公众号", "export.action.zhihu": "知乎", "export.action.twitterImg": "推特 / 微博 (PNG)", "export.action.html": "HTML 源码", "export.action.text": "纯文本", "export.action.downloadHtml": ".html 单文件", "export.action.downloadPng": ".png 高清图", "export.action.deckPdf": "PDF · 全部幻灯片 (打印)", "export.action.deckPngZip": "PNG · 每页一张 (.zip)", "export.action.deckPptx": ".pptx · PowerPoint", "export.action.remotionZip": "Remotion 项目 (.zip)", "export.toast.wechat": "已复制公众号格式", "export.toast.zhihu": "已复制知乎格式", "export.toast.image": "已复制图片", "export.toast.html": "已复制 HTML", "export.toast.text": "已复制文本", "export.toast.htmlSaved": "已下载 HTML", "export.toast.imgSaved": "已下载图片", "export.toast.deckPdf": "已打开打印窗口 — 选「另存为 PDF」", "export.toast.deckPngZip": "已打包 PNG ZIP", "export.toast.deckPptx": "已下载 PPTX", "export.toast.remotionZip": "已打包 Remotion 项目 — 解压后跑 `npx remotion render`", "export.error.previewNotReady": "预览未就绪", "export.error.generic": "失败", "drafts.button": "↶ 历史", "drafts.tooltip": "查看自动保存的版本历史", "drafts.heading": "自动保存的版本", "drafts.count": "{n} 个", "drafts.empty.title": "暂无历史版本", "drafts.empty.hint": "输入超过 20 字, 每 30 秒自动快照", "drafts.restoreConfirm": "恢复到 {when} 的版本? 当前内容会被替换 (新版本会自动备份)。", "drafts.restoredLog": "已恢复 {when} 的版本 ({n} 字)", "drafts.delete": "删除此版本", "drafts.chars": "{n} 字", "drafts.emptyPreview": "(空)", "upload.loadedLog": "已加载 {name} ({fmt})", "upload.failedLog": "解析失败: {err}", "formats.eyebrow": "格式示例", "formats.subtitle": "一键载入每种输入格式的样例片段。先在顶部选模板, 点击「载入」, 再 ⌘+Enter — 由上方的模板决定输出样式。", "formats.loadButton": "✨ 载入 →", "formats.loadedLog": "已载入 {name} ({fmt})", "samples.eyebrow": "示例画廊", "samples.subtitle.preRendered": "预渲染", "samples.subtitle.diff": "diff 编辑", "samples.subtitle.body": "每个示例都已 {preRendered} 世界级 HTML — 点击即在新任务中预览; 修改内容后再 ⌘+Enter, agent 只跑 {diff}, 省 token。", "samples.filter.all": "全部", "samples.loading": "正在载入示例…", "samples.loaded": "✓ 已加载", "samples.loadingButton": "载入中…", "samples.loadButton": "✨ 载入并预览 →", "samples.loadedTooltip": "此示例已经在某个任务里被加载过", "samples.sourceLink": "原文 · {label}", "template.loading": "载入模板…", "template.heading": "选择输出形态", "template.search.placeholder": "搜索模板名 / 描述 / 标签…", "template.search.clear": "清空", "template.filter.all": "全部", "template.filter.featured": "★ 推荐 · Featured", "template.featured.symbol": "★", "template.empty.loading": "正在载入模板…", "template.empty.noMatch": "没有匹配的模板。试试别的关键词或切换分类。", "template.footer.hint.search": "⌕ 搜索", "template.footer.hint.filter": "类别筛选", "template.footer.hint.hover": "悬停缩略预览", "template.footer.hint.preview": "「预览」按钮载入到编辑器", "template.footer.esc": "关闭", "template.preview.button": "预览", "template.preview.loadingButton": "…", "template.preview.tooltip": "把预览快照载入一个新任务", "template.popover.loading": "正在加载预览…", "template.popover.loadInto": "载入到编辑器 →", "template.popover.loadIntoTooltip": "把预览快照载入一个新任务", "template.source.tooltip": "参考开源项目: {label}", "template.scenario.marketing": "营销 / 内容", "template.scenario.engineering": "工程 / 开发", "template.scenario.operations": "运营 / 协作", "template.scenario.product": "产品", "template.scenario.design": "设计 / 探索", "template.scenario.finance": "财务 / 数据", "template.scenario.sales": "销售", "template.scenario.hr": "HR / 入职", "template.scenario.personal": "个人 / 生活", "template.scenario.education": "教育 / 学习", "template.scenario.creator": "创作者", "template.scenario.video": "视频", }; const DICTS: Record = { "en": en, "zh-CN": zhCN, }; export type DictKey = keyof Dict; function format(template: string, vars?: Record): string { if (!vars) return template; return template.replace(/\{(\w+)\}/g, (_, k) => { const v = vars[k]; return v === undefined ? `{${k}}` : String(v); }); } export function t( locale: Locale, key: DictKey, vars?: Record, ): string { const dict = DICTS[locale] ?? DICTS.en; return format(dict[key], vars); } /** * Returns a `t(key, vars?)` bound to the active locale. Components call this * once at the top of render and use the returned function inline. */ export function useT(): (key: DictKey, vars?: Record) => string { const locale = useStore((s) => s.locale); return (key, vars) => t(locale, key, vars); }