From ea6115a09a130525420733282685df623a6ba397 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 02:55:49 +0000 Subject: [PATCH] fix(i18n): delete the four `pick({en,zh})` clones (#2871, part 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four files each carried an identical private resolver that read `document.documentElement.lang` and branched on `startsWith('zh')`. Only Chinese was ever handled, so the other eight shipped languages silently rendered English — and because the copy lived in the components as inline {en,zh} pairs, no translator could reach it. All four copies are deleted along with their `I18n` type alias. Migrated to the locale packs in all ten languages: - `excelImport.*` (8) — ExcelImportBar - `cloudOnboarding.*` (5) — CloudOnboardingNext (Cloud welcome page) - `aiModelStatus.*` (11) — CloudAiModelStatus - `chatbotQuota.*` (4) — the AI quota banner Two spots needed restructuring rather than a straight port: - The import toast was a template literal baked into both variants; it is now a `{{count}}` / `{{object}}` interpolation. - The AI-model error spliced a conditional `(HTTP nnn)` fragment mid-sentence, which no translator can reorder. It is now two whole sentences. The chatbot banner still chooses between the server's `quota.message` (zh) and `quota.messageEn` — that pair is server-owned — but decides using the console's active language rather than `navigator.language`, which had ignored the in-app locale switcher entirely. CloudOnboardingNext's tests now render inside a real I18nProvider. Without one `t()` returns the raw key, so the previous assertions on literal English were asserting nothing — they failed the moment the component resolved properly, which is how I caught it. Same failure mode as EnvironmentListToolbar in part 1. The `startsWith('zh')` sites that remain are the ones classification marked KEEP: LoadingScreen (bootstrap, and it selects real locale packs), conversationLanguage (detects the chat's language for the agent, not UI copy), containers.tsx (normalises author-supplied schema data; `'与'` is a CJK typography rule), and the Studio/field-types data catalogs. Full suite 7664 passed. ESLint errors identical to baseline (25, all pre-existing). type-check clean except the pre-existing maplibre-gl error in plugin-map. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC --- .changeset/zh-branch-migration-part2.md | 51 +++++++++++++++++++ .../src/console/ai/ExcelImportBar.tsx | 27 ++++------ .../diagnostics/CloudAiModelStatus.tsx | 51 +++++++++---------- .../src/console/home/CloudOnboardingNext.tsx | 31 ++++------- .../__tests__/CloudOnboardingNext.test.tsx | 22 ++++++-- packages/i18n/src/locales/ar.ts | 36 +++++++++++++ packages/i18n/src/locales/de.ts | 36 +++++++++++++ packages/i18n/src/locales/en.ts | 36 +++++++++++++ packages/i18n/src/locales/es.ts | 36 +++++++++++++ packages/i18n/src/locales/fr.ts | 36 +++++++++++++ packages/i18n/src/locales/ja.ts | 36 +++++++++++++ packages/i18n/src/locales/ko.ts | 36 +++++++++++++ packages/i18n/src/locales/pt.ts | 36 +++++++++++++ packages/i18n/src/locales/ru.ts | 36 +++++++++++++ packages/i18n/src/locales/zh.ts | 36 +++++++++++++ packages/plugin-chatbot/package.json | 1 + .../plugin-chatbot/src/ChatbotEnhanced.tsx | 18 ++++--- pnpm-lock.yaml | 3 ++ 18 files changed, 485 insertions(+), 79 deletions(-) create mode 100644 .changeset/zh-branch-migration-part2.md diff --git a/.changeset/zh-branch-migration-part2.md b/.changeset/zh-branch-migration-part2.md new file mode 100644 index 000000000..868781986 --- /dev/null +++ b/.changeset/zh-branch-migration-part2.md @@ -0,0 +1,51 @@ +--- +"@object-ui/i18n": patch +"@object-ui/app-shell": patch +"@object-ui/plugin-chatbot": patch +--- + +fix(i18n): delete the four `pick({en,zh})` clones (objectui#2871, part 2) + +Four files each carried an identical private resolver: + +```ts +function pick(label: I18n): string { + const lang = document.documentElement.getAttribute('lang') || 'en'; + return lang.toLowerCase().startsWith('zh') ? label.zh : label.en; +} +``` + +Only Chinese was ever handled, so ja/ko/de/fr/es/pt/ru/ar silently rendered +English — and because the copy was baked into the components as inline +`{en, zh}` pairs, no translator could reach it. All four copies are deleted +along with their `I18n` type alias. + +Migrated to the locale packs, **all ten languages**: + +- `excelImport.*` (8 keys) — `ExcelImportBar`. The completion toast becomes a + proper `{{count}}` / `{{object}}` interpolation instead of a template literal + baked into both language variants. +- `cloudOnboarding.*` (5 keys) — `CloudOnboardingNext`, the Cloud welcome page. +- `aiModelStatus.*` (11 keys) — `CloudAiModelStatus`, including the + `sourceLabel()` enum→prose helper (now `t`-driven with a `{{source}}` + placeholder) and the three `ModelRow` labels. The conditional + `(HTTP nnn)` fragment becomes two whole sentences rather than a string + spliced mid-clause, which is not translatable into every word order. +- `chatbotQuota.*` (4 keys) — the AI quota banner in `ChatbotEnhanced`. + +The chatbot banner keeps choosing between the server's `quota.message` (zh) and +`quota.messageEn` — that pair is server-owned — but now decides using the +console's active language instead of `navigator.language`, which had ignored +the in-app locale switcher entirely. + +`CloudOnboardingNext`'s tests now render inside a real `I18nProvider`; without +one `t()` returns the raw key, so the previous assertions on literal English +were asserting nothing. + +This completes the `pick()` cluster from #2871. The remaining +`startsWith('zh')` sites are the ones that classification marked KEEP — +`LoadingScreen` (bootstrap, selects real locale packs before i18next is up), +`conversationLanguage` (detects the chat's language for the agent, not UI +copy), `containers.tsx` (normalises author-supplied schema data; its `'与'` +separator is a CJK typography rule), and the Studio catalog / `field-types.ts` +data catalog. diff --git a/packages/app-shell/src/console/ai/ExcelImportBar.tsx b/packages/app-shell/src/console/ai/ExcelImportBar.tsx index 79a2578ab..f6944c0c3 100644 --- a/packages/app-shell/src/console/ai/ExcelImportBar.tsx +++ b/packages/app-shell/src/console/ai/ExcelImportBar.tsx @@ -18,10 +18,9 @@ import { useEffect, useMemo, useState } from 'react'; import { Button, Badge } from '@object-ui/components'; import { createAuthenticatedFetch } from '@object-ui/auth'; import { toast } from 'sonner'; +import { useObjectTranslation } from '@object-ui/i18n'; import { ImportWizard } from '@object-ui/plugin-grid'; -type I18n = { en: string; zh: string }; - interface WizardField { name: string; label: string; @@ -41,12 +40,6 @@ interface ExcelImportBarProps { onDone: () => void; } -function pick(label: I18n): string { - const lang = - (typeof document !== 'undefined' && document.documentElement.getAttribute('lang')) || 'en'; - return lang.toLowerCase().startsWith('zh') ? label.zh : label.en; -} - const SKIP_OBJECT_PREFIXES = ['sys_', 'ai_', 'cloud_']; const NON_WRITABLE_TYPES = ['formula', 'summary', 'autonumber']; @@ -68,6 +61,7 @@ function normalizeFields(schema: any): WizardField[] { } export function ExcelImportBar({ file, dataSource, defaultObjectName, onDone }: ExcelImportBarProps) { + const { t } = useObjectTranslation(); const authFetch = useMemo(() => createAuthenticatedFetch(), []); const [objects, setObjects] = useState | null>(null); const [selected, setSelected] = useState(defaultObjectName ?? ''); @@ -109,13 +103,13 @@ export function ExcelImportBar({ file, dataSource, defaultObjectName, onDone }: const schema = await dataSource.getObjectSchema(selected); const f = normalizeFields(schema); if (!f.length) { - toast.error(pick({ en: 'That object has no importable fields.', zh: '该对象没有可导入的字段。' })); + toast.error(t('excelImport.noImportableFields')); return; } setFields(f); setOpen(true); } catch { - toast.error(pick({ en: 'Could not read the object schema.', zh: '无法读取对象结构。' })); + toast.error(t('excelImport.schemaReadFailed')); } finally { setLoadingFields(false); } @@ -135,10 +129,7 @@ export function ExcelImportBar({ file, dataSource, defaultObjectName, onDone }: initialFile={file} onComplete={(result) => { toast.success( - pick({ - en: `Imported ${result.importedRows} row(s) into ${selectedLabel}.`, - zh: `已把 ${result.importedRows} 行真实数据导入「${selectedLabel}」。`, - }), + t('excelImport.imported', { count: result.importedRows, object: selectedLabel }), ); }} /> @@ -149,10 +140,10 @@ export function ExcelImportBar({ file, dataSource, defaultObjectName, onDone }:
CSV / Excel - {pick({ en: 'Import the real rows from', zh: '把真实数据导入自' })} + {t('excelImport.importFrom')} {file.name} - {pick({ en: 'into', zh: '到' })} + {t('excelImport.into')}