From 3e4c7c81a26292faefa9f3c62041af99326f3899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Tue, 7 Jul 2026 13:22:19 -0400 Subject: [PATCH] Localize the renderer UI into Chinese behind the language setting --- src/main/app/create-window.ts | 3 + src/main/index.ts | 4 +- src/preload/index.ts | 1 + src/renderer/src/app/Onboarding.tsx | 100 ++-- .../src/app/OnboardingConnectorsStep.tsx | 7 +- .../src/app/OnboardingProviderStep.tsx | 27 +- src/renderer/src/i18n/i18n.ts | 17 + src/renderer/src/i18n/zh.ts | 440 ++++++++++++++++++ src/renderer/src/pages/ApprovalsPage.tsx | 67 +-- src/renderer/src/pages/ChatPage.tsx | 35 +- src/renderer/src/pages/ConnectorsPage.tsx | 5 +- src/renderer/src/pages/FilesPage.tsx | 25 +- src/renderer/src/pages/HomePage.tsx | 69 +-- src/renderer/src/pages/NewTaskDialog.tsx | 41 +- src/renderer/src/pages/RunsPage.tsx | 63 +-- src/renderer/src/pages/SettingsPage.tsx | 11 +- src/renderer/src/pages/TeamPage.tsx | 87 ++-- src/renderer/src/pages/WorkPage.tsx | 47 +- src/renderer/src/pages/chat/ChatComposer.tsx | 11 +- .../src/pages/chat/ChatConversation.tsx | 5 +- .../src/pages/chat/ChatMessageRow.tsx | 25 +- src/renderer/src/pages/chat/ChatSidebar.tsx | 5 +- src/renderer/src/pages/chat/ChatTimeline.tsx | 9 +- src/renderer/src/pages/chat/chat-helpers.ts | 11 +- .../src/pages/connectors/ConnectedList.tsx | 7 +- .../src/pages/connectors/ToolkitCatalog.tsx | 11 +- src/renderer/src/pages/files/FilesGrid.tsx | 5 +- src/renderer/src/pages/files/FilesTable.tsx | 13 +- src/renderer/src/pages/files/FilesTree.tsx | 13 +- src/renderer/src/pages/files/file-helpers.ts | 11 +- .../src/pages/home/DashboardCharts.tsx | 20 +- .../src/pages/office/AgentDetailCard.tsx | 13 +- src/renderer/src/pages/office/floor-plan.ts | 9 +- .../src/pages/settings/AdvancedPanel.tsx | 3 +- .../src/pages/settings/BillingPanel.tsx | 31 +- .../settings/ProviderConnectionDialog.tsx | 25 +- .../pages/settings/ProviderRequestPanel.tsx | 37 +- .../src/pages/settings/ProvidersPanel.tsx | 61 +-- .../src/pages/settings/SkillsPanel.tsx | 23 +- .../src/pages/settings/WorkspacePanel.tsx | 51 +- .../src/pages/social/SocialActivity.tsx | 13 +- .../src/pages/social/SocialCalendar.tsx | 5 +- .../src/pages/team/HiringPipeline.tsx | 11 +- src/renderer/src/pages/team/OrgChartTree.tsx | 9 +- src/renderer/src/pages/types.ts | 11 +- src/renderer/src/pages/work/GoalDialog.tsx | 11 +- src/renderer/src/pages/work/ListView.tsx | 9 +- src/renderer/src/pages/work/ProjectDialog.tsx | 15 +- .../src/pages/work/ProjectOverview.tsx | 5 +- .../src/pages/work/TaskDetailDialog.tsx | 67 +-- src/renderer/src/pages/work/TimelineView.tsx | 11 +- src/renderer/src/pages/work/task-helpers.ts | 10 +- src/renderer/src/shared/Dialog.tsx | 3 +- src/renderer/src/shared/OrgSwitcher.tsx | 19 +- src/renderer/src/shared/SearchDialog.tsx | 37 +- src/renderer/src/shared/Shell.tsx | 35 +- src/shared/desktop-api.ts | 1 + tests/unit/i18n-coverage.test.ts | 53 +++ 58 files changed, 1176 insertions(+), 597 deletions(-) create mode 100644 src/renderer/src/i18n/i18n.ts create mode 100644 src/renderer/src/i18n/zh.ts create mode 100644 tests/unit/i18n-coverage.test.ts diff --git a/src/main/app/create-window.ts b/src/main/app/create-window.ts index 7637a22b..c383e648 100644 --- a/src/main/app/create-window.ts +++ b/src/main/app/create-window.ts @@ -6,6 +6,8 @@ export interface CreateWindowOptions { preloadPath: string; rendererDist: string; devServerUrl?: string; + /** UI language for renderer strings; injected synchronously via argv. */ + language: string; } export function createMainWindow(options: CreateWindowOptions): BrowserWindow { @@ -27,6 +29,7 @@ export function createMainWindow(options: CreateWindowOptions): BrowserWindow { sandbox: true, webSecurity: true, allowRunningInsecureContent: false, + additionalArguments: [`--app-language=${options.language}`], }, }); diff --git a/src/main/index.ts b/src/main/index.ts index 1b6fbd96..670beed2 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -23,7 +23,8 @@ if (!app.requestSingleInstanceLock()) { // strings, date formatting) is only settable pre-ready, and it follows the // stored language preference instead of the OS locale. const connection = new DatabaseConnection(join(profileDir, "agentcompany.db")); - app.commandLine.appendSwitch("lang", storedLanguage(connection.db) === "zh" ? "zh-CN" : "en-US"); + const language = storedLanguage(connection.db); + app.commandLine.appendSwitch("lang", language === "zh" ? "zh-CN" : "en-US"); app.whenReady().then(() => { connection.transaction(() => recoverInterruptedWork(connection.db)); @@ -31,6 +32,7 @@ if (!app.requestSingleInstanceLock()) { preloadPath: join(__dirname, "../preload/index.js"), rendererDist: join(__dirname, "../renderer"), devServerUrl: process.env.ELECTRON_RENDERER_URL, + language, }); registerIpcHandlers({ diff --git a/src/preload/index.ts b/src/preload/index.ts index cb3eccc7..6a0d9b9e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -94,6 +94,7 @@ const api: DesktopApi = { return () => ipcRenderer.removeListener(IPC_CHANNELS.rendererEvent, listener); }, platform: process.platform as DesktopApi["platform"], + language: (process.argv.find((arg) => arg.startsWith("--app-language="))?.slice("--app-language=".length) === "zh" ? "zh" : "en") as DesktopApi["language"], windowMinimize: () => invoke(IPC_CHANNELS.windowMinimize), windowMaximize: () => invoke(IPC_CHANNELS.windowMaximize), windowClose: () => invoke(IPC_CHANNELS.windowClose), diff --git a/src/renderer/src/app/Onboarding.tsx b/src/renderer/src/app/Onboarding.tsx index facd6e07..73d39a24 100644 --- a/src/renderer/src/app/Onboarding.tsx +++ b/src/renderer/src/app/Onboarding.tsx @@ -8,6 +8,7 @@ import { Button, Field, Icon, IconButton, Pill } from "../shared/ui"; import type { SnapshotActions } from "./useSnapshot"; import { ProviderStep } from "./OnboardingProviderStep"; import { ConnectorsStep } from "./OnboardingConnectorsStep"; +import { t } from "../i18n/i18n"; type Step = "welcome" | "provider" | "company" | "mode" | "mission" | "connectors"; @@ -26,12 +27,12 @@ export interface Draft { } const STEPS: Array<{ id: Step; label: string }> = [ - { id: "welcome", label: "Start" }, - { id: "provider", label: "Provider" }, - { id: "company", label: "Company" }, - { id: "mode", label: "Mode" }, - { id: "mission", label: "Goal" }, - { id: "connectors", label: "Connectors" }, + { id: "welcome", label: t("Start") }, + { id: "provider", label: t("Provider") }, + { id: "company", label: t("Company") }, + { id: "mode", label: t("Mode") }, + { id: "mission", label: t("Goal") }, + { id: "connectors", label: t("Connectors") }, ]; export function Onboarding({ @@ -133,9 +134,9 @@ export function Onboarding({
{!isMac && ( - void window.agentCompany.windowMinimize()} /> - void window.agentCompany.windowMaximize()} /> - void window.agentCompany.windowClose()} /> + void window.agentCompany.windowMinimize()} /> + void window.agentCompany.windowMaximize()} /> + void window.agentCompany.windowClose()} /> )}
@@ -147,13 +148,13 @@ export function Onboarding({
AgentCompany
{onCancel && step !== "connectors" && ( )} -

Start your AI company.

+

{t("Start your AI company.")}

- Stand up the workspace, bind the provider, and queue the first operating loop. + {t("Stand up the workspace, bind the provider, and queue the first operating loop.")}

{STEPS.map((entry) => ( @@ -162,14 +163,14 @@ export function Onboarding({
-
Runtime selected
+
{t("Runtime selected")}
{selectedProvider.label} {draft.providerModel && {draft.providerModel}}
- We never embed the key in the build. Credentials encrypt locally via OS-protected storage. + {t("We never embed the key in the build. Credentials encrypt locally via OS-protected storage.")}
@@ -233,19 +234,18 @@ interface StepProps { function WelcomeStep({ onNext }: { onNext: () => void }) { return (
-

Welcome

+

{t("Welcome")}

- AgentCompany sets up your operating loop in six steps. Bring an API key, pick a runtime - for the founding CEO, and we'll wire up the first goal, project, and task. + {t("AgentCompany sets up your operating loop in six steps. Bring an API key, pick a runtime for the founding CEO, and we'll wire up the first goal, project, and task.")}

    -
  • Connect one model provider (OpenAI, Anthropic, xAI, or Groq).
  • -
  • Name the company and the human board chair.
  • -
  • Pick execution mode and bind the workspace path.
  • -
  • State the first goal — we kick off the rest.
  • +
  • {t("Connect one model provider (OpenAI, Anthropic, xAI, or Groq).")}
  • +
  • {t("Name the company and the human board chair.")}
  • +
  • {t("Pick execution mode and bind the workspace path.")}
  • +
  • {t("State the first goal — we kick off the rest.")}
- +
); @@ -259,20 +259,20 @@ function CompanyStep({ draft, onChange, onBack, onNext }: StepProps) { }; return (
-

Company

- +

{t("Company")}

+ onChange({ companyName: event.target.value })} autoFocus required /> - + onChange({ employeeName: event.target.value })} required /> - +