- Browse
+ {t("settings.codex.path.browse")}
-
Leave empty to use the system PATH resolution.
+
+ {t("settings.codex.path.help")}
+
onSetCodexArgsDraft("")}
>
- Clear
+ {t("settings.codex.args.clear")}
- Extra flags passed before app-server. Use quotes for values with spaces.
+ {t("settings.codex.args.help.before")}
+ app-server
+ {t("settings.codex.args.help.after")}
- These settings apply to the shared Codex app-server used across all connected workspaces.
+ {t("settings.codex.args.sharedHelp")}
- Per-thread override processing ignores unsupported flags: -m/
- --model, -a/--ask-for-approval,{" "}
+ {t("settings.codex.unsupportedFlags.prefix")}{" "}
+ -m/--model, -a/--ask-for-approval,{" "}
-s/--sandbox, --full-auto,{" "}
--dangerously-bypass-approvals-and-sandbox, --oss,{" "}
- --local-provider, and --no-alt-screen.
+ --local-provider, {t("settings.codex.unsupportedFlags.and")} --no-alt-screen
+ {t("common.punctuation.period")}
{codexDirty && (
@@ -304,7 +306,7 @@ export function SettingsCodexSection({
}}
disabled={isSavingSettings}
>
- {isSavingSettings ? "Saving..." : "Save"}
+ {isSavingSettings ? t("settings.codex.actions.saving") : t("settings.codex.actions.save")}
)}
{doctorState.result && (
- {doctorState.result.ok ? "Codex looks good" : "Codex issue detected"}
+ {doctorState.result.ok
+ ? t("settings.codex.doctor.okTitle")
+ : t("settings.codex.doctor.errorTitle")}
-
Version: {doctorState.result.version ?? "unknown"}
-
App-server: {doctorState.result.appServerOk ? "ok" : "failed"}
- Node:{" "}
+ {t("settings.codex.doctor.version")}:{" "}
+ {doctorState.result.version ?? t("settings.codex.doctor.unknown")}
+
+
+ {t("settings.codex.doctor.appServer")}:{" "}
+ {doctorState.result.appServerOk
+ ? t("settings.codex.doctor.ok")
+ : t("settings.codex.doctor.failed")}
+
+
+ {t("settings.codex.doctor.node")}:{" "}
{doctorState.result.nodeOk
- ? `ok (${doctorState.result.nodeVersion ?? "unknown"})`
- : "missing"}
+ ? `${t("settings.codex.doctor.ok")} (${doctorState.result.nodeVersion ?? t("settings.codex.doctor.unknown")})`
+ : t("settings.codex.doctor.missing")}
{doctorState.result.details &&
{doctorState.result.details}
}
{doctorState.result.nodeDetails &&
{doctorState.result.nodeDetails}
}
@@ -362,25 +378,25 @@ export function SettingsCodexSection({
{codexUpdateState.result.ok
? codexUpdateState.result.upgraded
- ? "Codex updated"
- : "Codex already up-to-date"
- : "Codex update failed"}
+ ? t("settings.codex.update.updated")
+ : t("settings.codex.update.upToDate")
+ : t("settings.codex.update.failed")}
-
Method: {codexUpdateState.result.method}
+
{t("settings.codex.update.method")}: {codexUpdateState.result.method}
{codexUpdateState.result.package && (
-
Package: {codexUpdateState.result.package}
+
{t("settings.codex.update.package")}: {codexUpdateState.result.package}
)}
- Version:{" "}
+ {t("settings.codex.doctor.version")}:{" "}
{codexUpdateState.result.afterVersion ??
codexUpdateState.result.beforeVersion ??
- "unknown"}
+ t("settings.codex.doctor.unknown")}
{codexUpdateState.result.details &&
{codexUpdateState.result.details}
}
{codexUpdateState.result.output && (
- output
+ {t("settings.codex.update.output")}
{codexUpdateState.result.output}
)}
@@ -391,25 +407,24 @@ export function SettingsCodexSection({
- Default parameters
+ {t("settings.codex.defaults.sectionTitle")}
-
- Model
+
+
+
- }
- subtitle={
- defaultModelsConnectedWorkspaceCount === 0
- ? "Add a workspace to load available models."
- : defaultModelsLoading
- ? "Loading models from the first workspace…"
- : defaultModelsError
- ? `Couldn’t load models: ${defaultModelsError}`
- : "Sourced from the first workspace and used when there is no thread-specific override."
- }
- >
+
+ {defaultModelsConnectedWorkspaceCount === 0
+ ? t("settings.codex.defaults.model.addWorkspace")
+ : defaultModelsLoading
+ ? t("settings.codex.defaults.model.loading")
+ : defaultModelsError
+ ? t("settings.codex.defaults.model.loadError", { error: defaultModelsError })
+ : t("settings.codex.defaults.model.help")}
+
+
-
+
-
- Reasoning effort
+
+
+
- }
- subtitle={
- reasoningSupported
- ? "Available options depend on the selected model."
- : "The selected model does not expose reasoning effort options."
- }
- >
+
+ {reasoningSupported
+ ? t("settings.codex.defaults.reasoning.supported")
+ : t("settings.codex.defaults.reasoning.unsupported")}
+
+
-
+
-
- Access mode
+
+
+
- }
- subtitle="Used when there is no thread-specific override."
- >
+
+ {t("settings.codex.defaults.access.help")}
+
+
-
+
- Choose whether /review runs in the current thread or a detached review
- thread.
+ {t("settings.codex.review.help.before")}
+ /review
+ {t("settings.codex.review.help.after")}
- Stored at ~/.codex/AGENTS.md.
+ {t("settings.codex.file.storedAt")}
+ ~/.codex/AGENTS.md
+ {t("common.punctuation.period")}
>
}
classNames={{
@@ -555,11 +575,11 @@ export function SettingsCodexSection({
/>
- Stored at ~/.codex/config.toml.
+ {t("settings.codex.file.storedAt")}
+ ~/.codex/config.toml
+ {t("common.punctuation.period")}
>
}
classNames={{
@@ -584,6 +606,6 @@ export function SettingsCodexSection({
help: "settings-help",
}}
/>
-
+
);
}
diff --git a/src/features/settings/components/sections/SettingsComposerSection.tsx b/src/features/settings/components/sections/SettingsComposerSection.tsx
index ae3014bbc..23e6ef7b9 100644
--- a/src/features/settings/components/sections/SettingsComposerSection.tsx
+++ b/src/features/settings/components/sections/SettingsComposerSection.tsx
@@ -1,9 +1,5 @@
import type { AppSettings } from "@/types";
-import {
- SettingsSection,
- SettingsToggleRow,
- SettingsToggleSwitch,
-} from "@/features/design-system/components/settings/SettingsPrimitives";
+import { useI18n } from "@/i18n/useI18n";
type ComposerPreset = AppSettings["composerEditorPreset"];
@@ -24,15 +20,15 @@ export function SettingsComposerSection({
onComposerPresetChange,
onUpdateAppSettings,
}: SettingsComposerSectionProps) {
+ const { t } = useI18n();
const steerUnavailable = !appSettings.steerEnabled;
return (
-
+
+ {t("settings.composer.sectionTitle")}
+ {t("settings.composer.sectionSubtitle")}
-
Follow-up behavior
-
+
{t("settings.composer.followUp.label")}
+
- Queue
+
+ {t("settings.composer.followUp.option.queue")}
+
- Choose the default while a run is active. Press {followUpShortcutLabel} to send the
- opposite behavior for one message.
+ {t("settings.composer.followUp.help.before")}
+ {followUpShortcutLabel}
+ {t("settings.composer.followUp.help.after")}
-
-
+
+
{t("settings.composer.followUp.hint.title")}
+
+ {t("settings.composer.followUp.hint.subtitle")}
+
+
+
+ aria-pressed={appSettings.composerFollowUpHintEnabled}
+ >
+
+
+
{steerUnavailable && (
- Steer is unavailable in the current Codex config. Follow-ups will queue.
+ {t("settings.composer.followUp.steerUnavailableHelp")}
)}
- Presets
-
- Choose a starting point and fine-tune the toggles below.
-
+ {t("settings.composer.presets.title")}
+ {t("settings.composer.presets.subtitle")}
- Code fences
-
- {t("settings.composer.codeFences.title")}
+
+
+
{t("settings.composer.codeFences.space.title")}
+
+ {t("settings.composer.codeFences.space.subtitle")}
+
+
+
+
+
+
+
{t("settings.composer.codeFences.enter.title")}
+
+ {t("settings.composer.codeFences.enter.subtitle")}
+
+
+
+
+
+
+
{t("settings.composer.codeFences.langTags.title")}
+
+ {t("settings.composer.codeFences.langTags.subtitle")}
+
+
+
+
+
+
+
+ {t("settings.composer.codeFences.wrapSelection.title")}
+
+
+ {t("settings.composer.codeFences.wrapSelection.subtitle")}
+
+
+
+
+
+
+
+ {t("settings.composer.codeFences.copyNoFence.title")}
+
+
+ {t("settings.composer.codeFences.copyNoFence.subtitle.before")}
+ {optionKeyLabel}
+ {t("settings.composer.codeFences.copyNoFence.subtitle.after")}
+
+
+
+
-
Pasting
-
- {t("settings.composer.pasting.title")}
+
+
+
+ {t("settings.composer.pasting.multiline.title")}
+
+
+ {t("settings.composer.pasting.multiline.subtitle")}
+
+
+
+
+
+
+
+ {t("settings.composer.pasting.codeLike.title")}
+
+
+ {t("settings.composer.pasting.codeLike.subtitle")}
+
+
+
+
-
Lists
-
- {t("settings.composer.lists.title")}
+
+
+
+ {t("settings.composer.lists.continue.title")}
+
+
+ {t("settings.composer.lists.continue.subtitle")}
+
+
+
+
+
);
}
diff --git a/src/features/settings/components/sections/SettingsDictationSection.tsx b/src/features/settings/components/sections/SettingsDictationSection.tsx
index 83c37279e..0dee0b8e1 100644
--- a/src/features/settings/components/sections/SettingsDictationSection.tsx
+++ b/src/features/settings/components/sections/SettingsDictationSection.tsx
@@ -1,10 +1,6 @@
import type { AppSettings, DictationModelStatus } from "@/types";
-import {
- SettingsSection,
- SettingsToggleRow,
- SettingsToggleSwitch,
-} from "@/features/design-system/components/settings/SettingsPrimitives";
import { formatDownloadSize } from "@utils/formatting";
+import { useI18n } from "@/i18n/useI18n";
type DictationModelOption = {
id: string;
@@ -40,19 +36,23 @@ export function SettingsDictationSection({
onCancelDictationDownload,
onRemoveDictationModel,
}: SettingsDictationSectionProps) {
+ const { t } = useI18n();
const dictationProgress = dictationModelStatus?.progress ?? null;
return (
-
-
-
+ {t("settings.dictation.sectionTitle")}
+ {t("settings.dictation.sectionSubtitle")}
+
+
+
{t("settings.dictation.enable.title")}
+
+ {t("settings.dictation.enable.subtitle")}
+
+
+
+
- Auto-detect stays on; this nudges the decoder toward your preference.
+ {t("settings.dictation.language.help")}
- Hold the key to start dictation, release to stop and process.
+ {t("settings.dictation.holdKey.help")}
{dictationModelStatus && (
-
Model status ({selectedDictationModel.label})
+
+ {t("settings.dictation.status.label")} ({selectedDictationModel.label})
+
- {dictationModelStatus.state === "ready" && "Ready for dictation."}
- {dictationModelStatus.state === "missing" && "Model not downloaded yet."}
- {dictationModelStatus.state === "downloading" && "Downloading model..."}
+ {dictationModelStatus.state === "ready" && t("settings.dictation.status.ready")}
+ {dictationModelStatus.state === "missing" && t("settings.dictation.status.missing")}
+ {dictationModelStatus.state === "downloading" &&
+ t("settings.dictation.status.downloading")}
{dictationModelStatus.state === "error" &&
- (dictationModelStatus.error ?? "Download error.")}
+ (dictationModelStatus.error ?? t("settings.dictation.status.errorFallback"))}
{dictationProgress && (
@@ -203,7 +210,7 @@ export function SettingsDictationSection({
onClick={onDownloadDictationModel}
disabled={!onDownloadDictationModel}
>
- Download model
+ {t("settings.dictation.actions.download")}
)}
{dictationModelStatus.state === "downloading" && (
@@ -213,7 +220,7 @@ export function SettingsDictationSection({
onClick={onCancelDictationDownload}
disabled={!onCancelDictationDownload}
>
- Cancel download
+ {t("settings.dictation.actions.cancelDownload")}
)}
{dictationReady && (
@@ -223,12 +230,12 @@ export function SettingsDictationSection({
onClick={onRemoveDictationModel}
disabled={!onRemoveDictationModel}
>
- Remove model
+ {t("settings.dictation.actions.removeModel")}
)}
)}
-
+
);
}
diff --git a/src/features/settings/components/sections/SettingsDisplaySection.test.tsx b/src/features/settings/components/sections/SettingsDisplaySection.test.tsx
index 2f273d437..b81be2213 100644
--- a/src/features/settings/components/sections/SettingsDisplaySection.test.tsx
+++ b/src/features/settings/components/sections/SettingsDisplaySection.test.tsx
@@ -61,6 +61,56 @@ describe("SettingsDisplaySection", () => {
expect.objectContaining({ threadTitleAutogenerationEnabled: true }),
);
});
+
+ it("updates interface language", () => {
+ const onUpdateAppSettings = vi.fn(async () => {});
+
+ render(
+
{})}
+ onResetScale={vi.fn(async () => {})}
+ onSetUiFontDraft={vi.fn() as any}
+ onCommitUiFont={vi.fn(async () => {})}
+ onSetCodeFontDraft={vi.fn() as any}
+ onCommitCodeFont={vi.fn(async () => {})}
+ onSetCodeFontSizeDraft={vi.fn() as any}
+ onCommitCodeFontSize={vi.fn(async () => {})}
+ onTestNotificationSound={vi.fn()}
+ onTestSystemNotification={vi.fn()}
+ />,
+ );
+
+ fireEvent.change(screen.getByLabelText("Interface language"), {
+ target: { value: "zh-CN" },
+ });
+
+ expect(onUpdateAppSettings).toHaveBeenCalledWith(
+ expect.objectContaining({ uiLanguage: "zh-CN" }),
+ );
+ });
it("toggles unlimited chat history", () => {
const onUpdateAppSettings = vi.fn(async () => {});
diff --git a/src/features/settings/components/sections/SettingsDisplaySection.tsx b/src/features/settings/components/sections/SettingsDisplaySection.tsx
index 0dc2b9583..23343639f 100644
--- a/src/features/settings/components/sections/SettingsDisplaySection.tsx
+++ b/src/features/settings/components/sections/SettingsDisplaySection.tsx
@@ -16,11 +16,7 @@ import {
clampChatScrollbackItems,
isChatScrollbackPreset,
} from "@utils/chatScrollback";
-import {
- SettingsSection,
- SettingsToggleRow,
- SettingsToggleSwitch,
-} from "@/features/design-system/components/settings/SettingsPrimitives";
+import { useI18n } from "@/i18n/useI18n";
type SettingsDisplaySectionProps = {
appSettings: AppSettings;
@@ -69,6 +65,8 @@ export function SettingsDisplaySection({
onTestNotificationSound,
onTestSystemNotification,
}: SettingsDisplaySectionProps) {
+ const { t } = useI18n();
+ const uiLanguage = appSettings.uiLanguage ?? "system";
const scrollbackUnlimited = appSettings.chatHistoryScrollbackItems === null;
const [scrollbackDraft, setScrollbackDraft] = useState(() => {
const value = appSettings.chatHistoryScrollbackItems;
@@ -159,17 +157,16 @@ export function SettingsDisplaySection({
};
return (
-
- Display
+
+ {t("settings.display.sectionTitle")}
+ {t("settings.display.sectionSubtitle")}
+ {t("settings.display.subsectionDisplayTitle")}
- Adjust how the window renders backgrounds and effects.
+ {t("settings.display.subsectionDisplaySubtitle")}
+
+
+
+
+
{t("settings.display.uiLanguage.help")}
-
-
+
+
{t("settings.display.usageRemaining.title")}
+
{t("settings.display.usageRemaining.subtitle")}
+
+
-
-
+
+
+
+
-