diff --git a/.plans/web-i18n-rollout.md b/.plans/web-i18n-rollout.md
new file mode 100644
index 000000000..1d73bab40
--- /dev/null
+++ b/.plans/web-i18n-rollout.md
@@ -0,0 +1,196 @@
+# Web i18n Rollout Tracker
+
+_Last updated: 2026-03-31_
+
+This document tracks the phased rollout of multilingual support for `apps/web`.
+
+Supported locales:
+
+- `en`
+- `es`
+- `fr`
+- `zh-CN`
+
+Status values:
+
+- `TODO`: Not started
+- `IN_PROGRESS`: Started but not yet shippable
+- `DONE`: Implemented and verified
+- `BLOCKED`: Waiting on a dependency or decision
+
+## Scope
+
+In scope for this rollout:
+
+- frontend-only localization in `apps/web`
+- product-owned UI strings only
+- locale persistence via app settings
+- locale-aware timestamps
+- root screens, settings, onboarding, and mobile pairing in the first shippable stop
+
+Out of scope for this rollout:
+
+- `apps/server`
+- `apps/marketing`
+- user/model/code content translation
+- arbitrary provider/server freeform error translation
+
+## Current Snapshot
+
+Overall status: `IN_PROGRESS`
+
+Completed so far:
+
+- Added `react-intl` to `apps/web`
+- Added locale schema support to `apps/web/src/appSettings.ts`
+- Added the shared i18n scaffolding under `apps/web/src/i18n/`
+- Added initial message catalogs for `en`, `es`, `fr`, and `zh-CN`
+- Wired the root route through a shared `I18nProvider`
+- Made timestamps honor the resolved app locale
+- Updated the timestamp callsites in chat and diff surfaces
+- Added Phase 1 guardrail tests for locale resolution, timestamp formatting, and catalog parity
+- Resolved the repo-wide server typecheck blocker by forcing a single `effect` version across `@effect/*`
+- `apps/web` tests pass
+- `bun fmt` passed
+- `bun lint` passed
+- `bun typecheck` passed
+
+Not yet completed:
+
+- Settings page migration
+- Onboarding migration
+- Mobile pairing migration
+
+## Phase 1 — Infrastructure
+
+Objective:
+Establish the shared localization foundation without coupling it to the server or user content.
+
+Checklist:
+
+- [x] Add `react-intl` to `apps/web`
+ - Status: `DONE`
+- [x] Add persisted locale preference to `apps/web/src/appSettings.ts`
+ - Status: `DONE`
+- [x] Add shared i18n module in `apps/web/src/i18n/`
+ - Status: `DONE`
+- [x] Add message catalogs for `en`, `es`, `fr`, and `zh-CN`
+ - Status: `DONE`
+- [ ] Wire `I18nProvider` into [apps/web/src/routes/__root.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/routes/__root.tsx)
+ - Status: `DONE`
+- [ ] Expose stable translation helpers for component usage
+ - Status: `DONE`
+- [ ] Add locale-aware timestamp formatting in [apps/web/src/timestampFormat.ts](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/timestampFormat.ts)
+ - Status: `DONE`
+- [ ] Update timestamp callsites in [apps/web/src/components/chat/MessagesTimeline.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/chat/MessagesTimeline.tsx)
+ - Status: `DONE`
+- [ ] Update timestamp callsites in [apps/web/src/components/DiffPanel.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/DiffPanel.tsx)
+ - Status: `DONE`
+
+Exit criteria:
+
+- Locale can be resolved at runtime from `system | en | es | fr | zh-CN`
+- The app can render under a single root i18n provider
+- Timestamp formatting can follow the selected app locale
+
+## Phase 2 — First Shippable Surfaces
+
+Objective:
+Ship a coherent multilingual slice that is complete on the highest-value product-owned surfaces.
+
+Checklist:
+
+- [ ] Migrate root route loading/error copy in [apps/web/src/routes/__root.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/routes/__root.tsx)
+ - Status: `TODO`
+- [ ] Migrate root keybinding toasts in [apps/web/src/routes/__root.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/routes/__root.tsx)
+ - Status: `TODO`
+- [ ] Add language selector to [apps/web/src/routes/_chat.settings.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/routes/_chat.settings.tsx)
+ - Status: `TODO`
+- [ ] Migrate product-owned settings copy in [apps/web/src/routes/_chat.settings.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/routes/_chat.settings.tsx)
+ - Status: `TODO`
+- [ ] Migrate supporting settings components in [apps/web/src/components/EnvironmentVariablesEditor.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/EnvironmentVariablesEditor.tsx)
+ - Status: `TODO`
+- [ ] Migrate supporting settings components in [apps/web/src/components/CustomThemeDialog.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/CustomThemeDialog.tsx)
+ - Status: `TODO`
+- [ ] Migrate onboarding content in [apps/web/src/components/onboarding/onboardingSteps.ts](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/onboarding/onboardingSteps.ts)
+ - Status: `TODO`
+- [ ] Migrate onboarding controls in [apps/web/src/components/onboarding/OnboardingDialog.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/onboarding/OnboardingDialog.tsx)
+ - Status: `TODO`
+- [ ] Migrate mobile pairing UI in [apps/web/src/components/mobile/MobilePairingScreen.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/mobile/MobilePairingScreen.tsx)
+ - Status: `TODO`
+
+Exit criteria:
+
+- Users can select a language in Settings without reloading
+- Root screens, Settings, Onboarding, and Mobile Pairing render localized product UI
+- English remains the safe fallback when a locale cannot be resolved or loaded
+
+## Phase 3 — High-Traffic Product Surfaces
+
+Objective:
+Extend localization to the most visible remaining chrome and toast-heavy flows.
+
+Checklist:
+
+- [ ] Migrate sidebar toasts and chrome in [apps/web/src/components/Sidebar.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/Sidebar.tsx)
+ - Status: `TODO`
+- [ ] Migrate chat home empty state in [apps/web/src/components/ChatHomeEmptyState.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/ChatHomeEmptyState.tsx)
+ - Status: `TODO`
+- [ ] Migrate workspace file tree messages in [apps/web/src/components/WorkspaceFileTree.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/WorkspaceFileTree.tsx)
+ - Status: `TODO`
+- [ ] Migrate Git actions UI copy in [apps/web/src/components/GitActionsControl.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/GitActionsControl.tsx)
+ - Status: `TODO`
+- [ ] Migrate branch selector copy in [apps/web/src/components/BranchToolbarBranchSelector.tsx](/Users/buns/.okcode/worktrees/okcode/okcode-587a5d98/apps/web/src/components/BranchToolbarBranchSelector.tsx)
+ - Status: `TODO`
+
+Exit criteria:
+
+- The highest-traffic app chrome and common toasts are localized
+- Remaining untranslated product UI is narrow and intentional
+
+## Phase 4 — Hardening and Verification
+
+Objective:
+Make the rollout safe to maintain and safe to ship repeatedly.
+
+Checklist:
+
+- [ ] Add locale resolution tests
+ - Status: `DONE`
+- [ ] Add app settings default tests for locale
+ - Status: `DONE`
+- [ ] Add message catalog parity tests
+ - Status: `DONE`
+- [ ] Add timestamp formatting tests
+ - Status: `DONE`
+- [ ] Run `bun fmt`
+ - Status: `DONE`
+- [ ] Run `bun lint`
+ - Status: `DONE`
+- [ ] Run `bun typecheck`
+ - Status: `DONE`
+
+Exit criteria:
+
+- Catalog drift is caught by tests
+- Locale behavior is covered by automated checks
+- Required repository quality gates pass
+
+## Shippable Stop
+
+The first shippable stop is:
+
+- Phase 1 complete
+- Phase 2 complete
+- Phase 4 verification complete
+
+Phase 3 can follow later without blocking the first release if the app’s core localized surfaces are already coherent.
+
+## Next Up
+
+Immediate next implementation steps:
+
+1. Migrate root route strings and root toasts.
+2. Migrate Settings and its supporting components.
+3. Migrate Onboarding and Mobile Pairing.
+4. Continue with Phase 2 completion toward the first shippable localized stop.
diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
index 6132277c6..468235269 100644
--- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
+++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
@@ -356,9 +356,7 @@ describe("ProviderCommandReactor", () => {
});
const readModel = await Effect.runPromise(harness.engine.getReadModel());
- const thread = readModel.threads.find(
- (entry) => entry.id === ThreadId.makeUnsafe("thread-1"),
- );
+ const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
expect(thread?.worktreePath).toBeNull();
});
diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
index ce6f53fd8..6ec0b2c17 100644
--- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
+++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
@@ -6,6 +6,7 @@ import {
DEFAULT_GIT_TEXT_GENERATION_MODEL,
EventId,
type OrchestrationEvent,
+ type ProjectId,
type ProviderModelOptions,
ProviderKind,
type ProviderStartOptions,
@@ -145,11 +146,11 @@ function buildGeneratedWorktreeBranchName(raw: string): string {
function resolveSessionCwd(input: {
readonly thread: {
readonly id: ThreadId;
- readonly projectId: string;
+ readonly projectId: ProjectId;
readonly worktreePath: string | null;
};
readonly projects: ReadonlyArray<{
- readonly id: string;
+ readonly id: ProjectId;
readonly workspaceRoot: string;
}>;
}): {
diff --git a/apps/web/package.json b/apps/web/package.json
index 435217dd2..768635a73 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -44,6 +44,7 @@
"oxfmt": "^0.42.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
+ "react-intl": "^10.1.1",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1",
"tailwind-merge": "^3.4.0",
diff --git a/apps/web/src/appSettings.test.ts b/apps/web/src/appSettings.test.ts
index b596e098c..e1fd28d33 100644
--- a/apps/web/src/appSettings.test.ts
+++ b/apps/web/src/appSettings.test.ts
@@ -5,6 +5,7 @@ import {
AppSettingsSchema,
DEFAULT_SIDEBAR_PROJECT_SORT_ORDER,
DEFAULT_SIDEBAR_THREAD_SORT_ORDER,
+ DEFAULT_APP_LOCALE,
DEFAULT_TIMESTAMP_FORMAT,
getAppModelOptions,
getCustomModelOptionsByProvider,
@@ -258,6 +259,7 @@ describe("AppSettingsSchema", () => {
defaultThreadEnvMode: "worktree",
confirmThreadDelete: false,
enableAssistantStreaming: false,
+ locale: DEFAULT_APP_LOCALE,
sidebarProjectSortOrder: DEFAULT_SIDEBAR_PROJECT_SORT_ORDER,
sidebarThreadSortOrder: DEFAULT_SIDEBAR_THREAD_SORT_ORDER,
timestampFormat: DEFAULT_TIMESTAMP_FORMAT,
diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts
index 14f53df75..bd4481aa1 100644
--- a/apps/web/src/appSettings.ts
+++ b/apps/web/src/appSettings.ts
@@ -11,6 +11,7 @@ import {
normalizeModelSlug,
resolveSelectableModel,
} from "@okcode/shared/model";
+import { APP_LOCALE_PREFERENCES } from "./i18n/types";
import { useLocalStorage } from "./hooks/useLocalStorage";
import { EnvMode } from "./components/BranchToolbar.logic";
@@ -21,6 +22,9 @@ export const MAX_CUSTOM_MODEL_LENGTH = 256;
export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"]);
export type TimestampFormat = typeof TimestampFormat.Type;
export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale";
+export const AppLocale = Schema.Literals(APP_LOCALE_PREFERENCES);
+export type AppLocale = typeof AppLocale.Type;
+export const DEFAULT_APP_LOCALE: AppLocale = "system";
export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]);
export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type;
export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at";
@@ -64,6 +68,7 @@ export const AppSettingsSchema = Schema.Struct({
confirmThreadDelete: Schema.Boolean.pipe(withDefaults(() => true)),
diffWordWrap: Schema.Boolean.pipe(withDefaults(() => false)),
enableAssistantStreaming: Schema.Boolean.pipe(withDefaults(() => false)),
+ locale: AppLocale.pipe(withDefaults(() => DEFAULT_APP_LOCALE)),
openLinksExternally: Schema.Boolean.pipe(withDefaults(() => false)),
sidebarProjectSortOrder: SidebarProjectSortOrder.pipe(
withDefaults(() => DEFAULT_SIDEBAR_PROJECT_SORT_ORDER),
diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx
index e7192088f..9b9821f03 100644
--- a/apps/web/src/components/DiffPanel.tsx
+++ b/apps/web/src/components/DiffPanel.tsx
@@ -23,6 +23,7 @@ import {
} from "../lib/diffFileReviewState";
import { resolveDiffThemeName } from "../lib/diffRendering";
import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";
+import { useI18n } from "../i18n/useI18n";
import { useStore } from "../store";
import { useAppSettings } from "../appSettings";
import { formatShortTimestamp } from "../timestampFormat";
@@ -349,6 +350,7 @@ export { DiffWorkerPoolProvider } from "./DiffWorkerPoolProvider";
export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
const navigate = useNavigate();
const { resolvedTheme } = useTheme();
+ const { resolvedLocale } = useI18n();
const { settings } = useAppSettings();
const [diffRenderMode, setDiffRenderMode] = useState
- {formatTimestamp(row.message.createdAt, timestampFormat)}
+ {formatTimestamp(row.message.createdAt, timestampFormat, resolvedLocale)}
okcode://pair?server=…&token=… or a server URL that includes ?token=….",
+ "mobilePairing.pair": "Pair device",
+ "mobilePairing.pairFailed": "Could not pair this device.",
+ "mobilePairing.pairing": "Pairing...",
+ "mobilePairing.placeholder": "okcode://pair?server=…&token=…",
+ "mobilePairing.title": "Pair this device",
+ "onboarding.actions.getStarted": "Get Started",
+ "onboarding.actions.next": "Next",
+ "onboarding.actions.skip": "Skip",
+ "onboarding.progress.label": "Onboarding progress",
+ "onboarding.progress.stepAria": "Go to step {index} of {total}: {title}",
+ "onboarding.step.approvals.description": "You decide what gets executed. The agent asks for your approval before making changes, so nothing happens without your say-so.",
+ "onboarding.step.approvals.detail1": "Approve, request changes, or cancel any proposed action",
+ "onboarding.step.approvals.detail2": "Switch between full-access and approval-required modes per thread",
+ "onboarding.step.approvals.detail3": "Review pending file changes before they're applied",
+ "onboarding.step.approvals.title": "Stay in Control",
+ "onboarding.step.chat.description": "Chat with AI coding agents in real time. Ask questions, request changes, or let the agent drive entire features.",
+ "onboarding.step.chat.detail1": "Choose between multiple providers - Codex and Claude",
+ "onboarding.step.chat.detail2": "Stream responses in real time as the agent works",
+ "onboarding.step.chat.detail3": "Attach images and terminal context directly in your prompts",
+ "onboarding.step.chat.title": "AI-Powered Conversations",
+ "onboarding.step.diff.description": "Inspect every code change the agent makes with a built-in diff viewer before accepting anything.",
+ "onboarding.step.diff.detail1": "Inline and side-by-side diff views with syntax highlighting",
+ "onboarding.step.diff.detail2": "Accept or reject changes per-file with a single click",
+ "onboarding.step.diff.detail3": "Word-level highlighting shows exactly what changed",
+ "onboarding.step.diff.title": "Review Changes Side-by-Side",
+ "onboarding.step.getStarted.description": "You're ready to start building. Here are a few shortcuts to help you move fast.",
+ "onboarding.step.getStarted.detail1": "Press Cmd+N (or Ctrl+N) to create a new thread instantly",
+ "onboarding.step.getStarted.detail2": "Use the sidebar to switch between projects and threads",
+ "onboarding.step.getStarted.detail3": "Open Settings to customize models, themes, and keybindings",
+ "onboarding.step.getStarted.title": "You're All Set!",
+ "onboarding.step.git.description": "Every thread can run in its own git worktree, keeping your main branch safe while the agent experiments freely.",
+ "onboarding.step.git.detail1": "New threads automatically create isolated worktrees",
+ "onboarding.step.git.detail2": "Switch branches, create PRs, and manage worktrees from the toolbar",
+ "onboarding.step.git.detail3": "Link threads to existing pull requests for focused code review",
+ "onboarding.step.git.title": "Built-in Git Workflows",
+ "onboarding.step.plan.description": "Switch to Plan mode and let the agent outline a structured implementation strategy before writing a single line of code.",
+ "onboarding.step.plan.detail1": "Step-by-step plans with status tracking as work progresses",
+ "onboarding.step.plan.detail2": "Review, copy, or export plans as Markdown",
+ "onboarding.step.plan.detail3": "Click \"Implement Plan\" to kick off execution in a new thread",
+ "onboarding.step.plan.title": "AI-Generated Plans",
+ "onboarding.step.terminal.description": "A full terminal lives inside every thread - run commands, see output, and feed context back to the agent.",
+ "onboarding.step.terminal.detail1": "Up to four terminal tabs per thread for parallel workflows",
+ "onboarding.step.terminal.detail2": "Select terminal output and add it directly to your prompt",
+ "onboarding.step.terminal.detail3": "Track running subprocesses with live activity indicators",
+ "onboarding.step.terminal.title": "Integrated Terminal",
+ "onboarding.step.welcome.description": "Your AI-powered coding companion. Let's take a quick tour of the features that will supercharge your workflow.",
+ "onboarding.step.welcome.detail1": "Work alongside AI agents that read, write, and reason about your code",
+ "onboarding.step.welcome.detail2": "Every conversation runs in an isolated git worktree by default",
+ "onboarding.step.welcome.detail3": "This tour takes about a minute - you can skip at any time",
+ "onboarding.step.welcome.title": "Welcome to OK Code",
+ "root.error.detailsUnavailable": "No additional error details are available.",
+ "root.error.hideDetails": "Hide error details",
+ "root.error.showDetails": "Show error details",
+ "root.error.title": "Something went wrong.",
+ "root.error.unexpected": "An unexpected router error occurred.",
+ "root.loading.connectingServer": "Connecting to {appName} server...",
+ "root.loading.restoringMobilePairing": "Restoring mobile pairing...",
+ "root.toast.invalidKeybindings.action": "Open keybindings.json",
+ "root.toast.invalidKeybindings.title": "Invalid keybindings configuration",
+ "root.toast.keybindingsUpdated.description": "Keybindings configuration reloaded successfully.",
+ "root.toast.keybindingsUpdated.title": "Keybindings updated",
+ "root.toast.openKeybindingsFailed.title": "Unable to open keybindings file",
+ "root.toast.unknownFileOpenError": "Unknown error opening file.",
+ "settings.advanced.keybindings.description": "Open the persisted `keybindings.json` file to edit advanced bindings directly.",
+ "settings.advanced.keybindings.noEditors": "No available editors found.",
+ "settings.advanced.keybindings.openFile": "Open file",
+ "settings.advanced.keybindings.opensInPreferredEditor": "Opens in your preferred editor.",
+ "settings.advanced.keybindings.opening": "Opening...",
+ "settings.advanced.keybindings.resolvingPath": "Resolving keybindings path...",
+ "settings.advanced.keybindings.title": "Keybindings",
+ "settings.advanced.providerInstalls.claude.binaryDescription": "Leave blank to use claude from your PATH. Authentication uses claude auth login.",
+ "settings.advanced.providerInstalls.claude.binaryPathLabel": "Anthropic binary path",
+ "settings.advanced.providerInstalls.claude.binaryPlaceholder": "Claude binary path",
+ "settings.advanced.providerInstalls.codex.binaryDescription": "Leave blank to use codex from your PATH. Authentication normally uses codex login unless your Codex config points at a custom model provider.",
+ "settings.advanced.providerInstalls.codex.binaryPathLabel": "Codex binary path",
+ "settings.advanced.providerInstalls.codex.binaryPlaceholder": "Codex binary path",
+ "settings.advanced.providerInstalls.codex.homeDescription": "Optional custom Codex home and config directory.",
+ "settings.advanced.providerInstalls.codex.homePathLabel": "CODEX_HOME path",
+ "settings.advanced.providerInstalls.codex.homePlaceholder": "CODEX_HOME",
+ "settings.advanced.providerInstalls.customBadge": "Custom",
+ "settings.advanced.providerInstalls.description": "Override the CLI binaries and auth homes used for new sessions.",
+ "settings.advanced.providerInstalls.title": "Provider installs",
+ "settings.advanced.version.description": "Current application version.",
+ "settings.advanced.version.title": "Version",
+ "settings.changed.assistantOutput": "Assistant output",
+ "settings.changed.borderRadius": "Border radius",
+ "settings.changed.colorTheme": "Color theme",
+ "settings.changed.customModels": "Custom models",
+ "settings.changed.deleteConfirmation": "Delete confirmation",
+ "settings.changed.diffWordWrap": "Diff line wrapping",
+ "settings.changed.font": "Font",
+ "settings.changed.fontFamily": "Font family",
+ "settings.changed.gitWritingModel": "Git writing model",
+ "settings.changed.language": "Language",
+ "settings.changed.newThreadMode": "New thread mode",
+ "settings.changed.openLinksExternally": "Open links externally",
+ "settings.changed.providerInstalls": "Provider installs",
+ "settings.changed.theme": "Theme",
+ "settings.changed.timeFormat": "Time format",
+ "settings.environment.global.description": "Available to every provider session, terminal, Git command, and health check launched on this machine.",
+ "settings.environment.global.editorDescription": "Global values are encrypted locally and merged into every runtime environment.",
+ "settings.environment.global.empty": "No global variables saved yet.",
+ "settings.environment.global.failed": "Failed to load saved variables: {error}",
+ "settings.environment.global.loading": "Loading saved variables...",
+ "settings.environment.global.save": "Save global",
+ "settings.environment.global.saved": "{count, plural, one {# saved variable} other {# saved variables}}",
+ "settings.environment.global.title": "Global variables",
+ "settings.environment.project.description": "Saved per project and merged on top of the global set when that project launches a provider, terminal, or helper command.",
+ "settings.environment.project.editorDescription": "Open or create a project to edit project variables.",
+ "settings.environment.project.editorDescriptionWithProject": "Project values override global values for {projectName}.",
+ "settings.environment.project.empty": "No project variables saved yet.",
+ "settings.environment.project.loading": "Loading project variables...",
+ "settings.environment.project.noProject": "Open a project to edit project variables.",
+ "settings.environment.project.noProjects": "No projects available.",
+ "settings.environment.project.save": "Save project",
+ "settings.environment.project.saveErrorNoProject": "Select a project before saving project variables.",
+ "settings.environment.project.selectPlaceholder": "Select project",
+ "settings.environment.project.title": "Project variables",
+ "settings.general.assistantOutput.aria": "Stream assistant messages",
+ "settings.general.assistantOutput.description": "Show token-by-token output while a response is in progress.",
+ "settings.general.assistantOutput.title": "Assistant output",
+ "settings.general.borderRadius.aria": "Border radius",
+ "settings.general.borderRadius.description": "Adjust the corner roundness of UI elements.",
+ "settings.general.borderRadius.title": "Border radius",
+ "settings.general.colorTheme.aria": "Color theme",
+ "settings.general.colorTheme.description": "Pick a color palette for light and dark modes.",
+ "settings.general.colorTheme.importAria": "Import custom theme",
+ "settings.general.colorTheme.importTooltip": "Import from tweakcn.com",
+ "settings.general.colorTheme.option.custom": "Custom",
+ "settings.general.colorTheme.option.default": "Default",
+ "settings.general.colorTheme.title": "Color theme",
+ "settings.general.deleteConfirmation.aria": "Confirm thread deletion",
+ "settings.general.deleteConfirmation.description": "Ask before deleting a thread and its chat history.",
+ "settings.general.deleteConfirmation.title": "Delete confirmation",
+ "settings.general.diffWordWrap.aria": "Wrap diff lines by default",
+ "settings.general.diffWordWrap.description": "Set the default wrap state when the diff panel opens. The in-panel wrap toggle only affects the current diff session.",
+ "settings.general.diffWordWrap.title": "Diff line wrapping",
+ "settings.general.font.description": "Choose the typeface for the interface.",
+ "settings.general.font.title": "Font",
+ "settings.general.fontFamilyOverride.aria": "Font family override",
+ "settings.general.fontFamilyOverride.description": "Override the UI font. Use any Google Font name.",
+ "settings.general.fontFamilyOverride.placeholder": "e.g. Inter, sans-serif",
+ "settings.general.fontFamilyOverride.title": "Font family",
+ "settings.general.language.aria": "Language preference",
+ "settings.general.language.description": "Choose the language used for the app interface.",
+ "settings.general.language.option.en": "English",
+ "settings.general.language.option.es": "Español",
+ "settings.general.language.option.fr": "Français",
+ "settings.general.language.option.system": "System default",
+ "settings.general.language.option.zh-CN": "简体中文",
+ "settings.general.language.title": "Language",
+ "settings.general.newThreads.aria": "Default thread mode",
+ "settings.general.newThreads.description": "Pick the default workspace mode for newly created draft threads.",
+ "settings.general.newThreads.title": "New threads",
+ "settings.general.openLinksExternally.aria": "Open links externally",
+ "settings.general.openLinksExternally.description": "Open terminal URLs in your default browser instead of the embedded preview panel.",
+ "settings.general.openLinksExternally.title": "Open links externally",
+ "settings.general.sidebarOpacity.aria": "Sidebar opacity",
+ "settings.general.sidebarOpacity.description": "Adjust the transparency of the side panel and project list.",
+ "settings.general.sidebarOpacity.title": "Sidebar opacity",
+ "settings.general.theme.aria": "Theme preference",
+ "settings.general.theme.description": "Choose how OK Code looks across the app.",
+ "settings.general.theme.title": "Theme",
+ "settings.general.timeFormat.aria": "Timestamp format",
+ "settings.general.timeFormat.description": "System default follows your browser or OS clock preference.",
+ "settings.general.timeFormat.option.12Hour": "12-hour",
+ "settings.general.timeFormat.option.24Hour": "24-hour",
+ "settings.general.timeFormat.option.locale": "System default",
+ "settings.general.timeFormat.title": "Time format",
+ "settings.general.windowOpacity.aria": "Window opacity",
+ "settings.general.windowOpacity.description": "Adjust the transparency of the entire application window.",
+ "settings.general.windowOpacity.title": "Window opacity",
+ "settings.models.customModels.addButton": "Add",
+ "settings.models.customModels.description": "Add custom model slugs for Codex or Anthropic. The chat picker groups models by provider.",
+ "settings.models.customModels.providerAria": "Custom model provider",
+ "settings.models.customModels.removeAria": "Remove {slug}",
+ "settings.models.customModels.showMore": "Show more ({count})",
+ "settings.models.customModels.title": "Custom models",
+ "settings.models.customModels.validation.alreadySaved": "That custom model is already saved.",
+ "settings.models.customModels.validation.builtIn": "That model is already built in.",
+ "settings.models.customModels.validation.enterSlug": "Enter a model slug.",
+ "settings.models.customModels.validation.tooLong": "Model slugs must be {max} characters or less.",
+ "settings.models.gitWritingModel.aria": "Git text generation model",
+ "settings.models.gitWritingModel.description": "Used for generated commit messages, PR titles, and branch names.",
+ "settings.models.gitWritingModel.title": "Git writing model",
+ "settings.reset.aria": "Reset {label} to default",
+ "settings.reset.tooltip": "Reset to default",
+ "settings.restoreDialog.description": "This will reset: {changes}.",
+ "settings.restoreDialog.title": "Restore default settings?",
+ "settings.section.advanced": "Advanced",
+ "settings.section.environment": "Environment",
+ "settings.section.general": "General",
+ "settings.section.models": "Models",
+ "settings.title": "Settings"
+}
diff --git a/apps/web/src/i18n/messages/es.json b/apps/web/src/i18n/messages/es.json
new file mode 100644
index 000000000..e09e58023
--- /dev/null
+++ b/apps/web/src/i18n/messages/es.json
@@ -0,0 +1,260 @@
+{
+ "common.actions.add": "Agregar",
+ "common.actions.back": "Atrás",
+ "common.actions.cancel": "Cancelar",
+ "common.actions.discard": "Descartar",
+ "common.actions.getStarted": "Comenzar",
+ "common.actions.next": "Siguiente",
+ "common.actions.openFile": "Abrir archivo",
+ "common.actions.opening": "Abriendo...",
+ "common.actions.reloadApp": "Recargar app",
+ "common.actions.restoreDefaults": "Restaurar valores predeterminados",
+ "common.actions.showLess": "Mostrar menos",
+ "common.actions.skip": "Omitir",
+ "common.actions.tryAgain": "Reintentar",
+ "common.custom": "Personalizado",
+ "common.dark": "Oscuro",
+ "common.default": "Predeterminado",
+ "common.light": "Claro",
+ "common.local": "Local",
+ "common.newWorktree": "Nuevo worktree",
+ "common.system": "Sistema",
+ "common.systemDefault": "Predeterminado del sistema",
+ "common.unknownError": "Error desconocido",
+ "customThemeDialog.action.apply": "Aplicar tema",
+ "customThemeDialog.action.loading": "Cargando...",
+ "customThemeDialog.action.parse": "Analizar tema",
+ "customThemeDialog.color.accent": "Acento",
+ "customThemeDialog.color.background": "Fondo",
+ "customThemeDialog.color.border": "Borde",
+ "customThemeDialog.color.card": "Tarjeta",
+ "customThemeDialog.color.destructive": "Destructivo",
+ "customThemeDialog.color.muted": "Atenuado",
+ "customThemeDialog.color.primary": "Primario",
+ "customThemeDialog.color.secondary": "Secundario",
+ "customThemeDialog.description": "Pega CSS o una URL de tema de tweakcn.com abajo.",
+ "customThemeDialog.error.parseFailed": "No se pudo analizar el tema.",
+ "customThemeDialog.placeholder": "Pega CSS del tema, JSON o una URL de tweakcn.com...\n\nEjemplo:\nhttps://tweakcn.com/themes/catppuccin\n\nu\n\n:root {\n --background: oklch(1 0 0);\n --primary: oklch(0.58 0.2 277);\n ...\n}",
+ "customThemeDialog.preview.title": "Vista previa",
+ "customThemeDialog.preview.titleWithName": "Vista previa - {name}",
+ "customThemeDialog.title": "Importar tema personalizado",
+ "customThemeDialog.tokens.font": "Fuente: {value}",
+ "customThemeDialog.tokens.mono": "Mono: {value}",
+ "customThemeDialog.tokens.radius": "Radio: {value}",
+ "customThemeDialog.urlBadge": "URL",
+ "customThemeDialog.variablesCount": "{lightCount} variables claras + {darkCount} variables oscuras",
+ "envEditor.action.saving": "Guardando...",
+ "envEditor.blankRowHint": "Las filas vacías se ignoran hasta que contengan una clave.",
+ "envEditor.encryptionNotice": "Los valores se cifran en reposo antes de escribirse en la base de datos local de estado.",
+ "envEditor.keyLabel": "Clave {index}",
+ "envEditor.keyPlaceholder": "API_KEY",
+ "envEditor.removeAria": "Eliminar variable",
+ "envEditor.saveError": "No se pudieron guardar las variables de entorno.",
+ "envEditor.savedCount": "{count}/{max} variables guardadas",
+ "envEditor.scopeHint": "Este valor estará disponible para los lanzamientos dentro del alcance correspondiente.",
+ "envEditor.validation.keyDuplicate": "Este nombre de variable está duplicado.",
+ "envEditor.validation.keyPattern": "Usa letras, números y guiones bajos, comenzando con una letra o un guion bajo.",
+ "envEditor.validation.keyTooLong": "Las claves deben tener como máximo {max} caracteres.",
+ "envEditor.validation.nameRequired": "Se requiere un nombre de variable.",
+ "envEditor.validation.valueTooLong": "Los valores deben tener como máximo {max} caracteres.",
+ "envEditor.valueLabel": "Valor",
+ "envEditor.valuePlaceholder": "valor secreto",
+ "mobilePairing.bridgeUnavailable": "El puente de emparejamiento móvil no está disponible.",
+ "mobilePairing.clearFailed": "No se pudo borrar el emparejamiento.",
+ "mobilePairing.clearSaved": "Borrar emparejamiento guardado",
+ "mobilePairing.description": "Pega un enlace de emparejamiento como okcode://pair?server=…&token=… o una URL del servidor que incluya ?token=….",
+ "mobilePairing.pair": "Emparejar dispositivo",
+ "mobilePairing.pairFailed": "No se pudo emparejar este dispositivo.",
+ "mobilePairing.pairing": "Emparejando...",
+ "mobilePairing.placeholder": "okcode://pair?server=…&token=…",
+ "mobilePairing.title": "Emparejar este dispositivo",
+ "onboarding.actions.getStarted": "Comenzar",
+ "onboarding.actions.next": "Siguiente",
+ "onboarding.actions.skip": "Omitir",
+ "onboarding.progress.label": "Progreso de introducción",
+ "onboarding.progress.stepAria": "Ir al paso {index} de {total}: {title}",
+ "onboarding.step.approvals.description": "Tú decides qué se ejecuta. El agente pide tu aprobación antes de hacer cambios, así que nada ocurre sin tu visto bueno.",
+ "onboarding.step.approvals.detail1": "Aprueba, solicita cambios o cancela cualquier acción propuesta",
+ "onboarding.step.approvals.detail2": "Cambia entre modos de acceso total y aprobación requerida por hilo",
+ "onboarding.step.approvals.detail3": "Revisa los cambios pendientes en archivos antes de aplicarlos",
+ "onboarding.step.approvals.title": "Mantén el control",
+ "onboarding.step.chat.description": "Chatea con agentes de codificación con IA en tiempo real. Haz preguntas, solicita cambios o deja que el agente lleve funciones completas.",
+ "onboarding.step.chat.detail1": "Elige entre varios proveedores: Codex y Claude",
+ "onboarding.step.chat.detail2": "Recibe respuestas en tiempo real mientras el agente trabaja",
+ "onboarding.step.chat.detail3": "Adjunta imágenes y contexto del terminal directamente en tus prompts",
+ "onboarding.step.chat.title": "Conversaciones con IA",
+ "onboarding.step.diff.description": "Inspecciona cada cambio de código que haga el agente con un visor de diff integrado antes de aceptar nada.",
+ "onboarding.step.diff.detail1": "Vistas diff en línea y lado a lado con resaltado de sintaxis",
+ "onboarding.step.diff.detail2": "Acepta o rechaza cambios por archivo con un solo clic",
+ "onboarding.step.diff.detail3": "El resaltado a nivel de palabra muestra exactamente qué cambió",
+ "onboarding.step.diff.title": "Revisa cambios lado a lado",
+ "onboarding.step.getStarted.description": "Ya puedes empezar a construir. Aquí tienes algunos atajos para moverte rápido.",
+ "onboarding.step.getStarted.detail1": "Pulsa Cmd+N (o Ctrl+N) para crear un hilo nuevo al instante",
+ "onboarding.step.getStarted.detail2": "Usa la barra lateral para alternar entre proyectos e hilos",
+ "onboarding.step.getStarted.detail3": "Abre Configuración para personalizar modelos, temas y atajos",
+ "onboarding.step.getStarted.title": "¡Todo listo!",
+ "onboarding.step.git.description": "Cada hilo puede ejecutarse en su propio worktree de git, manteniendo tu rama principal segura mientras el agente experimenta libremente.",
+ "onboarding.step.git.detail1": "Los hilos nuevos crean worktrees aislados automáticamente",
+ "onboarding.step.git.detail2": "Cambia ramas, crea PR y administra worktrees desde la barra de herramientas",
+ "onboarding.step.git.detail3": "Vincula hilos a pull requests existentes para revisiones de código enfocadas",
+ "onboarding.step.git.title": "Flujos de Git integrados",
+ "onboarding.step.plan.description": "Cambia al modo Plan y deja que el agente esboce una estrategia de implementación estructurada antes de escribir una sola línea de código.",
+ "onboarding.step.plan.detail1": "Planes paso a paso con seguimiento de estado mientras avanza el trabajo",
+ "onboarding.step.plan.detail2": "Revisa, copia o exporta planes como Markdown",
+ "onboarding.step.plan.detail3": "Haz clic en \"Implement Plan\" para iniciar la ejecución en un hilo nuevo",
+ "onboarding.step.plan.title": "Planes generados por IA",
+ "onboarding.step.terminal.description": "Un terminal completo vive dentro de cada hilo: ejecuta comandos, ve la salida y devuelve contexto al agente.",
+ "onboarding.step.terminal.detail1": "Hasta cuatro pestañas de terminal por hilo para flujos paralelos",
+ "onboarding.step.terminal.detail2": "Selecciona la salida del terminal y añádela directamente a tu prompt",
+ "onboarding.step.terminal.detail3": "Sigue subprocesos en ejecución con indicadores de actividad en vivo",
+ "onboarding.step.terminal.title": "Terminal integrado",
+ "onboarding.step.welcome.description": "Tu compañero de programación impulsado por IA. Hagamos un recorrido rápido por las funciones que potenciarán tu flujo de trabajo.",
+ "onboarding.step.welcome.detail1": "Trabaja junto a agentes de IA que leen, escriben y razonan sobre tu código",
+ "onboarding.step.welcome.detail2": "Cada conversación se ejecuta por defecto en un worktree de git aislado",
+ "onboarding.step.welcome.detail3": "Este recorrido tarda alrededor de un minuto; puedes omitirlo en cualquier momento",
+ "onboarding.step.welcome.title": "Bienvenido a OK Code",
+ "root.error.detailsUnavailable": "No hay detalles adicionales del error disponibles.",
+ "root.error.hideDetails": "Ocultar detalles del error",
+ "root.error.showDetails": "Mostrar detalles del error",
+ "root.error.title": "Algo salió mal.",
+ "root.error.unexpected": "Se produjo un error inesperado del enrutador.",
+ "root.loading.connectingServer": "Conectando con el servidor de {appName}...",
+ "root.loading.restoringMobilePairing": "Restaurando el emparejamiento móvil...",
+ "root.toast.invalidKeybindings.action": "Abrir keybindings.json",
+ "root.toast.invalidKeybindings.title": "Configuración de atajos inválida",
+ "root.toast.keybindingsUpdated.description": "La configuración de atajos se recargó correctamente.",
+ "root.toast.keybindingsUpdated.title": "Atajos actualizados",
+ "root.toast.openKeybindingsFailed.title": "No se pudo abrir el archivo de atajos",
+ "root.toast.unknownFileOpenError": "Error desconocido al abrir el archivo.",
+ "settings.advanced.keybindings.description": "Abre el archivo persistido `keybindings.json` para editar directamente los atajos avanzados.",
+ "settings.advanced.keybindings.noEditors": "No se encontraron editores disponibles.",
+ "settings.advanced.keybindings.openFile": "Abrir archivo",
+ "settings.advanced.keybindings.opensInPreferredEditor": "Se abre en tu editor preferido.",
+ "settings.advanced.keybindings.opening": "Abriendo...",
+ "settings.advanced.keybindings.resolvingPath": "Resolviendo la ruta de keybindings...",
+ "settings.advanced.keybindings.title": "Atajos",
+ "settings.advanced.providerInstalls.claude.binaryDescription": "Déjalo vacío para usar claude desde tu PATH. La autenticación usa claude auth login.",
+ "settings.advanced.providerInstalls.claude.binaryPathLabel": "Ruta del binario de Anthropic",
+ "settings.advanced.providerInstalls.claude.binaryPlaceholder": "Ruta del binario de Claude",
+ "settings.advanced.providerInstalls.codex.binaryDescription": "Déjalo vacío para usar codex desde tu PATH. La autenticación normalmente usa codex login, salvo que tu configuración de Codex apunte a un proveedor de modelos personalizado.",
+ "settings.advanced.providerInstalls.codex.binaryPathLabel": "Ruta del binario de Codex",
+ "settings.advanced.providerInstalls.codex.binaryPlaceholder": "Ruta del binario de Codex",
+ "settings.advanced.providerInstalls.codex.homeDescription": "Directorio opcional de inicio y configuración personalizado de Codex.",
+ "settings.advanced.providerInstalls.codex.homePathLabel": "Ruta de CODEX_HOME",
+ "settings.advanced.providerInstalls.codex.homePlaceholder": "CODEX_HOME",
+ "settings.advanced.providerInstalls.customBadge": "Personalizado",
+ "settings.advanced.providerInstalls.description": "Sobrescribe los binarios CLI y los homes de autenticación usados para sesiones nuevas.",
+ "settings.advanced.providerInstalls.title": "Instalaciones de proveedores",
+ "settings.advanced.version.description": "Versión actual de la aplicación.",
+ "settings.advanced.version.title": "Versión",
+ "settings.changed.assistantOutput": "Salida del asistente",
+ "settings.changed.borderRadius": "Radio del borde",
+ "settings.changed.colorTheme": "Tema de color",
+ "settings.changed.customModels": "Modelos personalizados",
+ "settings.changed.deleteConfirmation": "Confirmación de eliminación",
+ "settings.changed.diffWordWrap": "Ajuste de líneas en diff",
+ "settings.changed.font": "Fuente",
+ "settings.changed.fontFamily": "Familia tipográfica",
+ "settings.changed.gitWritingModel": "Modelo de escritura de Git",
+ "settings.changed.language": "Idioma",
+ "settings.changed.newThreadMode": "Modo de hilo nuevo",
+ "settings.changed.openLinksExternally": "Abrir enlaces externamente",
+ "settings.changed.providerInstalls": "Instalaciones de proveedores",
+ "settings.changed.theme": "Tema",
+ "settings.changed.timeFormat": "Formato de hora",
+ "settings.environment.global.description": "Disponible para cada sesión de proveedor, terminal, comando Git y verificación de salud lanzados en esta máquina.",
+ "settings.environment.global.editorDescription": "Los valores globales se cifran localmente y se combinan en cada entorno de ejecución.",
+ "settings.environment.global.empty": "Aún no hay variables globales guardadas.",
+ "settings.environment.global.failed": "No se pudieron cargar las variables guardadas: {error}",
+ "settings.environment.global.loading": "Cargando variables guardadas...",
+ "settings.environment.global.save": "Guardar global",
+ "settings.environment.global.saved": "{count, plural, one {# variable guardada} other {# variables guardadas}}",
+ "settings.environment.global.title": "Variables globales",
+ "settings.environment.project.description": "Se guardan por proyecto y se combinan sobre el conjunto global cuando ese proyecto inicia un proveedor, terminal o comando auxiliar.",
+ "settings.environment.project.editorDescription": "Abre o crea un proyecto para editar las variables del proyecto.",
+ "settings.environment.project.editorDescriptionWithProject": "Los valores del proyecto sobrescriben los globales para {projectName}.",
+ "settings.environment.project.empty": "Aún no hay variables del proyecto guardadas.",
+ "settings.environment.project.loading": "Cargando variables del proyecto...",
+ "settings.environment.project.noProject": "Abre un proyecto para editar las variables del proyecto.",
+ "settings.environment.project.noProjects": "No hay proyectos disponibles.",
+ "settings.environment.project.save": "Guardar proyecto",
+ "settings.environment.project.saveErrorNoProject": "Selecciona un proyecto antes de guardar las variables del proyecto.",
+ "settings.environment.project.selectPlaceholder": "Seleccionar proyecto",
+ "settings.environment.project.title": "Variables del proyecto",
+ "settings.general.assistantOutput.aria": "Transmitir mensajes del asistente",
+ "settings.general.assistantOutput.description": "Muestra salida token por token mientras una respuesta está en progreso.",
+ "settings.general.assistantOutput.title": "Salida del asistente",
+ "settings.general.borderRadius.aria": "Radio del borde",
+ "settings.general.borderRadius.description": "Ajusta la redondez de las esquinas de los elementos de UI.",
+ "settings.general.borderRadius.title": "Radio del borde",
+ "settings.general.colorTheme.aria": "Tema de color",
+ "settings.general.colorTheme.description": "Elige una paleta de colores para los modos claro y oscuro.",
+ "settings.general.colorTheme.importAria": "Importar tema personalizado",
+ "settings.general.colorTheme.importTooltip": "Importar desde tweakcn.com",
+ "settings.general.colorTheme.option.custom": "Personalizado",
+ "settings.general.colorTheme.option.default": "Predeterminado",
+ "settings.general.colorTheme.title": "Tema de color",
+ "settings.general.deleteConfirmation.aria": "Confirmar eliminación del hilo",
+ "settings.general.deleteConfirmation.description": "Pregunta antes de eliminar un hilo y su historial de chat.",
+ "settings.general.deleteConfirmation.title": "Confirmación de eliminación",
+ "settings.general.diffWordWrap.aria": "Ajustar líneas del diff por defecto",
+ "settings.general.diffWordWrap.description": "Define el estado de ajuste predeterminado cuando se abre el panel de diff. El control dentro del panel solo afecta la sesión actual del diff.",
+ "settings.general.diffWordWrap.title": "Ajuste de líneas en diff",
+ "settings.general.font.description": "Elige la tipografía de la interfaz.",
+ "settings.general.font.title": "Fuente",
+ "settings.general.fontFamilyOverride.aria": "Sobrescritura de familia tipográfica",
+ "settings.general.fontFamilyOverride.description": "Sobrescribe la fuente de la UI. Usa cualquier nombre de Google Font.",
+ "settings.general.fontFamilyOverride.placeholder": "p. ej. Inter, sans-serif",
+ "settings.general.fontFamilyOverride.title": "Familia tipográfica",
+ "settings.general.language.aria": "Preferencia de idioma",
+ "settings.general.language.description": "Elige el idioma usado para la interfaz de la app.",
+ "settings.general.language.option.en": "English",
+ "settings.general.language.option.es": "Español",
+ "settings.general.language.option.fr": "Français",
+ "settings.general.language.option.system": "Predeterminado del sistema",
+ "settings.general.language.option.zh-CN": "简体中文",
+ "settings.general.language.title": "Idioma",
+ "settings.general.newThreads.aria": "Modo predeterminado del hilo",
+ "settings.general.newThreads.description": "Elige el modo de espacio de trabajo predeterminado para los nuevos borradores de hilos.",
+ "settings.general.newThreads.title": "Hilos nuevos",
+ "settings.general.openLinksExternally.aria": "Abrir enlaces externamente",
+ "settings.general.openLinksExternally.description": "Abre las URL del terminal en tu navegador predeterminado en lugar del panel de vista previa integrado.",
+ "settings.general.openLinksExternally.title": "Abrir enlaces externamente",
+ "settings.general.sidebarOpacity.aria": "Opacidad de la barra lateral",
+ "settings.general.sidebarOpacity.description": "Ajusta la transparencia del panel lateral y la lista de proyectos.",
+ "settings.general.sidebarOpacity.title": "Opacidad de la barra lateral",
+ "settings.general.theme.aria": "Preferencia de tema",
+ "settings.general.theme.description": "Elige cómo se ve OK Code en toda la app.",
+ "settings.general.theme.title": "Tema",
+ "settings.general.timeFormat.aria": "Formato de marca de tiempo",
+ "settings.general.timeFormat.description": "El valor predeterminado del sistema sigue la preferencia de reloj de tu navegador o sistema operativo.",
+ "settings.general.timeFormat.option.12Hour": "12 horas",
+ "settings.general.timeFormat.option.24Hour": "24 horas",
+ "settings.general.timeFormat.option.locale": "Predeterminado del sistema",
+ "settings.general.timeFormat.title": "Formato de hora",
+ "settings.general.windowOpacity.aria": "Opacidad de la ventana",
+ "settings.general.windowOpacity.description": "Ajusta la transparencia de toda la ventana de la aplicación.",
+ "settings.general.windowOpacity.title": "Opacidad de la ventana",
+ "settings.models.customModels.addButton": "Agregar",
+ "settings.models.customModels.description": "Añade slugs de modelos personalizados para Codex o Anthropic. El selector de chat agrupa los modelos por proveedor.",
+ "settings.models.customModels.providerAria": "Proveedor de modelo personalizado",
+ "settings.models.customModels.removeAria": "Eliminar {slug}",
+ "settings.models.customModels.showMore": "Mostrar más ({count})",
+ "settings.models.customModels.title": "Modelos personalizados",
+ "settings.models.customModels.validation.alreadySaved": "Ese modelo personalizado ya está guardado.",
+ "settings.models.customModels.validation.builtIn": "Ese modelo ya está integrado.",
+ "settings.models.customModels.validation.enterSlug": "Introduce un slug de modelo.",
+ "settings.models.customModels.validation.tooLong": "Los slugs del modelo deben tener como máximo {max} caracteres.",
+ "settings.models.gitWritingModel.aria": "Modelo de generación de texto para Git",
+ "settings.models.gitWritingModel.description": "Se usa para mensajes de commit, títulos de PR y nombres de ramas generados.",
+ "settings.models.gitWritingModel.title": "Modelo de escritura de Git",
+ "settings.reset.aria": "Restablecer {label} al valor predeterminado",
+ "settings.reset.tooltip": "Restablecer al valor predeterminado",
+ "settings.restoreDialog.description": "Esto restablecerá: {changes}.",
+ "settings.restoreDialog.title": "¿Restaurar la configuración predeterminada?",
+ "settings.section.advanced": "Avanzado",
+ "settings.section.environment": "Entorno",
+ "settings.section.general": "General",
+ "settings.section.models": "Modelos",
+ "settings.title": "Configuración"
+}
diff --git a/apps/web/src/i18n/messages/fr.json b/apps/web/src/i18n/messages/fr.json
new file mode 100644
index 000000000..b93be2358
--- /dev/null
+++ b/apps/web/src/i18n/messages/fr.json
@@ -0,0 +1,260 @@
+{
+ "common.actions.add": "Ajouter",
+ "common.actions.back": "Retour",
+ "common.actions.cancel": "Annuler",
+ "common.actions.discard": "Ignorer",
+ "common.actions.getStarted": "Commencer",
+ "common.actions.next": "Suivant",
+ "common.actions.openFile": "Ouvrir le fichier",
+ "common.actions.opening": "Ouverture...",
+ "common.actions.reloadApp": "Recharger l'application",
+ "common.actions.restoreDefaults": "Restaurer les valeurs par défaut",
+ "common.actions.showLess": "Afficher moins",
+ "common.actions.skip": "Ignorer",
+ "common.actions.tryAgain": "Réessayer",
+ "common.custom": "Personnalisé",
+ "common.dark": "Sombre",
+ "common.default": "Par défaut",
+ "common.light": "Clair",
+ "common.local": "Local",
+ "common.newWorktree": "Nouveau worktree",
+ "common.system": "Système",
+ "common.systemDefault": "Par défaut du système",
+ "common.unknownError": "Erreur inconnue",
+ "customThemeDialog.action.apply": "Appliquer le thème",
+ "customThemeDialog.action.loading": "Chargement...",
+ "customThemeDialog.action.parse": "Analyser le thème",
+ "customThemeDialog.color.accent": "Accent",
+ "customThemeDialog.color.background": "Arrière-plan",
+ "customThemeDialog.color.border": "Bordure",
+ "customThemeDialog.color.card": "Carte",
+ "customThemeDialog.color.destructive": "Destructif",
+ "customThemeDialog.color.muted": "Atténué",
+ "customThemeDialog.color.primary": "Primaire",
+ "customThemeDialog.color.secondary": "Secondaire",
+ "customThemeDialog.description": "Collez du CSS ou une URL de thème tweakcn.com ci-dessous.",
+ "customThemeDialog.error.parseFailed": "Impossible d'analyser le thème.",
+ "customThemeDialog.placeholder": "Collez le CSS du thème, du JSON ou une URL tweakcn.com...\n\nExemple :\nhttps://tweakcn.com/themes/catppuccin\n\nou\n\n:root {\n --background: oklch(1 0 0);\n --primary: oklch(0.58 0.2 277);\n ...\n}",
+ "customThemeDialog.preview.title": "Aperçu",
+ "customThemeDialog.preview.titleWithName": "Aperçu - {name}",
+ "customThemeDialog.title": "Importer un thème personnalisé",
+ "customThemeDialog.tokens.font": "Police : {value}",
+ "customThemeDialog.tokens.mono": "Mono : {value}",
+ "customThemeDialog.tokens.radius": "Rayon : {value}",
+ "customThemeDialog.urlBadge": "URL",
+ "customThemeDialog.variablesCount": "{lightCount} variables claires + {darkCount} variables sombres",
+ "envEditor.action.saving": "Enregistrement...",
+ "envEditor.blankRowHint": "Les lignes vides sont ignorées tant qu'elles ne contiennent pas de clé.",
+ "envEditor.encryptionNotice": "Les valeurs sont chiffrées au repos avant d'être écrites dans la base d'état locale.",
+ "envEditor.keyLabel": "Clé {index}",
+ "envEditor.keyPlaceholder": "API_KEY",
+ "envEditor.removeAria": "Supprimer la variable",
+ "envEditor.saveError": "Impossible d'enregistrer les variables d'environnement.",
+ "envEditor.savedCount": "{count}/{max} variables enregistrées",
+ "envEditor.scopeHint": "Cette valeur sera disponible pour les lancements dans la portée correspondante.",
+ "envEditor.validation.keyDuplicate": "Ce nom de variable est dupliqué.",
+ "envEditor.validation.keyPattern": "Utilisez des lettres, des chiffres et des traits de soulignement, en commençant par une lettre ou un trait de soulignement.",
+ "envEditor.validation.keyTooLong": "Les clés doivent contenir au maximum {max} caractères.",
+ "envEditor.validation.nameRequired": "Un nom de variable est requis.",
+ "envEditor.validation.valueTooLong": "Les valeurs doivent contenir au maximum {max} caractères.",
+ "envEditor.valueLabel": "Valeur",
+ "envEditor.valuePlaceholder": "valeur secrète",
+ "mobilePairing.bridgeUnavailable": "Le pont d'appairage mobile n'est pas disponible.",
+ "mobilePairing.clearFailed": "Impossible d'effacer l'appairage.",
+ "mobilePairing.clearSaved": "Effacer l'appairage enregistré",
+ "mobilePairing.description": "Collez un lien d'appairage comme okcode://pair?server=…&token=… ou une URL de serveur qui inclut ?token=….",
+ "mobilePairing.pair": "Appairer l'appareil",
+ "mobilePairing.pairFailed": "Impossible d'appairer cet appareil.",
+ "mobilePairing.pairing": "Appairage...",
+ "mobilePairing.placeholder": "okcode://pair?server=…&token=…",
+ "mobilePairing.title": "Appairer cet appareil",
+ "onboarding.actions.getStarted": "Commencer",
+ "onboarding.actions.next": "Suivant",
+ "onboarding.actions.skip": "Ignorer",
+ "onboarding.progress.label": "Progression de l'intégration",
+ "onboarding.progress.stepAria": "Aller à l'étape {index} sur {total} : {title}",
+ "onboarding.step.approvals.description": "Vous décidez de ce qui est exécuté. L'agent demande votre approbation avant d'effectuer des changements, donc rien ne se passe sans votre accord.",
+ "onboarding.step.approvals.detail1": "Approuvez, demandez des changements ou annulez toute action proposée",
+ "onboarding.step.approvals.detail2": "Passez entre les modes accès complet et approbation requise par fil",
+ "onboarding.step.approvals.detail3": "Examinez les modifications de fichiers en attente avant leur application",
+ "onboarding.step.approvals.title": "Gardez le contrôle",
+ "onboarding.step.chat.description": "Discutez avec des agents de codage IA en temps réel. Posez des questions, demandez des changements ou laissez l'agent piloter des fonctionnalités entières.",
+ "onboarding.step.chat.detail1": "Choisissez entre plusieurs fournisseurs : Codex et Claude",
+ "onboarding.step.chat.detail2": "Diffusez les réponses en temps réel pendant que l'agent travaille",
+ "onboarding.step.chat.detail3": "Joignez des images et le contexte du terminal directement dans vos prompts",
+ "onboarding.step.chat.title": "Conversations pilotées par l'IA",
+ "onboarding.step.diff.description": "Inspectez chaque modification de code effectuée par l'agent avec un visualiseur de diff intégré avant d'accepter quoi que ce soit.",
+ "onboarding.step.diff.detail1": "Vues diff en ligne et côte à côte avec coloration syntaxique",
+ "onboarding.step.diff.detail2": "Acceptez ou rejetez les changements fichier par fichier en un clic",
+ "onboarding.step.diff.detail3": "Le surlignage au niveau des mots montre exactement ce qui a changé",
+ "onboarding.step.diff.title": "Examinez les changements côte à côte",
+ "onboarding.step.getStarted.description": "Vous êtes prêt à démarrer. Voici quelques raccourcis pour aller vite.",
+ "onboarding.step.getStarted.detail1": "Appuyez sur Cmd+N (ou Ctrl+N) pour créer instantanément un nouveau fil",
+ "onboarding.step.getStarted.detail2": "Utilisez la barre latérale pour passer d'un projet ou d'un fil à l'autre",
+ "onboarding.step.getStarted.detail3": "Ouvrez Paramètres pour personnaliser les modèles, thèmes et raccourcis",
+ "onboarding.step.getStarted.title": "Vous êtes prêt !",
+ "onboarding.step.git.description": "Chaque fil peut fonctionner dans son propre worktree git, ce qui protège votre branche principale pendant que l'agent expérimente librement.",
+ "onboarding.step.git.detail1": "Les nouveaux fils créent automatiquement des worktrees isolés",
+ "onboarding.step.git.detail2": "Changez de branche, créez des PR et gérez les worktrees depuis la barre d'outils",
+ "onboarding.step.git.detail3": "Liez des fils à des pull requests existantes pour une revue de code ciblée",
+ "onboarding.step.git.title": "Workflows Git intégrés",
+ "onboarding.step.plan.description": "Passez en mode Plan et laissez l'agent définir une stratégie d'implémentation structurée avant d'écrire une seule ligne de code.",
+ "onboarding.step.plan.detail1": "Plans étape par étape avec suivi d'état à mesure que le travail progresse",
+ "onboarding.step.plan.detail2": "Examinez, copiez ou exportez les plans en Markdown",
+ "onboarding.step.plan.detail3": "Cliquez sur \"Implement Plan\" pour lancer l'exécution dans un nouveau fil",
+ "onboarding.step.plan.title": "Plans générés par l'IA",
+ "onboarding.step.terminal.description": "Un terminal complet vit dans chaque fil : exécutez des commandes, voyez la sortie et renvoyez du contexte à l'agent.",
+ "onboarding.step.terminal.detail1": "Jusqu'à quatre onglets de terminal par fil pour des workflows parallèles",
+ "onboarding.step.terminal.detail2": "Sélectionnez la sortie du terminal et ajoutez-la directement à votre prompt",
+ "onboarding.step.terminal.detail3": "Suivez les sous-processus actifs avec des indicateurs en direct",
+ "onboarding.step.terminal.title": "Terminal intégré",
+ "onboarding.step.welcome.description": "Votre compagnon de codage propulsé par l'IA. Faisons un rapide tour des fonctionnalités qui vont accélérer votre workflow.",
+ "onboarding.step.welcome.detail1": "Travaillez aux côtés d'agents IA qui lisent, écrivent et raisonnent sur votre code",
+ "onboarding.step.welcome.detail2": "Chaque conversation s'exécute par défaut dans un worktree git isolé",
+ "onboarding.step.welcome.detail3": "Cette visite dure environ une minute ; vous pouvez la passer à tout moment",
+ "onboarding.step.welcome.title": "Bienvenue dans OK Code",
+ "root.error.detailsUnavailable": "Aucun détail d'erreur supplémentaire n'est disponible.",
+ "root.error.hideDetails": "Masquer les détails de l'erreur",
+ "root.error.showDetails": "Afficher les détails de l'erreur",
+ "root.error.title": "Une erreur est survenue.",
+ "root.error.unexpected": "Une erreur de routeur inattendue s'est produite.",
+ "root.loading.connectingServer": "Connexion au serveur {appName}...",
+ "root.loading.restoringMobilePairing": "Restauration de l'appairage mobile...",
+ "root.toast.invalidKeybindings.action": "Ouvrir keybindings.json",
+ "root.toast.invalidKeybindings.title": "Configuration de raccourcis invalide",
+ "root.toast.keybindingsUpdated.description": "La configuration des raccourcis a bien été rechargée.",
+ "root.toast.keybindingsUpdated.title": "Raccourcis mis à jour",
+ "root.toast.openKeybindingsFailed.title": "Impossible d'ouvrir le fichier des raccourcis",
+ "root.toast.unknownFileOpenError": "Erreur inconnue lors de l'ouverture du fichier.",
+ "settings.advanced.keybindings.description": "Ouvrez le fichier `keybindings.json` persisté pour modifier directement les raccourcis avancés.",
+ "settings.advanced.keybindings.noEditors": "Aucun éditeur disponible.",
+ "settings.advanced.keybindings.openFile": "Ouvrir le fichier",
+ "settings.advanced.keybindings.opensInPreferredEditor": "S'ouvre dans votre éditeur préféré.",
+ "settings.advanced.keybindings.opening": "Ouverture...",
+ "settings.advanced.keybindings.resolvingPath": "Résolution du chemin de keybindings...",
+ "settings.advanced.keybindings.title": "Raccourcis",
+ "settings.advanced.providerInstalls.claude.binaryDescription": "Laissez vide pour utiliser claude depuis votre PATH. L'authentification utilise claude auth login.",
+ "settings.advanced.providerInstalls.claude.binaryPathLabel": "Chemin du binaire Anthropic",
+ "settings.advanced.providerInstalls.claude.binaryPlaceholder": "Chemin du binaire Claude",
+ "settings.advanced.providerInstalls.codex.binaryDescription": "Laissez vide pour utiliser codex depuis votre PATH. L'authentification utilise normalement codex login, sauf si votre configuration Codex pointe vers un fournisseur de modèle personnalisé.",
+ "settings.advanced.providerInstalls.codex.binaryPathLabel": "Chemin du binaire Codex",
+ "settings.advanced.providerInstalls.codex.binaryPlaceholder": "Chemin du binaire Codex",
+ "settings.advanced.providerInstalls.codex.homeDescription": "Répertoire de configuration et d'accueil Codex personnalisé facultatif.",
+ "settings.advanced.providerInstalls.codex.homePathLabel": "Chemin CODEX_HOME",
+ "settings.advanced.providerInstalls.codex.homePlaceholder": "CODEX_HOME",
+ "settings.advanced.providerInstalls.customBadge": "Personnalisé",
+ "settings.advanced.providerInstalls.description": "Remplacez les binaires CLI et les répertoires d'authentification utilisés pour les nouvelles sessions.",
+ "settings.advanced.providerInstalls.title": "Installations des fournisseurs",
+ "settings.advanced.version.description": "Version actuelle de l'application.",
+ "settings.advanced.version.title": "Version",
+ "settings.changed.assistantOutput": "Sortie de l'assistant",
+ "settings.changed.borderRadius": "Rayon des bordures",
+ "settings.changed.colorTheme": "Thème de couleur",
+ "settings.changed.customModels": "Modèles personnalisés",
+ "settings.changed.deleteConfirmation": "Confirmation de suppression",
+ "settings.changed.diffWordWrap": "Retour à la ligne du diff",
+ "settings.changed.font": "Police",
+ "settings.changed.fontFamily": "Famille de police",
+ "settings.changed.gitWritingModel": "Modèle d'écriture Git",
+ "settings.changed.language": "Langue",
+ "settings.changed.newThreadMode": "Mode de nouveau fil",
+ "settings.changed.openLinksExternally": "Ouvrir les liens à l'extérieur",
+ "settings.changed.providerInstalls": "Installations des fournisseurs",
+ "settings.changed.theme": "Thème",
+ "settings.changed.timeFormat": "Format de l'heure",
+ "settings.environment.global.description": "Disponible pour chaque session de fournisseur, terminal, commande Git et vérification d'état lancés sur cette machine.",
+ "settings.environment.global.editorDescription": "Les valeurs globales sont chiffrées localement et fusionnées dans chaque environnement d'exécution.",
+ "settings.environment.global.empty": "Aucune variable globale enregistrée pour l'instant.",
+ "settings.environment.global.failed": "Impossible de charger les variables enregistrées : {error}",
+ "settings.environment.global.loading": "Chargement des variables enregistrées...",
+ "settings.environment.global.save": "Enregistrer global",
+ "settings.environment.global.saved": "{count, plural, one {# variable enregistrée} other {# variables enregistrées}}",
+ "settings.environment.global.title": "Variables globales",
+ "settings.environment.project.description": "Enregistrées par projet et fusionnées par-dessus l'ensemble global lorsque ce projet lance un fournisseur, un terminal ou une commande utilitaire.",
+ "settings.environment.project.editorDescription": "Ouvrez ou créez un projet pour modifier les variables du projet.",
+ "settings.environment.project.editorDescriptionWithProject": "Les valeurs du projet remplacent les valeurs globales pour {projectName}.",
+ "settings.environment.project.empty": "Aucune variable de projet enregistrée pour l'instant.",
+ "settings.environment.project.loading": "Chargement des variables du projet...",
+ "settings.environment.project.noProject": "Ouvrez un projet pour modifier les variables du projet.",
+ "settings.environment.project.noProjects": "Aucun projet disponible.",
+ "settings.environment.project.save": "Enregistrer le projet",
+ "settings.environment.project.saveErrorNoProject": "Sélectionnez un projet avant d'enregistrer les variables du projet.",
+ "settings.environment.project.selectPlaceholder": "Sélectionner un projet",
+ "settings.environment.project.title": "Variables du projet",
+ "settings.general.assistantOutput.aria": "Diffuser les messages de l'assistant",
+ "settings.general.assistantOutput.description": "Affiche la sortie jeton par jeton pendant qu'une réponse est en cours.",
+ "settings.general.assistantOutput.title": "Sortie de l'assistant",
+ "settings.general.borderRadius.aria": "Rayon des bordures",
+ "settings.general.borderRadius.description": "Ajustez l'arrondi des coins des éléments d'interface.",
+ "settings.general.borderRadius.title": "Rayon des bordures",
+ "settings.general.colorTheme.aria": "Thème de couleur",
+ "settings.general.colorTheme.description": "Choisissez une palette de couleurs pour les modes clair et sombre.",
+ "settings.general.colorTheme.importAria": "Importer un thème personnalisé",
+ "settings.general.colorTheme.importTooltip": "Importer depuis tweakcn.com",
+ "settings.general.colorTheme.option.custom": "Personnalisé",
+ "settings.general.colorTheme.option.default": "Par défaut",
+ "settings.general.colorTheme.title": "Thème de couleur",
+ "settings.general.deleteConfirmation.aria": "Confirmer la suppression du fil",
+ "settings.general.deleteConfirmation.description": "Demander confirmation avant de supprimer un fil et son historique de discussion.",
+ "settings.general.deleteConfirmation.title": "Confirmation de suppression",
+ "settings.general.diffWordWrap.aria": "Activer le retour à la ligne du diff par défaut",
+ "settings.general.diffWordWrap.description": "Définit l'état de retour à la ligne par défaut à l'ouverture du panneau diff. Le bouton dans le panneau n'affecte que la session diff en cours.",
+ "settings.general.diffWordWrap.title": "Retour à la ligne du diff",
+ "settings.general.font.description": "Choisissez la police de l'interface.",
+ "settings.general.font.title": "Police",
+ "settings.general.fontFamilyOverride.aria": "Remplacement de la famille de police",
+ "settings.general.fontFamilyOverride.description": "Remplacez la police de l'interface. Utilisez n'importe quel nom de Google Font.",
+ "settings.general.fontFamilyOverride.placeholder": "ex. Inter, sans-serif",
+ "settings.general.fontFamilyOverride.title": "Famille de police",
+ "settings.general.language.aria": "Préférence de langue",
+ "settings.general.language.description": "Choisissez la langue utilisée pour l'interface de l'application.",
+ "settings.general.language.option.en": "English",
+ "settings.general.language.option.es": "Español",
+ "settings.general.language.option.fr": "Français",
+ "settings.general.language.option.system": "Par défaut du système",
+ "settings.general.language.option.zh-CN": "简体中文",
+ "settings.general.language.title": "Langue",
+ "settings.general.newThreads.aria": "Mode de fil par défaut",
+ "settings.general.newThreads.description": "Choisissez le mode d'espace de travail par défaut pour les nouveaux brouillons de fil.",
+ "settings.general.newThreads.title": "Nouveaux fils",
+ "settings.general.openLinksExternally.aria": "Ouvrir les liens à l'extérieur",
+ "settings.general.openLinksExternally.description": "Ouvrez les URL du terminal dans votre navigateur par défaut au lieu du panneau d'aperçu intégré.",
+ "settings.general.openLinksExternally.title": "Ouvrir les liens à l'extérieur",
+ "settings.general.sidebarOpacity.aria": "Opacité de la barre latérale",
+ "settings.general.sidebarOpacity.description": "Ajustez la transparence du panneau latéral et de la liste des projets.",
+ "settings.general.sidebarOpacity.title": "Opacité de la barre latérale",
+ "settings.general.theme.aria": "Préférence de thème",
+ "settings.general.theme.description": "Choisissez l'apparence d'OK Code dans toute l'application.",
+ "settings.general.theme.title": "Thème",
+ "settings.general.timeFormat.aria": "Format d'horodatage",
+ "settings.general.timeFormat.description": "Le mode système suit la préférence d'horloge de votre navigateur ou système d'exploitation.",
+ "settings.general.timeFormat.option.12Hour": "12 heures",
+ "settings.general.timeFormat.option.24Hour": "24 heures",
+ "settings.general.timeFormat.option.locale": "Par défaut du système",
+ "settings.general.timeFormat.title": "Format de l'heure",
+ "settings.general.windowOpacity.aria": "Opacité de la fenêtre",
+ "settings.general.windowOpacity.description": "Ajustez la transparence de toute la fenêtre de l'application.",
+ "settings.general.windowOpacity.title": "Opacité de la fenêtre",
+ "settings.models.customModels.addButton": "Ajouter",
+ "settings.models.customModels.description": "Ajoutez des slugs de modèles personnalisés pour Codex ou Anthropic. Le sélecteur de chat regroupe les modèles par fournisseur.",
+ "settings.models.customModels.providerAria": "Fournisseur de modèle personnalisé",
+ "settings.models.customModels.removeAria": "Supprimer {slug}",
+ "settings.models.customModels.showMore": "Afficher plus ({count})",
+ "settings.models.customModels.title": "Modèles personnalisés",
+ "settings.models.customModels.validation.alreadySaved": "Ce modèle personnalisé est déjà enregistré.",
+ "settings.models.customModels.validation.builtIn": "Ce modèle est déjà intégré.",
+ "settings.models.customModels.validation.enterSlug": "Saisissez un slug de modèle.",
+ "settings.models.customModels.validation.tooLong": "Les slugs de modèle doivent contenir au maximum {max} caractères.",
+ "settings.models.gitWritingModel.aria": "Modèle de génération de texte Git",
+ "settings.models.gitWritingModel.description": "Utilisé pour générer les messages de commit, titres de PR et noms de branches.",
+ "settings.models.gitWritingModel.title": "Modèle d'écriture Git",
+ "settings.reset.aria": "Réinitialiser {label} à la valeur par défaut",
+ "settings.reset.tooltip": "Réinitialiser à la valeur par défaut",
+ "settings.restoreDialog.description": "Cela réinitialisera : {changes}.",
+ "settings.restoreDialog.title": "Restaurer les paramètres par défaut ?",
+ "settings.section.advanced": "Avancé",
+ "settings.section.environment": "Environnement",
+ "settings.section.general": "Général",
+ "settings.section.models": "Modèles",
+ "settings.title": "Paramètres"
+}
diff --git a/apps/web/src/i18n/messages/zh-CN.json b/apps/web/src/i18n/messages/zh-CN.json
new file mode 100644
index 000000000..c5fe781dd
--- /dev/null
+++ b/apps/web/src/i18n/messages/zh-CN.json
@@ -0,0 +1,260 @@
+{
+ "common.actions.add": "添加",
+ "common.actions.back": "返回",
+ "common.actions.cancel": "取消",
+ "common.actions.discard": "放弃",
+ "common.actions.getStarted": "开始使用",
+ "common.actions.next": "下一步",
+ "common.actions.openFile": "打开文件",
+ "common.actions.opening": "打开中...",
+ "common.actions.reloadApp": "重新加载应用",
+ "common.actions.restoreDefaults": "恢复默认值",
+ "common.actions.showLess": "收起",
+ "common.actions.skip": "跳过",
+ "common.actions.tryAgain": "重试",
+ "common.custom": "自定义",
+ "common.dark": "深色",
+ "common.default": "默认",
+ "common.light": "浅色",
+ "common.local": "本地",
+ "common.newWorktree": "新建 worktree",
+ "common.system": "系统",
+ "common.systemDefault": "系统默认",
+ "common.unknownError": "未知错误",
+ "customThemeDialog.action.apply": "应用主题",
+ "customThemeDialog.action.loading": "加载中...",
+ "customThemeDialog.action.parse": "解析主题",
+ "customThemeDialog.color.accent": "强调色",
+ "customThemeDialog.color.background": "背景",
+ "customThemeDialog.color.border": "边框",
+ "customThemeDialog.color.card": "卡片",
+ "customThemeDialog.color.destructive": "危险",
+ "customThemeDialog.color.muted": "弱化",
+ "customThemeDialog.color.primary": "主色",
+ "customThemeDialog.color.secondary": "次色",
+ "customThemeDialog.description": "在下方粘贴 CSS 或 tweakcn.com 主题链接。",
+ "customThemeDialog.error.parseFailed": "无法解析主题。",
+ "customThemeDialog.placeholder": "粘贴主题 CSS、JSON 或 tweakcn.com 链接...\n\n示例:\nhttps://tweakcn.com/themes/catppuccin\n\n或\n\n:root {\n --background: oklch(1 0 0);\n --primary: oklch(0.58 0.2 277);\n ...\n}",
+ "customThemeDialog.preview.title": "预览",
+ "customThemeDialog.preview.titleWithName": "预览 - {name}",
+ "customThemeDialog.title": "导入自定义主题",
+ "customThemeDialog.tokens.font": "字体:{value}",
+ "customThemeDialog.tokens.mono": "等宽:{value}",
+ "customThemeDialog.tokens.radius": "圆角:{value}",
+ "customThemeDialog.urlBadge": "链接",
+ "customThemeDialog.variablesCount": "{lightCount} 个浅色变量 + {darkCount} 个深色变量",
+ "envEditor.action.saving": "保存中...",
+ "envEditor.blankRowHint": "空行在包含键名之前会被忽略。",
+ "envEditor.encryptionNotice": "这些值会在写入本地状态数据库之前以静态加密方式保存。",
+ "envEditor.keyLabel": "键 {index}",
+ "envEditor.keyPlaceholder": "API_KEY",
+ "envEditor.removeAria": "删除变量",
+ "envEditor.saveError": "无法保存环境变量。",
+ "envEditor.savedCount": "{count}/{max} 个已保存变量",
+ "envEditor.scopeHint": "此值将在对应作用域内的启动过程中可用。",
+ "envEditor.validation.keyDuplicate": "该变量名重复。",
+ "envEditor.validation.keyPattern": "请使用字母、数字和下划线,并以字母或下划线开头。",
+ "envEditor.validation.keyTooLong": "键名长度不能超过 {max} 个字符。",
+ "envEditor.validation.nameRequired": "必须填写变量名。",
+ "envEditor.validation.valueTooLong": "值长度不能超过 {max} 个字符。",
+ "envEditor.valueLabel": "值",
+ "envEditor.valuePlaceholder": "secret value",
+ "mobilePairing.bridgeUnavailable": "移动配对桥接不可用。",
+ "mobilePairing.clearFailed": "无法清除配对信息。",
+ "mobilePairing.clearSaved": "清除已保存的配对",
+ "mobilePairing.description": "粘贴类似 okcode://pair?server=…&token=… 的配对链接,或包含 ?token=… 的服务器 URL。",
+ "mobilePairing.pair": "配对此设备",
+ "mobilePairing.pairFailed": "无法配对此设备。",
+ "mobilePairing.pairing": "配对中...",
+ "mobilePairing.placeholder": "okcode://pair?server=…&token=…",
+ "mobilePairing.title": "配对此设备",
+ "onboarding.actions.getStarted": "开始使用",
+ "onboarding.actions.next": "下一步",
+ "onboarding.actions.skip": "跳过",
+ "onboarding.progress.label": "引导进度",
+ "onboarding.progress.stepAria": "转到第 {index} / {total} 步:{title}",
+ "onboarding.step.approvals.description": "由你决定执行什么。代理在进行更改前会请求你的批准,因此没有你的同意就不会发生任何事。",
+ "onboarding.step.approvals.detail1": "可以批准、请求修改或取消任何提议的操作",
+ "onboarding.step.approvals.detail2": "按线程切换完全访问和需要批准的模式",
+ "onboarding.step.approvals.detail3": "在应用前查看待处理的文件更改",
+ "onboarding.step.approvals.title": "保持掌控",
+ "onboarding.step.chat.description": "与 AI 编码代理实时对话。可以提问、请求修改,或让代理主导完整功能开发。",
+ "onboarding.step.chat.detail1": "可在多个提供方之间切换:Codex 和 Claude",
+ "onboarding.step.chat.detail2": "代理工作时实时流式展示回复",
+ "onboarding.step.chat.detail3": "可直接在提示中附加图片和终端上下文",
+ "onboarding.step.chat.title": "AI 对话",
+ "onboarding.step.diff.description": "在接受任何内容之前,使用内置 diff 查看器检查代理所做的每一次代码修改。",
+ "onboarding.step.diff.detail1": "支持行内和并排 diff 视图,并带语法高亮",
+ "onboarding.step.diff.detail2": "一键按文件接受或拒绝更改",
+ "onboarding.step.diff.detail3": "词级高亮可准确显示变更内容",
+ "onboarding.step.diff.title": "并排审查改动",
+ "onboarding.step.getStarted.description": "你已经准备好开始构建。这里有几个快捷方式可以帮助你更快上手。",
+ "onboarding.step.getStarted.detail1": "按 Cmd+N(或 Ctrl+N)即可立即创建新线程",
+ "onboarding.step.getStarted.detail2": "使用侧边栏在项目和线程之间切换",
+ "onboarding.step.getStarted.detail3": "打开设置以自定义模型、主题和快捷键",
+ "onboarding.step.getStarted.title": "一切就绪!",
+ "onboarding.step.git.description": "每个线程都可以在自己的 git worktree 中运行,让你的主分支保持安全,同时允许代理自由实验。",
+ "onboarding.step.git.detail1": "新线程会自动创建隔离的 worktree",
+ "onboarding.step.git.detail2": "可从工具栏切换分支、创建 PR 并管理 worktree",
+ "onboarding.step.git.detail3": "将线程链接到现有 pull request 以进行更聚焦的代码审查",
+ "onboarding.step.git.title": "内置 Git 工作流",
+ "onboarding.step.plan.description": "切换到 Plan 模式,让代理先给出结构化的实现方案,再开始写任何代码。",
+ "onboarding.step.plan.detail1": "分步骤计划,并在工作推进时跟踪状态",
+ "onboarding.step.plan.detail2": "可查看、复制或导出为 Markdown",
+ "onboarding.step.plan.detail3": "点击“Implement Plan”即可在新线程中启动执行",
+ "onboarding.step.plan.title": "AI 生成计划",
+ "onboarding.step.terminal.description": "每个线程都内置完整终端,可运行命令、查看输出,并将上下文反馈给代理。",
+ "onboarding.step.terminal.detail1": "每个线程最多支持四个终端标签页并行工作",
+ "onboarding.step.terminal.detail2": "可选取终端输出并直接添加到提示中",
+ "onboarding.step.terminal.detail3": "通过实时活动指示器跟踪正在运行的子进程",
+ "onboarding.step.terminal.title": "集成终端",
+ "onboarding.step.welcome.description": "你的 AI 编码搭档。让我们快速浏览一下能显著提升工作流的功能。",
+ "onboarding.step.welcome.detail1": "与能够阅读、编写并理解你代码的 AI 代理协作",
+ "onboarding.step.welcome.detail2": "每次对话默认都在隔离的 git worktree 中运行",
+ "onboarding.step.welcome.detail3": "本次引导大约只需一分钟,你可随时跳过",
+ "onboarding.step.welcome.title": "欢迎使用 OK Code",
+ "root.error.detailsUnavailable": "没有更多错误详情可用。",
+ "root.error.hideDetails": "隐藏错误详情",
+ "root.error.showDetails": "显示错误详情",
+ "root.error.title": "出现问题。",
+ "root.error.unexpected": "发生了意外的路由错误。",
+ "root.loading.connectingServer": "正在连接到 {appName} 服务器...",
+ "root.loading.restoringMobilePairing": "正在恢复移动配对...",
+ "root.toast.invalidKeybindings.action": "打开 keybindings.json",
+ "root.toast.invalidKeybindings.title": "快捷键配置无效",
+ "root.toast.keybindingsUpdated.description": "快捷键配置已成功重新加载。",
+ "root.toast.keybindingsUpdated.title": "快捷键已更新",
+ "root.toast.openKeybindingsFailed.title": "无法打开快捷键文件",
+ "root.toast.unknownFileOpenError": "打开文件时发生未知错误。",
+ "settings.advanced.keybindings.description": "打开已持久化的 `keybindings.json` 文件以直接编辑高级快捷键。",
+ "settings.advanced.keybindings.noEditors": "没有可用的编辑器。",
+ "settings.advanced.keybindings.openFile": "打开文件",
+ "settings.advanced.keybindings.opensInPreferredEditor": "将在你偏好的编辑器中打开。",
+ "settings.advanced.keybindings.opening": "打开中...",
+ "settings.advanced.keybindings.resolvingPath": "正在解析 keybindings 路径...",
+ "settings.advanced.keybindings.title": "快捷键",
+ "settings.advanced.providerInstalls.claude.binaryDescription": "留空则使用 PATH 中的 claude。认证使用 claude auth login。",
+ "settings.advanced.providerInstalls.claude.binaryPathLabel": "Anthropic 二进制路径",
+ "settings.advanced.providerInstalls.claude.binaryPlaceholder": "Claude 二进制路径",
+ "settings.advanced.providerInstalls.codex.binaryDescription": "留空则使用 PATH 中的 codex。认证通常使用 codex login,除非你的 Codex 配置指向了自定义模型提供方。",
+ "settings.advanced.providerInstalls.codex.binaryPathLabel": "Codex 二进制路径",
+ "settings.advanced.providerInstalls.codex.binaryPlaceholder": "Codex 二进制路径",
+ "settings.advanced.providerInstalls.codex.homeDescription": "可选的自定义 Codex 主目录和配置目录。",
+ "settings.advanced.providerInstalls.codex.homePathLabel": "CODEX_HOME 路径",
+ "settings.advanced.providerInstalls.codex.homePlaceholder": "CODEX_HOME",
+ "settings.advanced.providerInstalls.customBadge": "自定义",
+ "settings.advanced.providerInstalls.description": "覆盖新会话使用的 CLI 二进制和认证主目录。",
+ "settings.advanced.providerInstalls.title": "提供方安装配置",
+ "settings.advanced.version.description": "当前应用版本。",
+ "settings.advanced.version.title": "版本",
+ "settings.changed.assistantOutput": "助手输出",
+ "settings.changed.borderRadius": "边框圆角",
+ "settings.changed.colorTheme": "配色主题",
+ "settings.changed.customModels": "自定义模型",
+ "settings.changed.deleteConfirmation": "删除确认",
+ "settings.changed.diffWordWrap": "Diff 自动换行",
+ "settings.changed.font": "字体",
+ "settings.changed.fontFamily": "字体族",
+ "settings.changed.gitWritingModel": "Git 写作模型",
+ "settings.changed.language": "语言",
+ "settings.changed.newThreadMode": "新线程模式",
+ "settings.changed.openLinksExternally": "在外部打开链接",
+ "settings.changed.providerInstalls": "提供方安装配置",
+ "settings.changed.theme": "主题",
+ "settings.changed.timeFormat": "时间格式",
+ "settings.environment.global.description": "适用于此机器上启动的每个提供方会话、终端、Git 命令和健康检查。",
+ "settings.environment.global.editorDescription": "全局值会在本地加密,并合并到每个运行环境中。",
+ "settings.environment.global.empty": "还没有保存任何全局变量。",
+ "settings.environment.global.failed": "加载已保存变量失败:{error}",
+ "settings.environment.global.loading": "正在加载已保存变量...",
+ "settings.environment.global.save": "保存全局",
+ "settings.environment.global.saved": "{count} 个已保存变量",
+ "settings.environment.global.title": "全局变量",
+ "settings.environment.project.description": "按项目保存,并在该项目启动提供方、终端或辅助命令时覆盖全局集合。",
+ "settings.environment.project.editorDescription": "打开或创建项目以编辑项目变量。",
+ "settings.environment.project.editorDescriptionWithProject": "{projectName} 的项目值会覆盖全局值。",
+ "settings.environment.project.empty": "还没有保存任何项目变量。",
+ "settings.environment.project.loading": "正在加载项目变量...",
+ "settings.environment.project.noProject": "打开项目以编辑项目变量。",
+ "settings.environment.project.noProjects": "没有可用项目。",
+ "settings.environment.project.save": "保存项目",
+ "settings.environment.project.saveErrorNoProject": "请先选择项目再保存项目变量。",
+ "settings.environment.project.selectPlaceholder": "选择项目",
+ "settings.environment.project.title": "项目变量",
+ "settings.general.assistantOutput.aria": "流式显示助手消息",
+ "settings.general.assistantOutput.description": "在回复生成过程中逐 token 显示输出。",
+ "settings.general.assistantOutput.title": "助手输出",
+ "settings.general.borderRadius.aria": "边框圆角",
+ "settings.general.borderRadius.description": "调整界面元素圆角的弧度。",
+ "settings.general.borderRadius.title": "边框圆角",
+ "settings.general.colorTheme.aria": "配色主题",
+ "settings.general.colorTheme.description": "为浅色和深色模式选择配色方案。",
+ "settings.general.colorTheme.importAria": "导入自定义主题",
+ "settings.general.colorTheme.importTooltip": "从 tweakcn.com 导入",
+ "settings.general.colorTheme.option.custom": "自定义",
+ "settings.general.colorTheme.option.default": "默认",
+ "settings.general.colorTheme.title": "配色主题",
+ "settings.general.deleteConfirmation.aria": "确认删除线程",
+ "settings.general.deleteConfirmation.description": "删除线程及其聊天记录前先询问。",
+ "settings.general.deleteConfirmation.title": "删除确认",
+ "settings.general.diffWordWrap.aria": "默认换行显示 diff 行",
+ "settings.general.diffWordWrap.description": "设置 diff 面板打开时的默认换行状态。面板内的换行开关只影响当前 diff 会话。",
+ "settings.general.diffWordWrap.title": "Diff 自动换行",
+ "settings.general.font.description": "选择界面使用的字体。",
+ "settings.general.font.title": "字体",
+ "settings.general.fontFamilyOverride.aria": "字体族覆盖",
+ "settings.general.fontFamilyOverride.description": "覆盖界面字体。可使用任意 Google Font 名称。",
+ "settings.general.fontFamilyOverride.placeholder": "例如:Inter, sans-serif",
+ "settings.general.fontFamilyOverride.title": "字体族",
+ "settings.general.language.aria": "语言偏好",
+ "settings.general.language.description": "选择应用界面使用的语言。",
+ "settings.general.language.option.en": "English",
+ "settings.general.language.option.es": "Español",
+ "settings.general.language.option.fr": "Français",
+ "settings.general.language.option.system": "系统默认",
+ "settings.general.language.option.zh-CN": "简体中文",
+ "settings.general.language.title": "语言",
+ "settings.general.newThreads.aria": "默认线程模式",
+ "settings.general.newThreads.description": "为新建草稿线程选择默认工作区模式。",
+ "settings.general.newThreads.title": "新线程",
+ "settings.general.openLinksExternally.aria": "在外部打开链接",
+ "settings.general.openLinksExternally.description": "终端 URL 将在默认浏览器中打开,而不是嵌入式预览面板。",
+ "settings.general.openLinksExternally.title": "在外部打开链接",
+ "settings.general.sidebarOpacity.aria": "侧边栏透明度",
+ "settings.general.sidebarOpacity.description": "调整侧边面板和项目列表的透明度。",
+ "settings.general.sidebarOpacity.title": "侧边栏透明度",
+ "settings.general.theme.aria": "主题偏好",
+ "settings.general.theme.description": "选择 OK Code 在整个应用中的外观。",
+ "settings.general.theme.title": "主题",
+ "settings.general.timeFormat.aria": "时间戳格式",
+ "settings.general.timeFormat.description": "系统默认会遵循浏览器或操作系统的时钟偏好设置。",
+ "settings.general.timeFormat.option.12Hour": "12 小时制",
+ "settings.general.timeFormat.option.24Hour": "24 小时制",
+ "settings.general.timeFormat.option.locale": "系统默认",
+ "settings.general.timeFormat.title": "时间格式",
+ "settings.general.windowOpacity.aria": "窗口透明度",
+ "settings.general.windowOpacity.description": "调整整个应用窗口的透明度。",
+ "settings.general.windowOpacity.title": "窗口透明度",
+ "settings.models.customModels.addButton": "添加",
+ "settings.models.customModels.description": "为 Codex 或 Anthropic 添加自定义模型 slug。聊天选择器会按提供方对模型分组。",
+ "settings.models.customModels.providerAria": "自定义模型提供方",
+ "settings.models.customModels.removeAria": "移除 {slug}",
+ "settings.models.customModels.showMore": "显示更多({count})",
+ "settings.models.customModels.title": "自定义模型",
+ "settings.models.customModels.validation.alreadySaved": "该自定义模型已保存。",
+ "settings.models.customModels.validation.builtIn": "该模型已内置。",
+ "settings.models.customModels.validation.enterSlug": "请输入模型 slug。",
+ "settings.models.customModels.validation.tooLong": "模型 slug 长度不能超过 {max} 个字符。",
+ "settings.models.gitWritingModel.aria": "Git 文本生成模型",
+ "settings.models.gitWritingModel.description": "用于生成提交信息、PR 标题和分支名。",
+ "settings.models.gitWritingModel.title": "Git 写作模型",
+ "settings.reset.aria": "将 {label} 重置为默认值",
+ "settings.reset.tooltip": "重置为默认值",
+ "settings.restoreDialog.description": "这将重置:{changes}。",
+ "settings.restoreDialog.title": "恢复默认设置?",
+ "settings.section.advanced": "高级",
+ "settings.section.environment": "环境",
+ "settings.section.general": "常规",
+ "settings.section.models": "模型",
+ "settings.title": "设置"
+}
diff --git a/apps/web/src/i18n/types.ts b/apps/web/src/i18n/types.ts
new file mode 100644
index 000000000..6c617f78c
--- /dev/null
+++ b/apps/web/src/i18n/types.ts
@@ -0,0 +1,8 @@
+export const APP_LOCALE_PREFERENCES = ["system", "en", "es", "fr", "zh-CN"] as const;
+export type AppLocalePreference = (typeof APP_LOCALE_PREFERENCES)[number];
+
+export const RESOLVED_APP_LOCALES = ["en", "es", "fr", "zh-CN"] as const;
+export type ResolvedAppLocale = (typeof RESOLVED_APP_LOCALES)[number];
+
+export type AppMessages = Record