wip(app): i18n prompt input

This commit is contained in:
Adam
2026-01-20 07:53:46 -06:00
parent 7138bd021c
commit 835fea6bb1
4 changed files with 158 additions and 93 deletions

View File

@@ -67,33 +67,33 @@ interface PromptInputProps {
onNewSessionWorktreeReset?: () => void
}
const PLACEHOLDERS = [
"Fix a TODO in the codebase",
"What is the tech stack of this project?",
"Fix broken tests",
"Explain how authentication works",
"Find and fix security vulnerabilities",
"Add unit tests for the user service",
"Refactor this function to be more readable",
"What does this error mean?",
"Help me debug this issue",
"Generate API documentation",
"Optimize database queries",
"Add input validation",
"Create a new component for...",
"How do I deploy this project?",
"Review my code for best practices",
"Add error handling to this function",
"Explain this regex pattern",
"Convert this to TypeScript",
"Add logging throughout the codebase",
"What dependencies are outdated?",
"Help me write a migration script",
"Implement caching for this endpoint",
"Add pagination to this list",
"Create a CLI command for...",
"How do environment variables work here?",
]
const EXAMPLES = [
"prompt.example.1",
"prompt.example.2",
"prompt.example.3",
"prompt.example.4",
"prompt.example.5",
"prompt.example.6",
"prompt.example.7",
"prompt.example.8",
"prompt.example.9",
"prompt.example.10",
"prompt.example.11",
"prompt.example.12",
"prompt.example.13",
"prompt.example.14",
"prompt.example.15",
"prompt.example.16",
"prompt.example.17",
"prompt.example.18",
"prompt.example.19",
"prompt.example.20",
"prompt.example.21",
"prompt.example.22",
"prompt.example.23",
"prompt.example.24",
"prompt.example.25",
] as const
interface SlashCommand {
id: string
@@ -186,7 +186,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
popover: null,
historyIndex: -1,
savedPrompt: null,
placeholder: Math.floor(Math.random() * PLACEHOLDERS.length),
placeholder: Math.floor(Math.random() * EXAMPLES.length),
dragging: false,
mode: "normal",
applyingHistory: false,
@@ -259,7 +259,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
params.id
if (params.id) return
const interval = setInterval(() => {
setStore("placeholder", (prev) => (prev + 1) % PLACEHOLDERS.length)
setStore("placeholder", (prev) => (prev + 1) % EXAMPLES.length)
}, 6500)
onCleanup(() => clearInterval(interval))
})
@@ -314,8 +314,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (fileItems.length > 0) {
showToast({
title: "Unsupported paste",
description: "Only images or PDFs can be pasted here.",
title: language.t("prompt.toast.pasteUnsupported.title"),
description: language.t("prompt.toast.pasteUnsupported.description"),
})
return
}
@@ -999,8 +999,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const currentAgent = local.agent.current()
if (!currentModel || !currentAgent) {
showToast({
title: "Select an agent and model",
description: "Choose an agent and model before sending a prompt.",
title: language.t("prompt.toast.modelAgentRequired.title"),
description: language.t("prompt.toast.modelAgentRequired.description"),
})
return
}
@@ -1011,7 +1011,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (data?.message) return data.message
}
if (err instanceof Error) return err.message
return "Request failed"
return language.t("common.requestFailed")
}
addToHistory(currentPrompt, mode)
@@ -1032,7 +1032,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
.then((x) => x.data)
.catch((err) => {
showToast({
title: "Failed to create worktree",
title: language.t("prompt.toast.worktreeCreateFailed.title"),
description: errorMessage(err),
})
return undefined
@@ -1040,8 +1040,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!createdWorktree?.directory) {
showToast({
title: "Failed to create worktree",
description: "Request failed",
title: language.t("prompt.toast.worktreeCreateFailed.title"),
description: language.t("common.requestFailed"),
})
return
}
@@ -1072,7 +1072,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
.then((x) => x.data ?? undefined)
.catch((err) => {
showToast({
title: "Failed to create session",
title: language.t("prompt.toast.sessionCreateFailed.title"),
description: errorMessage(err),
})
return undefined
@@ -1116,7 +1116,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
.catch((err) => {
showToast({
title: "Failed to send shell command",
title: language.t("prompt.toast.shellSendFailed.title"),
description: errorMessage(err),
})
restoreInput()
@@ -1148,7 +1148,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
.catch((err) => {
showToast({
title: "Failed to send command",
title: language.t("prompt.toast.commandSendFailed.title"),
description: errorMessage(err),
})
restoreInput()
@@ -1316,7 +1316,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
.catch((err) => {
showToast({
title: "Failed to send prompt",
title: language.t("prompt.toast.promptSendFailed.title"),
description: errorMessage(err),
})
removeOptimisticMessage()
@@ -1340,7 +1340,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={store.popover === "at"}>
<Show
when={atFlat().length > 0}
fallback={<div class="text-text-weak px-2 py-1">No matching results</div>}
fallback={<div class="text-text-weak px-2 py-1">{language.t("prompt.popover.emptyResults")}</div>}
>
<For each={atFlat().slice(0, 10)}>
{(item) => (
@@ -1386,7 +1386,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Match when={store.popover === "slash"}>
<Show
when={slashFlat().length > 0}
fallback={<div class="text-text-weak px-2 py-1">No matching commands</div>}
fallback={<div class="text-text-weak px-2 py-1">{language.t("prompt.popover.emptyCommands")}</div>}
>
<For each={slashFlat()}>
{(cmd) => (
@@ -1408,7 +1408,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="flex items-center gap-2 shrink-0">
<Show when={cmd.type === "custom"}>
<span class="text-11-regular text-text-subtle px-1.5 py-0.5 bg-surface-base rounded">
custom
{language.t("prompt.slash.badge.custom")}
</span>
</Show>
<Show when={command.keybind(cmd.id)}>
@@ -1437,7 +1437,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
<div class="flex flex-col items-center gap-2 text-text-weak">
<Icon name="photo" class="size-8" />
<span class="text-14-regular">Drop images or PDFs here</span>
<span class="text-14-regular">{language.t("prompt.dropzone.label")}</span>
</div>
</div>
</Show>
@@ -1450,7 +1450,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="flex items-center text-12-regular min-w-0">
<span class="text-text-weak whitespace-nowrap truncate min-w-0">{getDirectory(path())}</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(path())}</span>
<span class="text-text-weak whitespace-nowrap ml-1">active</span>
<span class="text-text-weak whitespace-nowrap ml-1">{language.t("prompt.context.active")}</span>
</div>
<IconButton
type="button"
@@ -1469,7 +1469,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
onClick={() => prompt.context.addActive()}
>
<Icon name="plus-small" size="small" />
<span>Include active file</span>
<span>{language.t("prompt.context.includeActiveFile")}</span>
</button>
</Show>
<For each={prompt.context.items()}>
@@ -1563,7 +1563,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="absolute top-0 inset-x-0 px-5 py-3 pr-12 text-14-regular text-text-weak pointer-events-none whitespace-nowrap truncate">
{store.mode === "shell"
? language.t("prompt.placeholder.shell")
: language.t("prompt.placeholder.normal", { example: PLACEHOLDERS[store.placeholder] })}
: language.t("prompt.placeholder.normal", { example: language.t(EXAMPLES[store.placeholder]) })}
</div>
</Show>
</div>
@@ -1681,7 +1681,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<div class="flex items-center gap-2">
<SessionContextUsage />
<Show when={store.mode === "normal"}>
<Tooltip placement="top" value="Attach file">
<Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
<Button type="button" variant="ghost" class="size-6" onClick={() => fileInputRef.click()}>
<Icon name="photo" class="size-4.5" />
</Button>
@@ -1695,13 +1695,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Switch>
<Match when={working()}>
<div class="flex items-center gap-2">
<span>Stop</span>
<span>{language.t("prompt.action.stop")}</span>
<span class="text-icon-base text-12-medium text-[10px]!">ESC</span>
</div>
</Match>
<Match when={true}>
<div class="flex items-center gap-2">
<span>Send</span>
<span>{language.t("prompt.action.send")}</span>
<Icon name="enter" size="small" class="text-icon-base" />
</div>
</Match>

View File

@@ -110,6 +110,52 @@ export const dict = {
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "esc to exit",
"prompt.example.1": "Fix a TODO in the codebase",
"prompt.example.2": "What is the tech stack of this project?",
"prompt.example.3": "Fix broken tests",
"prompt.example.4": "Explain how authentication works",
"prompt.example.5": "Find and fix security vulnerabilities",
"prompt.example.6": "Add unit tests for the user service",
"prompt.example.7": "Refactor this function to be more readable",
"prompt.example.8": "What does this error mean?",
"prompt.example.9": "Help me debug this issue",
"prompt.example.10": "Generate API documentation",
"prompt.example.11": "Optimize database queries",
"prompt.example.12": "Add input validation",
"prompt.example.13": "Create a new component for...",
"prompt.example.14": "How do I deploy this project?",
"prompt.example.15": "Review my code for best practices",
"prompt.example.16": "Add error handling to this function",
"prompt.example.17": "Explain this regex pattern",
"prompt.example.18": "Convert this to TypeScript",
"prompt.example.19": "Add logging throughout the codebase",
"prompt.example.20": "What dependencies are outdated?",
"prompt.example.21": "Help me write a migration script",
"prompt.example.22": "Implement caching for this endpoint",
"prompt.example.23": "Add pagination to this list",
"prompt.example.24": "Create a CLI command for...",
"prompt.example.25": "How do environment variables work here?",
"prompt.popover.emptyResults": "No matching results",
"prompt.popover.emptyCommands": "No matching commands",
"prompt.dropzone.label": "Drop images or PDFs here",
"prompt.slash.badge.custom": "custom",
"prompt.context.active": "active",
"prompt.context.includeActiveFile": "Include active file",
"prompt.action.attachFile": "Attach file",
"prompt.action.send": "Send",
"prompt.action.stop": "Stop",
"prompt.toast.pasteUnsupported.title": "Unsupported paste",
"prompt.toast.pasteUnsupported.description": "Only images or PDFs can be pasted here.",
"prompt.toast.modelAgentRequired.title": "Select an agent and model",
"prompt.toast.modelAgentRequired.description": "Choose an agent and model before sending a prompt.",
"prompt.toast.worktreeCreateFailed.title": "Failed to create worktree",
"prompt.toast.sessionCreateFailed.title": "Failed to create session",
"prompt.toast.shellSendFailed.title": "Failed to send shell command",
"prompt.toast.commandSendFailed.title": "Failed to send command",
"prompt.toast.promptSendFailed.title": "Failed to send prompt",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "{{enabled}} of {{total}} enabled",
"dialog.mcp.empty": "No MCPs configured",

View File

@@ -114,6 +114,52 @@ export const dict = {
"prompt.mode.shell": "Shell",
"prompt.mode.shell.exit": "按 esc 退出",
"prompt.example.1": "修复代码库中的一个 TODO",
"prompt.example.2": "这个项目的技术栈是什么?",
"prompt.example.3": "修复失败的测试",
"prompt.example.4": "解释认证是如何工作的",
"prompt.example.5": "查找并修复安全漏洞",
"prompt.example.6": "为用户服务添加单元测试",
"prompt.example.7": "重构这个函数,让它更易读",
"prompt.example.8": "这个错误是什么意思?",
"prompt.example.9": "帮我调试这个问题",
"prompt.example.10": "生成 API 文档",
"prompt.example.11": "优化数据库查询",
"prompt.example.12": "添加输入校验",
"prompt.example.13": "创建一个新的组件用于...",
"prompt.example.14": "我该如何部署这个项目?",
"prompt.example.15": "审查我的代码并给出最佳实践建议",
"prompt.example.16": "为这个函数添加错误处理",
"prompt.example.17": "解释这个正则表达式",
"prompt.example.18": "把它转换成 TypeScript",
"prompt.example.19": "在整个代码库中添加日志",
"prompt.example.20": "哪些依赖已经过期?",
"prompt.example.21": "帮我写一个迁移脚本",
"prompt.example.22": "为这个接口实现缓存",
"prompt.example.23": "给这个列表添加分页",
"prompt.example.24": "创建一个 CLI 命令用于...",
"prompt.example.25": "这里的环境变量是怎么工作的?",
"prompt.popover.emptyResults": "没有匹配的结果",
"prompt.popover.emptyCommands": "没有匹配的命令",
"prompt.dropzone.label": "将图片或 PDF 拖到这里",
"prompt.slash.badge.custom": "自定义",
"prompt.context.active": "当前",
"prompt.context.includeActiveFile": "包含当前文件",
"prompt.action.attachFile": "附加文件",
"prompt.action.send": "发送",
"prompt.action.stop": "停止",
"prompt.toast.pasteUnsupported.title": "不支持的粘贴",
"prompt.toast.pasteUnsupported.description": "这里只能粘贴图片或 PDF 文件。",
"prompt.toast.modelAgentRequired.title": "请选择智能体和模型",
"prompt.toast.modelAgentRequired.description": "发送提示前请先选择智能体和模型。",
"prompt.toast.worktreeCreateFailed.title": "创建工作树失败",
"prompt.toast.sessionCreateFailed.title": "创建会话失败",
"prompt.toast.shellSendFailed.title": "发送 shell 命令失败",
"prompt.toast.commandSendFailed.title": "发送命令失败",
"prompt.toast.promptSendFailed.title": "发送提示失败",
"dialog.mcp.title": "MCPs",
"dialog.mcp.description": "已启用 {{enabled}} / {{total}}",
"dialog.mcp.empty": "未配置 MCPs",

View File

@@ -9,8 +9,8 @@ This report documents the remaining user-facing strings in `packages/app/src` th
## Current State
- The app uses `useLanguage().t("...")` with dictionaries in `packages/app/src/i18n/en.ts` and `packages/app/src/i18n/zh.ts`.
- Recent progress (already translated): `packages/app/src/pages/home.tsx`, `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/session.tsx` (plus new keys added in both dictionaries).
- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (242 keys each; no missing or extra keys).
- Recent progress (already translated): `packages/app/src/pages/home.tsx`, `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/session.tsx`, `packages/app/src/components/prompt-input.tsx` (plus new keys added in both dictionaries).
- Dictionary parity check: `en.ts` and `zh.ts` currently contain the same key set (285 keys each; no missing or extra keys).
## Methodology
@@ -59,38 +59,13 @@ This is the largest remaining untranslated surface and is user-visible during ap
File: `packages/app/src/components/prompt-input.tsx`
This is the largest remaining i18n surface (placeholders, empty states, tooltips, toasts).
Completed (2026-01-20):
**Untranslated prompt examples**
- `PLACEHOLDERS` array is English-only (e.g. "Fix broken tests", "Explain how authentication works", ...)
- Note: the placeholder key `prompt.placeholder.normal` exists and interpolates `{{example}}`, but the examples are not localized.
**Toast copy**
- "Unsupported paste" / "Only images or PDFs can be pasted here."
- "Select an agent and model" / "Choose an agent and model before sending a prompt."
- Failure toasts:
- "Failed to create worktree"
- "Failed to create session"
- "Failed to send shell command"
- "Failed to send command"
- "Failed to send prompt"
- Fallback return string: "Request failed" (you already have `common.requestFailed`)
**Empty states / popovers / overlays**
- "No matching results"
- "No matching commands"
- Drag/drop overlay: "Drop images or PDFs here"
**Labels / badges / buttons**
- Slash badge label: "custom"
- File pill label: "active"
- Action: "Include active file"
- Send/Stop labels: "Send", "Stop" (and the "ESC" hint)
- Tooltip: "Attach file"
**Recommendation:**
- Introduce a `prompt.*` namespace for UI strings and toast titles/descriptions.
- Handle prompt examples as locale-specific arrays OR enumerated keys (e.g. `prompt.example.1`, `prompt.example.2`, ...).
- Localized placeholder examples by replacing the hardcoded `PLACEHOLDERS` list with `prompt.example.*` keys.
- Localized toast titles/descriptions via `prompt.toast.*` and reused `common.requestFailed` for fallback error text.
- Localized popover empty states and drag/drop overlay copy (`prompt.popover.*`, `prompt.dropzone.label`).
- Localized smaller labels (slash "custom" badge, attach button tooltip, Send/Stop tooltip labels).
- Kept the `ESC` keycap itself untranslated (key label).
### 3) Provider Connection / Auth Flow
@@ -275,13 +250,12 @@ This is only thrown in DEV and is more of a developer diagnostic. Optional to tr
## Prioritized Implementation Plan
1. `packages/app/src/components/prompt-input.tsx`
2. `packages/app/src/components/dialog-connect-provider.tsx`
3. `packages/app/src/components/session/session-header.tsx`
4. `packages/app/src/pages/error.tsx`
5. `packages/app/src/components/session/session-new-view.tsx`
6. `packages/app/src/components/session-context-usage.tsx` + locale formatting improvements (also `packages/app/src/components/session/session-context-tab.tsx`)
7. Small stragglers:
1. `packages/app/src/components/dialog-connect-provider.tsx`
2. `packages/app/src/components/session/session-header.tsx`
3. `packages/app/src/pages/error.tsx`
4. `packages/app/src/components/session/session-new-view.tsx`
5. `packages/app/src/components/session-context-usage.tsx` + locale formatting improvements (also `packages/app/src/components/session/session-context-tab.tsx`)
6. Small stragglers:
- `packages/app/src/components/session-lsp-indicator.tsx`
- `packages/app/src/components/session/session-sortable-tab.tsx`
- `packages/app/src/components/titlebar.tsx`
@@ -290,7 +264,7 @@ This is only thrown in DEV and is more of a developer diagnostic. Optional to tr
- `packages/app/src/context/global-sync.tsx`
- `packages/app/src/context/file.tsx` + `packages/app/src/context/local.tsx`
- `packages/app/src/utils/prompt.ts`
8. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`).
7. Decide on the terminal naming approach (`packages/app/src/context/terminal.tsx`).
## Suggested Key Naming Conventions
@@ -313,7 +287,6 @@ Pages:
- `packages/app/src/pages/error.tsx`
Components:
- `packages/app/src/components/prompt-input.tsx`
- `packages/app/src/components/dialog-connect-provider.tsx`
- `packages/app/src/components/session/session-header.tsx`
- `packages/app/src/components/session/session-new-view.tsx`