Skip to content

Commit dc334da

Browse files
os-zhuangclaude
andauthored
fix(i18n): close the last three zh-branch gaps (#2871, part 3) (#2898)
The three items the classification marked as real but not a migrate-the-copy fix. Each needed a different remedy. LoadingScreen collapsed ten languages into two. It already selected real locale packs rather than inline copy, but via `startsWith('zh') ? zh : en`, so a ja/ko/de user watched the entire boot in English. Now indexes builtInLocales by two-letter prefix, with a PER-FIELD fallback to `en` — `console.*` is one of the namespaces trailing in the non-zh packs (#2872 part a), so a whole-object swap would have rendered `undefined` on the splash instead of English. `console.loadingHint` was in fact missing from all eight; added, because a blank line under the progress list is worse than an English one. containers.tsx had two language sources that could disagree: the call sites resolved `language` from useObjectTranslation, then translateLabel called detectLocale() and read document.documentElement.lang itself. Those update independently, so an in-app language switch could leave a tab label and its chrome in different languages until reload. `language` is threaded in and detectLocale is deleted so nothing reaches for the DOM again. field-types.ts carried a `labelZh` column beside `label`, capping the field-type picker at two languages by construction. The 46 type names and 9 category names move to the Studio catalog as `engine.fieldType.<id>` / `engine.fieldCategory.<cat>`, generated from the existing values so no wording changes. This removes `isZh` from BOTH ObjectFieldInspector and ObjectFormCanvas — the two files classified as "keep the component, fix the catalog". Also caught while there: the picker's search filter matched id, the English label, and `labelZh`, so searching in Japanese or German matched nothing. It now matches the label as the user actually sees it. Full suite 7730 passed. ESLint errors identical to baseline (25, all pre-existing). turbo type-check 31/31 for the changed packages. Claude-Session: https://claude.ai/code/session_01TubWYdWquVkS9dj733sDmC Co-authored-by: Claude <noreply@anthropic.com>
1 parent 54886ca commit dc334da

15 files changed

Lines changed: 273 additions & 105 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@object-ui/i18n": patch
3+
"@object-ui/components": patch
4+
"@object-ui/app-shell": patch
5+
---
6+
7+
fix(i18n): close the last three zh-branch gaps (objectui#2871, part 3)
8+
9+
The three items the #2871 classification marked as real but *not* a
10+
migrate-the-copy fix. Each needed a different remedy.
11+
12+
**`LoadingScreen` — ten languages collapsed to two.** The boot splash already
13+
selected real locale packs (not inline copy), but through
14+
`lang.startsWith('zh') ? zh : en`, so a ja/ko/de user watched the whole startup
15+
in English. It now indexes `builtInLocales` by the two-letter prefix.
16+
17+
Each field falls back to `en` **individually**, which matters: `console.*` is
18+
one of the namespaces that trails in the non-`zh` packs (objectui#2872 part a),
19+
so a whole-object swap would have rendered `undefined` on the splash rather
20+
than English. `console.loadingHint` was in fact missing from all eight — added
21+
here, since a blank line under the progress list is worse than an English one.
22+
23+
**`containers.tsx` — two language sources that could disagree.** The tab-label
24+
call sites resolved `language` from `useObjectTranslation()`, then handed the
25+
string to `translateLabel`, which called `detectLocale()` and read
26+
`document.documentElement.lang` on its own. Those update independently, so an
27+
in-app language switch could leave a tab label and its surrounding chrome in
28+
different languages until the next reload. `language` is now threaded in, and
29+
`detectLocale` is deleted so nothing reaches for the DOM again.
30+
31+
**`field-types.ts` — a two-language data catalog.** `FieldTypeMeta` carried a
32+
`labelZh` column beside `label`, which capped the field-type picker at English
33+
or Chinese by construction. The 46 type names and 9 category names move into
34+
the Studio catalog as `engine.fieldType.<id>` / `engine.fieldCategory.<cat>`,
35+
generated from the existing values so no wording changes. This removes the
36+
`isZh` helper from **both** `ObjectFieldInspector` and `ObjectFormCanvas` — the
37+
two files the classification listed as "keep the component, fix the catalog".
38+
39+
The picker's search filter previously matched `id`, the English label, and
40+
`labelZh` — so searching in Japanese or German matched nothing. It now matches
41+
the label as the user actually sees it.

packages/app-shell/src/chrome/LoadingScreen.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Spinner, Button } from '@object-ui/components';
33
import { Database, CheckCircle2, Loader2, AlertCircle, RefreshCw } from 'lucide-react';
44
import { useState, useEffect, useMemo } from 'react';
55
import { getProductName, getLogoUrl } from '../runtime-config';
6-
import { en as enLocale, zh as zhLocale } from '@object-ui/i18n';
6+
import { en as enLocale, builtInLocales } from '@object-ui/i18n';
77

88
interface LoadingScreenProps {
99
/** Optional message override */
@@ -23,14 +23,29 @@ interface LoadingScreenProps {
2323
// synchronous dictionary for the startup shell instead.
2424
// The product name is read from the runtime-config singleton (sync) so it
2525
// reflects server-pushed branding when available, falling back to 'ObjectOS'.
26+
//
27+
// Indexed by the two-letter prefix across ALL built-in packs rather than a
28+
// `zh ? … : en` check: the previous form collapsed ten shipped languages into
29+
// two, so a ja/ko/de user saw English for the whole boot (objectui#2871).
30+
//
31+
// Each field falls back to `en` individually. A pack that is behind on some
32+
// `console.*` keys — several are, see objectui#2872 part (a) — must degrade to
33+
// English, not to `undefined`, which would render blank on the splash.
2634
function getStartupStrings() {
27-
if (typeof document !== 'undefined' && document.documentElement.lang?.startsWith('zh')) {
28-
return zhLocale.console;
29-
}
30-
if (typeof navigator !== 'undefined' && navigator.language?.startsWith('zh')) {
31-
return zhLocale.console;
32-
}
33-
return enLocale.console;
35+
const tag =
36+
(typeof document !== 'undefined' ? document.documentElement.lang : '') ||
37+
(typeof navigator !== 'undefined' ? navigator.language : '') ||
38+
'en';
39+
const base = tag.toLowerCase().split('-')[0] as keyof typeof builtInLocales;
40+
const pack = builtInLocales[base]?.console;
41+
if (!pack || pack === enLocale.console) return enLocale.console;
42+
return {
43+
...enLocale.console,
44+
...pack,
45+
loadingSteps: { ...enLocale.console.loadingSteps, ...pack.loadingSteps },
46+
error: { ...enLocale.console.error, ...pack.error },
47+
actions: { ...enLocale.console.actions, ...pack.actions },
48+
};
3449
}
3550

3651
export function LoadingScreen({ message, error, onRetry, retrying }: LoadingScreenProps) {

packages/app-shell/src/views/metadata-admin/i18n.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,61 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
12071207
'engine.studio.toggleRail': 'Toggle sidebar',
12081208
'engine.studio.home': 'Back to home',
12091209
// Pillar tab labels
1210+
'engine.fieldType.text': 'Text',
1211+
'engine.fieldType.textarea': 'Text Area',
1212+
'engine.fieldType.email': 'Email',
1213+
'engine.fieldType.url': 'URL',
1214+
'engine.fieldType.phone': 'Phone',
1215+
'engine.fieldType.password': 'Password',
1216+
'engine.fieldType.markdown': 'Markdown',
1217+
'engine.fieldType.html': 'HTML',
1218+
'engine.fieldType.richtext': 'Rich Text',
1219+
'engine.fieldType.number': 'Number',
1220+
'engine.fieldType.currency': 'Currency',
1221+
'engine.fieldType.percent': 'Percent',
1222+
'engine.fieldType.date': 'Date',
1223+
'engine.fieldType.datetime': 'Date/Time',
1224+
'engine.fieldType.time': 'Time',
1225+
'engine.fieldType.boolean': 'Checkbox',
1226+
'engine.fieldType.toggle': 'Toggle',
1227+
'engine.fieldType.select': 'Picklist',
1228+
'engine.fieldType.multiselect': 'Multi-Select',
1229+
'engine.fieldType.radio': 'Radio',
1230+
'engine.fieldType.checkboxes': 'Checkboxes',
1231+
'engine.fieldType.lookup': 'Lookup',
1232+
'engine.fieldType.master_detail': 'Master-Detail',
1233+
'engine.fieldType.tree': 'Tree',
1234+
'engine.fieldType.image': 'Image',
1235+
'engine.fieldType.file': 'File',
1236+
'engine.fieldType.avatar': 'Avatar',
1237+
'engine.fieldType.video': 'Video',
1238+
'engine.fieldType.audio': 'Audio',
1239+
'engine.fieldType.formula': 'Formula',
1240+
'engine.fieldType.summary': 'Rollup',
1241+
'engine.fieldType.autonumber': 'Auto Number',
1242+
'engine.fieldType.composite': 'Composite',
1243+
'engine.fieldType.repeater': 'Repeater',
1244+
'engine.fieldType.location': 'Location',
1245+
'engine.fieldType.address': 'Address',
1246+
'engine.fieldType.code': 'Code',
1247+
'engine.fieldType.json': 'JSON',
1248+
'engine.fieldType.color': 'Color',
1249+
'engine.fieldType.rating': 'Rating',
1250+
'engine.fieldType.slider': 'Slider',
1251+
'engine.fieldType.signature': 'Signature',
1252+
'engine.fieldType.qrcode': 'QR Code',
1253+
'engine.fieldType.progress': 'Progress',
1254+
'engine.fieldType.tags': 'Tags',
1255+
'engine.fieldType.vector': 'Vector',
1256+
'engine.fieldCategory.text': 'Text',
1257+
'engine.fieldCategory.number': 'Number',
1258+
'engine.fieldCategory.date': 'Date / time',
1259+
'engine.fieldCategory.logic': 'Logic',
1260+
'engine.fieldCategory.selection': 'Selection',
1261+
'engine.fieldCategory.relation': 'Relation',
1262+
'engine.fieldCategory.media': 'Media',
1263+
'engine.fieldCategory.calculated': 'Calculated',
1264+
'engine.fieldCategory.advanced': 'Advanced',
12101265
'engine.studio.aiCopilot': 'AI copilot',
12111266
'engine.studio.pillar.data': 'Data',
12121267
'engine.studio.pillar.automations': 'Automations',
@@ -2742,6 +2797,61 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
27422797
'engine.studio.toggleRail': '切换侧栏',
27432798
'engine.studio.home': '返回主页',
27442799
// Pillar tab labels
2800+
'engine.fieldType.text': '单行文本',
2801+
'engine.fieldType.textarea': '多行文本',
2802+
'engine.fieldType.email': '邮箱',
2803+
'engine.fieldType.url': '网址',
2804+
'engine.fieldType.phone': '电话',
2805+
'engine.fieldType.password': '密码',
2806+
'engine.fieldType.markdown': 'Markdown',
2807+
'engine.fieldType.html': 'HTML',
2808+
'engine.fieldType.richtext': '富文本',
2809+
'engine.fieldType.number': '数字',
2810+
'engine.fieldType.currency': '货币',
2811+
'engine.fieldType.percent': '百分比',
2812+
'engine.fieldType.date': '日期',
2813+
'engine.fieldType.datetime': '日期时间',
2814+
'engine.fieldType.time': '时间',
2815+
'engine.fieldType.boolean': '复选框',
2816+
'engine.fieldType.toggle': '开关',
2817+
'engine.fieldType.select': '下拉选择',
2818+
'engine.fieldType.multiselect': '多选',
2819+
'engine.fieldType.radio': '单选',
2820+
'engine.fieldType.checkboxes': '复选组',
2821+
'engine.fieldType.lookup': '查找关系',
2822+
'engine.fieldType.master_detail': '主从关系',
2823+
'engine.fieldType.tree': '树形关系',
2824+
'engine.fieldType.image': '图片',
2825+
'engine.fieldType.file': '文件',
2826+
'engine.fieldType.avatar': '头像',
2827+
'engine.fieldType.video': '视频',
2828+
'engine.fieldType.audio': '音频',
2829+
'engine.fieldType.formula': '公式',
2830+
'engine.fieldType.summary': '汇总',
2831+
'engine.fieldType.autonumber': '自动编号',
2832+
'engine.fieldType.composite': '复合字段',
2833+
'engine.fieldType.repeater': '重复字段',
2834+
'engine.fieldType.location': '地理坐标',
2835+
'engine.fieldType.address': '地址',
2836+
'engine.fieldType.code': '代码',
2837+
'engine.fieldType.json': 'JSON',
2838+
'engine.fieldType.color': '颜色',
2839+
'engine.fieldType.rating': '评分',
2840+
'engine.fieldType.slider': '滑块',
2841+
'engine.fieldType.signature': '签名',
2842+
'engine.fieldType.qrcode': '二维码',
2843+
'engine.fieldType.progress': '进度条',
2844+
'engine.fieldType.tags': '标签',
2845+
'engine.fieldType.vector': '向量',
2846+
'engine.fieldCategory.text': '文本',
2847+
'engine.fieldCategory.number': '数值',
2848+
'engine.fieldCategory.date': '日期/时间',
2849+
'engine.fieldCategory.logic': '逻辑',
2850+
'engine.fieldCategory.selection': '选择',
2851+
'engine.fieldCategory.relation': '关系',
2852+
'engine.fieldCategory.media': '媒体',
2853+
'engine.fieldCategory.calculated': '计算',
2854+
'engine.fieldCategory.advanced': '高级',
27452855
'engine.studio.aiCopilot': 'AI 副驾',
27462856
'engine.studio.pillar.data': '数据',
27472857
'engine.studio.pillar.automations': '自动化',

packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,11 @@ import {
5151
import {
5252
FIELD_TYPE_META,
5353
TYPES_BY_CATEGORY,
54-
CATEGORY_LABEL_EN,
55-
CATEGORY_LABEL_ZH,
5654
type FieldTypeId,
5755
} from '../previews/field-types';
5856
import { CelPredicateField } from '../CelPredicateField';
5957
import { t, tFormat } from '../i18n';
6058

61-
const isZh = (locale?: string) => (locale ?? '').toLowerCase().startsWith('zh');
6259

6360
interface Option {
6461
value: string;
@@ -162,13 +159,14 @@ function writePredicate(orig: unknown, next: string): unknown {
162159
}
163160

164161
function buildTypeOptions(locale?: string): Array<{ value: string; label: string }> {
165-
const zh = (locale ?? '').toLowerCase().startsWith('zh');
166-
const cats = zh ? CATEGORY_LABEL_ZH : CATEGORY_LABEL_EN;
162+
// Type and category names come from the Studio catalog rather than the
163+
// `labelZh` column that used to sit on FIELD_TYPE_META, so this no longer
164+
// needs a zh/en branch (objectui#2871).
167165
return TYPES_BY_CATEGORY.flatMap((g) =>
168-
g.types.map((id) => {
169-
const m = FIELD_TYPE_META[id];
170-
return { value: id, label: `${cats[g.category]} · ${zh ? m.labelZh : m.label}` };
171-
}),
166+
g.types.map((id) => ({
167+
value: id,
168+
label: `${t(`engine.fieldCategory.${g.category}`, locale)} · ${t(`engine.fieldType.${id}`, locale)}`,
169+
})),
172170
);
173171
}
174172

@@ -350,7 +348,7 @@ export function ObjectFieldInspector({
350348
/>
351349
);
352350

353-
const typeMetaLabel = isZh(locale) ? typeMeta?.labelZh : typeMeta?.label;
351+
const typeMetaLabel = typeMeta ? t(`engine.fieldType.${typeMeta.id}`, locale) : undefined;
354352

355353
return (
356354
<InspectorShell

packages/app-shell/src/views/metadata-admin/previews/ObjectFormCanvas.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ import {
6666
import {
6767
FIELD_TYPE_META,
6868
TYPES_BY_CATEGORY,
69-
CATEGORY_LABEL_EN,
70-
CATEGORY_LABEL_ZH,
7169
CATEGORY_TONE,
7270
type FieldTypeId,
7371
type FieldTypeMeta,
@@ -77,12 +75,13 @@ import { FieldStub } from './FieldStub';
7775
import { t, tFormat } from '../i18n';
7876

7977
/* ─── locale helpers ─── */
80-
const isZh = (locale?: string) => (locale ?? '').toLowerCase().startsWith('zh');
81-
/** Field-type display label in the active locale (data carries both). */
78+
// Both resolve through the Studio catalog now. They used to read a `labelZh`
79+
// column on FIELD_TYPE_META behind an `isZh` check, which meant the picker
80+
// only ever spoke English or Chinese (objectui#2871).
8281
const typeLabel = (meta: FieldTypeMeta | undefined, locale?: string): string | undefined =>
83-
meta ? (isZh(locale) ? meta.labelZh : meta.label) : undefined;
82+
meta ? t(`engine.fieldType.${meta.id}`, locale) : undefined;
8483
const categoryLabel = (cat: FieldTypeCategory, locale?: string): string =>
85-
(isZh(locale) ? CATEGORY_LABEL_ZH : CATEGORY_LABEL_EN)[cat];
84+
t(`engine.fieldCategory.${cat}`, locale);
8685

8786
export interface ObjectFormCanvasProps {
8887
objectName: string;
@@ -1274,7 +1273,11 @@ function AddFieldButton({ onPick, compact, locale }: { onPick: (type: FieldTypeI
12741273
category: g.category,
12751274
types: g.types.filter((id) => {
12761275
const m = FIELD_TYPE_META[id];
1277-
return id.includes(q) || m.label.toLowerCase().includes(q) || m.labelZh.includes(filter.trim());
1276+
// Match the id, the English label, and the label as the user
1277+
// actually sees it — previously the third arm was hard-wired to
1278+
// Chinese, so searching in ja/de matched nothing (objectui#2871).
1279+
const shown = typeLabel(m, locale) ?? '';
1280+
return id.includes(q) || m.label.toLowerCase().includes(q) || shown.toLowerCase().includes(q);
12781281
}),
12791282
}))
12801283
.filter((g) => g.types.length > 0);

0 commit comments

Comments
 (0)