From 1aa742053b8a15dcb5e3bfbc1062b5d9868f4c8b Mon Sep 17 00:00:00 2001 From: Anderson Shindy Oki Date: Tue, 2 Jun 2026 15:29:29 +0900 Subject: [PATCH] i18n: add japanese locale (MUL-2893) (#3538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: add japanese locale * fix: spacing issues * refactor * fix(desktop): set before paint to avoid JA Kanji font flash Switch the documentElement.lang sync from useEffect to useLayoutEffect so lang is committed before the first paint. Otherwise Japanese desktop users saw one frame of Kanji rendered with the Chinese-first fallback stack before the html[lang|="ja"] CJK override applied. Also fix the stale selector in the HTML_LANG comment (html[lang^="ja"] -> html[lang|="ja"]). Addresses review nits on MUL-2893. Co-authored-by: multica-agent * fix(docs): tokenize the ideographic iteration mark in JA search Add U+3005 (々) to the Japanese search tokenizer character class. It sits just below the kana blocks, so words like 様々 / 日々 / 個々 previously dropped the mark and split awkwardly, hurting recall. Addresses a review nit on MUL-2893. Co-authored-by: multica-agent * fix(i18n): restore ja locale parity after merging main Merging main brought new EN strings into agents/chat/onboarding/settings/ squads that the ja bundle (authored against an older snapshot) lacked, breaking the locales parity test. Add the Japanese translations for the new keys (workspace logo upload, agents runtime filter, chat session-history stop dialog, onboarding social_github, squad archived status) and drop the two renamed chat window keys (active_group / archived_group) that EN removed in favour of history_group. Fixes the failing @multica/views parity.test.ts on the FE CI for MUL-2893. Co-authored-by: multica-agent --------- Co-authored-by: J Co-authored-by: multica-agent --- apps/desktop/src/renderer/src/App.tsx | 23 +- .../renderer/src/font-fallback-order.test.ts | 27 + apps/desktop/src/renderer/src/globals.css | 20 + apps/docs/app/[lang]/layout.tsx | 18 +- apps/docs/app/api/search/route.ts | 27 + apps/docs/app/font-fallback-order.test.ts | 32 +- apps/docs/app/global.css | 30 + apps/docs/content/docs/agents-create.ja.mdx | 127 ++ apps/docs/content/docs/agents.ja.mdx | 49 + .../docs/content/docs/assigning-issues.ja.mdx | 83 ++ apps/docs/content/docs/auth-setup.ja.mdx | 166 +++ apps/docs/content/docs/auth-tokens.ja.mdx | 80 ++ apps/docs/content/docs/autopilots.ja.mdx | 239 ++++ apps/docs/content/docs/chat.ja.mdx | 63 + apps/docs/content/docs/cli.ja.mdx | 147 +++ .../docs/content/docs/cloud-quickstart.ja.mdx | 119 ++ apps/docs/content/docs/comments.ja.mdx | 81 ++ apps/docs/content/docs/daemon-runtimes.ja.mdx | 111 ++ apps/docs/content/docs/desktop-app.ja.mdx | 99 ++ .../docs/developers/conventions.ja.mdx | 302 +++++ .../docs/content/docs/developers/meta.ja.json | 4 + .../content/docs/environment-variables.ja.mdx | 224 ++++ .../content/docs/github-integration.ja.mdx | 183 +++ .../content/docs/how-multica-works.ja.mdx | 54 + apps/docs/content/docs/inbox.ja.mdx | 65 + apps/docs/content/docs/index.ja.mdx | 50 + .../content/docs/install-agent-runtime.ja.mdx | 180 +++ apps/docs/content/docs/issues.ja.mdx | 79 ++ apps/docs/content/docs/members-roles.ja.mdx | 60 + .../content/docs/mentioning-agents.ja.mdx | 63 + apps/docs/content/docs/meta.ja.json | 46 + apps/docs/content/docs/mobile-app.ja.mdx | 82 ++ .../content/docs/project-resources.ja.mdx | 262 ++++ apps/docs/content/docs/projects.ja.mdx | 49 + apps/docs/content/docs/providers.ja.mdx | 127 ++ .../content/docs/self-host-quickstart.ja.mdx | 275 ++++ apps/docs/content/docs/skills.ja.mdx | 67 + apps/docs/content/docs/squads.ja.mdx | 136 ++ apps/docs/content/docs/tasks.ja.mdx | 117 ++ apps/docs/content/docs/troubleshooting.ja.mdx | 279 ++++ apps/docs/content/docs/workspaces.ja.mdx | 56 + apps/docs/lib/i18n.ts | 8 +- apps/docs/lib/locale-link.test.ts | 2 + apps/docs/lib/site.test.ts | 17 + apps/docs/lib/static-params.test.ts | 4 + apps/docs/lib/translations.ts | 19 + apps/web/app/font-fallback-order.test.ts | 43 +- apps/web/app/globals.css | 33 + apps/web/app/layout.tsx | 34 +- .../use-cases/auto-data-analysis.ja.mdx | 102 ++ .../components/changelog-page-client.test.ts | 2 + apps/web/features/landing/i18n/context.tsx | 2 + apps/web/features/landing/i18n/ja.ts | 1173 +++++++++++++++++ apps/web/features/landing/i18n/types.ts | 6 +- apps/web/lib/docs-href.test.ts | 1 + apps/web/lib/docs-href.ts | 1 + apps/web/lib/locale-routing.test.ts | 9 + apps/web/lib/use-case-locale-fallback.test.ts | 25 + apps/web/lib/use-cases-i18n.ts | 10 + apps/web/lib/use-cases-source.ts | 5 +- packages/core/i18n/pick-locale.test.ts | 7 +- packages/core/i18n/types.ts | 4 +- packages/ui/types/i18next.ts | 2 +- packages/views/locales/en/settings.json | 1 + packages/views/locales/index.ts | 52 + packages/views/locales/ja/agents.json | 430 ++++++ packages/views/locales/ja/auth.json | 48 + packages/views/locales/ja/autopilots.json | 349 +++++ packages/views/locales/ja/billing.json | 75 ++ packages/views/locales/ja/chat.json | 129 ++ packages/views/locales/ja/common.json | 13 + packages/views/locales/ja/editor.json | 76 ++ packages/views/locales/ja/inbox.json | 71 + packages/views/locales/ja/invite.json | 53 + packages/views/locales/ja/issues.json | 445 +++++++ packages/views/locales/ja/labels.json | 3 + packages/views/locales/ja/layout.json | 41 + packages/views/locales/ja/members.json | 20 + packages/views/locales/ja/modals.json | 190 +++ packages/views/locales/ja/my-issues.json | 44 + packages/views/locales/ja/onboarding.json | 375 ++++++ packages/views/locales/ja/projects.json | 120 ++ packages/views/locales/ja/runtimes.json | 315 +++++ packages/views/locales/ja/search.json | 44 + packages/views/locales/ja/settings.json | 303 +++++ packages/views/locales/ja/skills.json | 245 ++++ packages/views/locales/ja/squads.json | 73 + packages/views/locales/ja/ui.json | 8 + packages/views/locales/ja/usage.json | 58 + packages/views/locales/ja/workspace.json | 34 + packages/views/locales/ko/settings.json | 1 + packages/views/locales/zh-Hans/settings.json | 1 + .../templates/create-agent-guide-issue.ts | 48 +- .../templates/helper-instructions.ts | 32 +- .../templates/helper-starter-prompts.ts | 35 +- .../views/onboarding/templates/index.test.ts | 1 + packages/views/onboarding/templates/index.ts | 3 +- .../templates/install-runtime-issue.ts | 43 +- .../components/preferences-tab.test.tsx | 12 + .../settings/components/preferences-tab.tsx | 1 + packages/views/test/i18n.tsx | 4 +- .../welcome-after-onboarding.test.tsx | 102 ++ .../workspace/welcome-after-onboarding.tsx | 2 +- server/internal/handler/auth.go | 1 + server/internal/handler/user_language_test.go | 20 + 105 files changed, 9667 insertions(+), 64 deletions(-) create mode 100644 apps/docs/content/docs/agents-create.ja.mdx create mode 100644 apps/docs/content/docs/agents.ja.mdx create mode 100644 apps/docs/content/docs/assigning-issues.ja.mdx create mode 100644 apps/docs/content/docs/auth-setup.ja.mdx create mode 100644 apps/docs/content/docs/auth-tokens.ja.mdx create mode 100644 apps/docs/content/docs/autopilots.ja.mdx create mode 100644 apps/docs/content/docs/chat.ja.mdx create mode 100644 apps/docs/content/docs/cli.ja.mdx create mode 100644 apps/docs/content/docs/cloud-quickstart.ja.mdx create mode 100644 apps/docs/content/docs/comments.ja.mdx create mode 100644 apps/docs/content/docs/daemon-runtimes.ja.mdx create mode 100644 apps/docs/content/docs/desktop-app.ja.mdx create mode 100644 apps/docs/content/docs/developers/conventions.ja.mdx create mode 100644 apps/docs/content/docs/developers/meta.ja.json create mode 100644 apps/docs/content/docs/environment-variables.ja.mdx create mode 100644 apps/docs/content/docs/github-integration.ja.mdx create mode 100644 apps/docs/content/docs/how-multica-works.ja.mdx create mode 100644 apps/docs/content/docs/inbox.ja.mdx create mode 100644 apps/docs/content/docs/index.ja.mdx create mode 100644 apps/docs/content/docs/install-agent-runtime.ja.mdx create mode 100644 apps/docs/content/docs/issues.ja.mdx create mode 100644 apps/docs/content/docs/members-roles.ja.mdx create mode 100644 apps/docs/content/docs/mentioning-agents.ja.mdx create mode 100644 apps/docs/content/docs/meta.ja.json create mode 100644 apps/docs/content/docs/mobile-app.ja.mdx create mode 100644 apps/docs/content/docs/project-resources.ja.mdx create mode 100644 apps/docs/content/docs/projects.ja.mdx create mode 100644 apps/docs/content/docs/providers.ja.mdx create mode 100644 apps/docs/content/docs/self-host-quickstart.ja.mdx create mode 100644 apps/docs/content/docs/skills.ja.mdx create mode 100644 apps/docs/content/docs/squads.ja.mdx create mode 100644 apps/docs/content/docs/tasks.ja.mdx create mode 100644 apps/docs/content/docs/troubleshooting.ja.mdx create mode 100644 apps/docs/content/docs/workspaces.ja.mdx create mode 100644 apps/web/content/use-cases/auto-data-analysis.ja.mdx create mode 100644 apps/web/features/landing/i18n/ja.ts create mode 100644 packages/views/locales/ja/agents.json create mode 100644 packages/views/locales/ja/auth.json create mode 100644 packages/views/locales/ja/autopilots.json create mode 100644 packages/views/locales/ja/billing.json create mode 100644 packages/views/locales/ja/chat.json create mode 100644 packages/views/locales/ja/common.json create mode 100644 packages/views/locales/ja/editor.json create mode 100644 packages/views/locales/ja/inbox.json create mode 100644 packages/views/locales/ja/invite.json create mode 100644 packages/views/locales/ja/issues.json create mode 100644 packages/views/locales/ja/labels.json create mode 100644 packages/views/locales/ja/layout.json create mode 100644 packages/views/locales/ja/members.json create mode 100644 packages/views/locales/ja/modals.json create mode 100644 packages/views/locales/ja/my-issues.json create mode 100644 packages/views/locales/ja/onboarding.json create mode 100644 packages/views/locales/ja/projects.json create mode 100644 packages/views/locales/ja/runtimes.json create mode 100644 packages/views/locales/ja/search.json create mode 100644 packages/views/locales/ja/settings.json create mode 100644 packages/views/locales/ja/skills.json create mode 100644 packages/views/locales/ja/squads.json create mode 100644 packages/views/locales/ja/ui.json create mode 100644 packages/views/locales/ja/usage.json create mode 100644 packages/views/locales/ja/workspace.json diff --git a/apps/desktop/src/renderer/src/App.tsx b/apps/desktop/src/renderer/src/App.tsx index 26bc77ad2..ea3fed71a 100644 --- a/apps/desktop/src/renderer/src/App.tsx +++ b/apps/desktop/src/renderer/src/App.tsx @@ -1,7 +1,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { CoreProvider } from "@multica/core/platform"; -import { pickLocale } from "@multica/core/i18n"; +import { pickLocale, type SupportedLocale } from "@multica/core/i18n"; import { useAuthStore } from "@multica/core/auth"; import { useWelcomeStore } from "@multica/core/onboarding"; import { workspaceKeys, workspaceListOptions } from "@multica/core/workspace/queries"; @@ -21,6 +21,18 @@ import { useDaemonIPCBridge } from "./platform/daemon-ipc-bridge"; import { createDesktopLocaleAdapter } from "./platform/i18n-adapter"; import { RESOURCES } from "@multica/views/locales"; +// BCP-47 region tags for the attribute, mirroring +// apps/web/app/layout.tsx HTML_LANG. index.html ships a static lang="en"; +// we sync it to the resolved locale at boot so screen readers announce the +// right language AND the Japanese-scoped CJK font override in globals.css +// (`html[lang|="ja"]`) can take effect. +const HTML_LANG: Record = { + en: "en", + "zh-Hans": "zh-CN", + ko: "ko-KR", + ja: "ja-JP", +}; + function AppContent() { const user = useAuthStore((s) => s.user); @@ -303,6 +315,15 @@ export default function App() { [locale], ); + // Keep in sync with the resolved locale (index.html hardcodes + // "en"). Drives the lang-scoped Japanese CJK font override and a11y. + // useLayoutEffect (not useEffect) so lang is committed before the first + // paint — otherwise Japanese users would see one frame of Kanji rendered + // with the Chinese-first fallback stack before the override kicks in. + useLayoutEffect(() => { + document.documentElement.lang = HTML_LANG[locale]; + }, [locale]); + // React to OS-level language changes detected by main on focus regain. // Only act when the user is following the system signal (no explicit // Settings choice) — otherwise their preference wins. Cross-device sync diff --git a/apps/desktop/src/renderer/src/font-fallback-order.test.ts b/apps/desktop/src/renderer/src/font-fallback-order.test.ts index 4e7499597..b48fc7918 100644 --- a/apps/desktop/src/renderer/src/font-fallback-order.test.ts +++ b/apps/desktop/src/renderer/src/font-fallback-order.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest"; const chineseFonts = ["PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC"]; const koreanFonts = ["Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans CJK KR"]; +const japaneseFonts = ["Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP"]; function expectChineseFontsBeforeKoreanFonts(source: string) { const chineseIndexes = chineseFonts.map((font) => source.indexOf(font)); @@ -19,6 +20,23 @@ function expectChineseFontsBeforeKoreanFonts(source: string) { } } +// Japanese Kanji share the Han Unicode block with Chinese, so the global +// Chinese-first stack must stay Chinese-first (no zh regression) while a +// Japanese-first CJK stack is scoped to html[lang|="ja"]. App.tsx syncs +// document.documentElement.lang so the selector matches at runtime. +function expectJapaneseScopedOverride(source: string) { + expect(source).toContain('html[lang|="ja"]'); + + const japaneseIndexes = japaneseFonts.map((font) => source.indexOf(font)); + expect(japaneseIndexes).not.toContain(-1); + + const firstJapanese = Math.min(...japaneseIndexes); + const lastChinese = Math.max( + ...chineseFonts.map((font) => source.lastIndexOf(font)), + ); + expect(firstJapanese).toBeLessThan(lastChinese); +} + describe("CJK font fallback order", () => { it("keeps desktop Chinese font fallbacks before Korean font fallbacks", () => { const desktopCss = readFileSync( @@ -28,4 +46,13 @@ describe("CJK font fallback order", () => { expectChineseFontsBeforeKoreanFonts(desktopCss); }); + + it("scopes the Japanese-first CJK stack to html[lang|='ja']", () => { + const desktopCss = readFileSync( + resolve(process.cwd(), "src/renderer/src/globals.css"), + "utf8", + ); + + expectJapaneseScopedOverride(desktopCss); + }); }); diff --git a/apps/desktop/src/renderer/src/globals.css b/apps/desktop/src/renderer/src/globals.css index c170deb2c..6496a738d 100644 --- a/apps/desktop/src/renderer/src/globals.css +++ b/apps/desktop/src/renderer/src/globals.css @@ -31,6 +31,26 @@ monospace; } +/* Japanese-scoped CJK override. Japanese Kanji share the Han Unicode block + with Chinese, and CSS font-fallback order is not changed by — + so the global Chinese-first stack above would give Japanese users Chinese + glyph shapes for shared ideographs. We keep the global stack Chinese-first + (no regression for zh users) and promote Japanese fonts ahead of the + Chinese/Korean families only when the locale is Japanese. App.tsx syncs + document.documentElement.lang to the active locale so this selector + matches. Mirrors the lang-scoped override in apps/web/app/layout.tsx. + `[lang|="ja"]` is the BCP-47 language-range selector: it matches exactly + `ja` or `ja-` (App.tsx sets `ja-JP`), never unrelated subtags + such as `jam`. */ +html[lang|="ja"] { + --font-sans: "Inter Variable", "Inter", "Hiragino Sans", + "Hiragino Kaku Gothic ProN", "Yu Gothic", "YuGothic", "Meiryo", + "Noto Sans CJK JP", "Noto Sans JP", -apple-system, + BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", + "Noto Sans CJK SC", "Apple SD Gothic Neo", "Malgun Gothic", + "Noto Sans CJK KR", sans-serif; +} + @source "../../../../../packages/ui/**/*.tsx"; @source "../../../../../packages/core/**/*.{ts,tsx}"; @source "../../../../../packages/views/**/*.{ts,tsx}"; diff --git a/apps/docs/app/[lang]/layout.tsx b/apps/docs/app/[lang]/layout.tsx index a34b120c9..ebee47a9c 100644 --- a/apps/docs/app/[lang]/layout.tsx +++ b/apps/docs/app/[lang]/layout.tsx @@ -11,21 +11,13 @@ import { i18n, type Lang } from "@/lib/i18n"; import { uiTranslations, localeLabels } from "@/lib/translations"; import { DocsSettings } from "@/components/docs-settings"; +// Inter (Latin UI face) is exposed under `--font-inter`. The full `--font-sans` +// stack — Inter + the per-locale CJK fallback chain, including the Japanese-first +// override scoped to `` — is composed in static CSS in +// ./global.css (CSP-safe, no inline