Skip to content

Add English and Chinese localization#528

Open
NAMEWTA wants to merge 1 commit into
cline:mainfrom
NAMEWTA:feature/i18n-en-zh
Open

Add English and Chinese localization#528
NAMEWTA wants to merge 1 commit into
cline:mainfrom
NAMEWTA:feature/i18n-en-zh

Conversation

@NAMEWTA

@NAMEWTA NAMEWTA commented Jun 9, 2026

Copy link
Copy Markdown

Summary

  • add app-level i18n with English as the default language and a Chinese switcher
  • localize board, task, top bar, project navigation, detail panels, and Settings/Cline provider surfaces
  • bypass Codex hook trust prompts for Kanban-managed hooks so hook review prompts do not block new Codex sessions

Testing

  • npm run typecheck
  • npm --prefix web-ui run typecheck
  • npm run lint
  • npm --prefix web-ui run build
  • npm run test -- test/runtime/terminal/agent-session-adapters.test.ts
  • npm --prefix web-ui run test -- top-bar.test.tsx task-agent-model-picker.test.tsx board-card.test.tsx
  • npm run test:fast via pre-commit hooks
  • Playwright smoke check in zh-CN for Settings and Cline settings

Copilot AI review requested due to automatic review settings June 9, 2026 03:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds an i18n system (English + Chinese) to the web UI with a language switcher and persisted language preference, and updates Codex agent launch args for hook-trust bypass.

Changes:

  • Introduces i18n infrastructure (translations, context provider, language persistence) and wires it into the app root.
  • Replaces many user-facing strings with translated keys and adds a language switcher in the top bar.
  • Adds --dangerously-bypass-hook-trust to Codex CLI args (and tests) to ensure hook strategies run without trust gating.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
web-ui/src/storage/local-storage-store.ts Adds a LocalStorage key for persisted language selection.
web-ui/src/main.tsx Wraps the app in I18nProvider so translations are available globally.
web-ui/src/i18n/translations.ts Adds language registry, translation dictionaries, and translate/interpolation helpers.
web-ui/src/i18n/i18n-context.tsx Adds i18n React context/provider with localStorage persistence and <html lang> updates.
web-ui/src/components/top-bar.tsx Localizes labels/tooltips and adds the LanguageSwitcher to the top bar.
web-ui/src/components/task-trash-warning-dialog.tsx Localizes warning copy and guidance strings.
web-ui/src/components/task-prompt-composer.tsx Localizes composer placeholder and file mention messages.
web-ui/src/components/task-inline-create-card.tsx Localizes create/edit UI, including auto-review mode labels.
web-ui/src/components/task-create-dialog.tsx Localizes dialog titles, buttons, and multi-create copy.
web-ui/src/components/task-agent-model-picker.tsx Localizes labels (“Default”, “Provider”, etc.) and loading strings.
web-ui/src/components/shared/cline-setup-section.tsx Localizes Cline setup UI strings and errors; localizes reasoning effort labels.
web-ui/src/components/shared/cline-add-provider-dialog.tsx Localizes provider dialog UI strings and errors.
web-ui/src/components/shared/account-organization-section.tsx Localizes account/org and credits UI strings and errors.
web-ui/src/components/search-select-dropdown.tsx Adds i18n-backed default placeholder/empty/no-results text.
web-ui/src/components/runtime-settings-dialog.tsx Localizes settings navigation/labels/errors and related UI copy.
web-ui/src/components/project-navigation-panel.tsx Localizes sidebar, tips, shortcuts, and project list UI strings.
web-ui/src/components/open-workspace-button.tsx Localizes “Open” and aria-labels for open targets.
web-ui/src/components/language-switcher.tsx New language selector component using i18n context.
web-ui/src/components/detail-panels/column-context-panel.tsx Localizes column actions/titles/empty states.
web-ui/src/components/clear-trash-dialog.tsx Localizes clear-done confirmation dialog strings.
web-ui/src/components/card-detail-view.tsx Localizes tab labels, diff toolbar labels, resize aria labels, and action text.
web-ui/src/components/branch-select-dropdown.tsx Uses i18n defaults for empty/no-results texts.
web-ui/src/components/board-column.tsx Localizes column titles and column action labels/tooltips.
web-ui/src/components/board-card.tsx Localizes status text, labels, suffixes, and action aria-labels.
web-ui/src/App.tsx Localizes top-level empty state strings and toast messages.
test/runtime/terminal/agent-session-adapters.test.ts Adds coverage asserting the new Codex CLI flag is present and ordered correctly.
src/terminal/agent-session-adapters.ts Adds helper to insert CLI flags before subcommands; applies new Codex hook-trust bypass flag.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +31
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { LocalStorageKey, readLocalStorageItem, writeLocalStorageItem } from "@/storage/local-storage-store";
import {
type AppLanguage,
DEFAULT_LANGUAGE,
isAppLanguage,
type TranslationKey,
type TranslationValues,
translate,
} from "./translations";

interface I18nContextValue {
language: AppLanguage;
setLanguage: (language: AppLanguage) => void;
t: (key: TranslationKey, values?: TranslationValues) => string;
}

const I18nContext = createContext<I18nContextValue | null>(null);
const fallbackI18nContextValue: I18nContextValue = {
language: DEFAULT_LANGUAGE,
setLanguage: () => {},
t: (key, values) => translate(DEFAULT_LANGUAGE, key, values),
};

function readStoredLanguage(): AppLanguage {
const storedLanguage = readLocalStorageItem(LocalStorageKey.Language);
return isAppLanguage(storedLanguage) ? storedLanguage : DEFAULT_LANGUAGE;
}

export function I18nProvider({ children }: { children: React.ReactNode }): React.ReactElement {
const [language, setLanguageState] = useState<AppLanguage>(readStoredLanguage);
Comment on lines +130 to +136
function addCliOptionBeforeSubcommand(args: string[], optionName: string, subcommands: readonly string[]): void {
if (hasCliOption(args, optionName)) {
return;
}
const subcommandIndex = args.findIndex((arg) => subcommands.includes(arg));
args.splice(subcommandIndex === -1 ? args.length : subcommandIndex, 0, optionName);
}
Comment on lines 771 to 775
const hooks = resolveHookContext(input);
if (hooks) {
addCliOptionBeforeSubcommand(codexArgs, "--dangerously-bypass-hook-trust", ["resume", "fork"]);
configureCodexHooks(codexArgs);
Object.assign(
Comment on lines +40 to +55
{APP_LANGUAGES.map((item) => (
<button
type="button"
key={item.id}
className={cn(
"flex w-full cursor-pointer items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-[13px] text-text-primary hover:bg-surface-3",
item.id === language && "bg-surface-3",
)}
onClick={() => handleSelect(item.id)}
>
<span className="flex-1">
{item.id === "zh" ? t("language.option.zh") : t("language.option.en")}
</span>
{item.id === language ? <Check size={14} className="text-text-secondary" /> : null}
</button>
))}
Comment thread web-ui/src/components/board-column.tsx Outdated
Comment on lines +91 to +98
const columnTitle =
column.id === "backlog"
? t("board.column.backlog")
: column.id === "in_progress"
? t("board.column.inProgress")
: column.id === "review"
? t("board.column.review")
: t("board.column.done");
Comment on lines +70 to +77
const columnTitle =
column.id === "backlog"
? t("board.column.backlog")
: column.id === "in_progress"
? t("board.column.inProgress")
: column.id === "review"
? t("board.column.review")
: t("board.column.done");
@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces app-level i18n with English as default and Simplified Chinese as a switchable locale, covering all UI surfaces (board, task cards, top bar, settings, detail panels, Cline provider dialogs). It also adds --dangerously-bypass-hook-trust to Codex launch args when Kanban-managed hooks are configured, preventing hook trust prompts from blocking new sessions.

  • i18n infrastructure: A new I18nProvider/useI18n context persists the user's language preference to localStorage, and a LanguageSwitcher popover in the top bar controls the setting. TypeScript's satisfies Record<TranslationKey, string> on the Chinese translation object enforces compile-time completeness.
  • String localization: All 400+ hardcoded strings across 20+ components are replaced with t() calls. Pluralization is handled by keying on singular/plural translation entries; useMemo/useCallback deps are correctly updated to include t.
  • Codex hook trust bypass: addCliOptionBeforeSubcommand inserts --dangerously-bypass-hook-trust before any resume/fork subcommand when Kanban hooks are active, with an hasCliOption guard to prevent double-insertion and a new test asserting correct placement.

Confidence Score: 4/5

Safe to merge. The i18n infrastructure is well-structured and the only behavioral change outside UI strings is the automatic insertion of --dangerously-bypass-hook-trust when Kanban hooks fire, which is covered by an updated test and matches the stated intent.

The implementation is thorough: TypeScript enforces translation completeness, useMemo/useCallback deps are correctly updated throughout, and localStorage persistence works symmetrically with the existing theme pattern. The two minor concerns are board-column.tsx silently falling back to "Done" for any unrecognized column ID, and the positional sentence-fragment pattern in task.fileImageHint.* that would need JSX reordering for any future locale with different grammar around inline code elements.

board-column.tsx (implicit "done" fallback for unknown column IDs) and task-create-dialog.tsx / task-inline-create-card.tsx (positional fileImageHint sentence fragments)

Important Files Changed

Filename Overview
web-ui/src/i18n/translations.ts Adds 400+ translation keys for English and Chinese. TypeScript satisfies Record<TranslationKey, string> on the zh object guarantees compile-time completeness.
web-ui/src/i18n/i18n-context.tsx New React context providing language, setLanguage, and t. Language preference is persisted to localStorage. useI18n returns a safe English fallback when used outside the provider.
web-ui/src/components/language-switcher.tsx New popover component for switching between EN and ZH. Correctly uses useI18n and renders in the top bar.
web-ui/src/components/board-column.tsx Column title now derived from column.id via i18n. The "trash" column ID correctly falls through to board.column.done, but any future/unknown column ID would also silently display "Done".
web-ui/src/components/board-card.tsx Localizes all status text, labels, and aria-labels. getCardSessionActivity now takes t as a parameter; useMemo deps updated accordingly.
src/terminal/agent-session-adapters.ts Adds --dangerously-bypass-hook-trust before any resume/fork subcommand when Kanban hooks are configured. Guards against double-insertion and covered by updated test.
web-ui/src/components/task-trash-warning-dialog.tsx Replaces hardcoded strings with t() calls. task.changedFiles correctly called with count and fileLabel, producing well-formed sentences in both locales.
web-ui/src/components/task-create-dialog.tsx Fully localized. AUTO_REVIEW_MODE_OPTIONS correctly simplified to TaskAutoReviewMode[]. The task.fileImageHint.* three-part pattern works but is order-dependent.
web-ui/src/storage/local-storage-store.ts Adds Language = "kanban.language" enum value. No other changes.
web-ui/src/components/top-bar.tsx All labels and aria-attributes localized. LanguageSwitcher added. Git tooltip pluralization uses correct deps.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    LS[localStorage kanban.language] -->|read on mount| IP[I18nProvider useState language]
    IP -->|useCallback t| CTX[I18nContext]
    IP -->|useEffect| LS2[write localStorage set document.lang]
    CTX -->|useI18n| COMP[All UI Components]
    COMP -->|t key values| TR[translate function]
    TR -->|lookup| EN[en translations]
    TR -->|lookup| ZH[zh translations]
    EN -->|template string| OUT[Rendered string]
    ZH -->|template string| OUT
    LSUI[LanguageSwitcher in TopBar] -->|setLanguage| IP
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
web-ui/src/components/board-column.tsx:83-91
**Implicit fallback for "trash" and future column IDs**

The ternary chain falls through to `t("board.column.done")` for any `column.id` that isn't "backlog", "in_progress", or "review". This works today because `"trash"` is the Done column in this codebase, but the mapping is implicit and will silently render "Done" for any column ID added in the future. Previously, `column.title` from the data model handled this gracefully. Consider explicitly handling the `"trash"` case or using a lookup map (`columnTitleKeys[column.id] ?? column.title`) so unknown IDs don't silently fall back to "Done".

### Issue 2 of 2
web-ui/src/components/task-create-dialog.tsx:444-460
**Sentence-fragment translation pattern is fragile across locales**

The hint text is split into three positional keys (`task.fileImageHint.beforeFile`, `task.fileImageHint.afterFile`, `task.fileImageHint.afterShortcut`) that are interleaved with two JSX `<code>` elements in a fixed order. This works for English and Simplified Chinese, but the same pattern appears in `task-inline-create-card.tsx`. Any locale whose grammar requires a different word order around the inline code elements would require a code change to reorder the JSX nodes. A single key with named string placeholders, or a light rich-text helper, would make future locale additions self-contained.

Reviews (1): Last reviewed commit: "fix: bypass Codex hook trust prompts" | Re-trigger Greptile

Comment thread web-ui/src/components/board-column.tsx Outdated
Comment on lines +83 to +91
const createTaskButtonText = (
<span className="inline-flex items-center gap-1.5">
<span>Create task</span>
<span>{t("board.createTask")}</span>
<span aria-hidden className="text-text-secondary">
(c)
</span>
</span>
);
const columnTitle =

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Implicit fallback for "trash" and future column IDs

The ternary chain falls through to t("board.column.done") for any column.id that isn't "backlog", "in_progress", or "review". This works today because "trash" is the Done column in this codebase, but the mapping is implicit and will silently render "Done" for any column ID added in the future. Previously, column.title from the data model handled this gracefully. Consider explicitly handling the "trash" case or using a lookup map (columnTitleKeys[column.id] ?? column.title) so unknown IDs don't silently fall back to "Done".

Prompt To Fix With AI
This is a comment left during a code review.
Path: web-ui/src/components/board-column.tsx
Line: 83-91

Comment:
**Implicit fallback for "trash" and future column IDs**

The ternary chain falls through to `t("board.column.done")` for any `column.id` that isn't "backlog", "in_progress", or "review". This works today because `"trash"` is the Done column in this codebase, but the mapping is implicit and will silently render "Done" for any column ID added in the future. Previously, `column.title` from the data model handled this gracefully. Consider explicitly handling the `"trash"` case or using a lookup map (`columnTitleKeys[column.id] ?? column.title`) so unknown IDs don't silently fall back to "Done".

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +444 to +460
onImagesChange={onImagesChange}
onSubmit={handleCreateSingle}
onSubmitAndStart={() => handleRunSingleStartAction("start")}
placeholder="Describe the task..."
placeholder={t("task.describePlaceholder")}
autoFocus
workspaceId={workspaceId}
showAttachImageButton={false}
/>
<div className="flex items-center justify-between mt-1.5">
<p className="text-[11px] text-text-tertiary">
Use <code className="rounded bg-surface-3 px-1 py-px font-mono text-[11px]">@file</code> to
reference files. Drag and drop or{" "}
{t("task.fileImageHint.beforeFile")}{" "}
<code className="rounded bg-surface-3 px-1 py-px font-mono text-[11px]">@file</code>{" "}
{t("task.fileImageHint.afterFile")}{" "}
<code className="rounded bg-surface-3 px-1 py-px font-mono text-[11px]">
{pasteShortcutLabel}
</code>{" "}
to add images.
{t("task.fileImageHint.afterShortcut")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Sentence-fragment translation pattern is fragile across locales

The hint text is split into three positional keys (task.fileImageHint.beforeFile, task.fileImageHint.afterFile, task.fileImageHint.afterShortcut) that are interleaved with two JSX <code> elements in a fixed order. This works for English and Simplified Chinese, but the same pattern appears in task-inline-create-card.tsx. Any locale whose grammar requires a different word order around the inline code elements would require a code change to reorder the JSX nodes. A single key with named string placeholders, or a light rich-text helper, would make future locale additions self-contained.

Prompt To Fix With AI
This is a comment left during a code review.
Path: web-ui/src/components/task-create-dialog.tsx
Line: 444-460

Comment:
**Sentence-fragment translation pattern is fragile across locales**

The hint text is split into three positional keys (`task.fileImageHint.beforeFile`, `task.fileImageHint.afterFile`, `task.fileImageHint.afterShortcut`) that are interleaved with two JSX `<code>` elements in a fixed order. This works for English and Simplified Chinese, but the same pattern appears in `task-inline-create-card.tsx`. Any locale whose grammar requires a different word order around the inline code elements would require a code change to reorder the JSX nodes. A single key with named string placeholders, or a light rich-text helper, would make future locale additions self-contained.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@NAMEWTA NAMEWTA force-pushed the feature/i18n-en-zh branch from 9a4b98e to 32c5161 Compare June 9, 2026 03:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants