Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .changeset/zh-branch-migration-part3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
"@object-ui/i18n": patch
"@object-ui/components": patch
"@object-ui/app-shell": patch
---

fix(i18n): close the last three zh-branch gaps (objectui#2871, part 3)

The three items the #2871 classification marked as real but *not* a
migrate-the-copy fix. Each needed a different remedy.

**`LoadingScreen` — ten languages collapsed to two.** The boot splash already
selected real locale packs (not inline copy), but through
`lang.startsWith('zh') ? zh : en`, so a ja/ko/de user watched the whole startup
in English. It now indexes `builtInLocales` by the two-letter prefix.

Each field falls back to `en` **individually**, which matters: `console.*` is
one of the namespaces that trails in the non-`zh` packs (objectui#2872 part a),
so a whole-object swap would have rendered `undefined` on the splash rather
than English. `console.loadingHint` was in fact missing from all eight — added
here, since a blank line under the progress list is worse than an English one.

**`containers.tsx` — two language sources that could disagree.** The tab-label
call sites resolved `language` from `useObjectTranslation()`, then handed the
string to `translateLabel`, which called `detectLocale()` and read
`document.documentElement.lang` on its own. Those update independently, so an
in-app language switch could leave a tab label and its surrounding chrome in
different languages until the next reload. `language` is now threaded in, and
`detectLocale` is deleted so nothing reaches for the DOM again.

**`field-types.ts` — a two-language data catalog.** `FieldTypeMeta` carried a
`labelZh` column beside `label`, which capped the field-type picker at English
or Chinese by construction. The 46 type names and 9 category names move into
the Studio catalog as `engine.fieldType.<id>` / `engine.fieldCategory.<cat>`,
generated from the existing values so no wording changes. This removes the
`isZh` helper from **both** `ObjectFieldInspector` and `ObjectFormCanvas` — the
two files the classification listed as "keep the component, fix the catalog".

The picker's search filter previously 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.
31 changes: 23 additions & 8 deletions packages/app-shell/src/chrome/LoadingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Spinner, Button } from '@object-ui/components';
import { Database, CheckCircle2, Loader2, AlertCircle, RefreshCw } from 'lucide-react';
import { useState, useEffect, useMemo } from 'react';
import { getProductName, getLogoUrl } from '../runtime-config';
import { en as enLocale, zh as zhLocale } from '@object-ui/i18n';
import { en as enLocale, builtInLocales } from '@object-ui/i18n';

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

export function LoadingScreen({ message, error, onRetry, retrying }: LoadingScreenProps) {
Expand Down
110 changes: 110 additions & 0 deletions packages/app-shell/src/views/metadata-admin/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,61 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
'engine.studio.toggleRail': 'Toggle sidebar',
'engine.studio.home': 'Back to home',
// Pillar tab labels
'engine.fieldType.text': 'Text',
'engine.fieldType.textarea': 'Text Area',
'engine.fieldType.email': 'Email',
'engine.fieldType.url': 'URL',
'engine.fieldType.phone': 'Phone',
'engine.fieldType.password': 'Password',
'engine.fieldType.markdown': 'Markdown',
'engine.fieldType.html': 'HTML',
'engine.fieldType.richtext': 'Rich Text',
'engine.fieldType.number': 'Number',
'engine.fieldType.currency': 'Currency',
'engine.fieldType.percent': 'Percent',
'engine.fieldType.date': 'Date',
'engine.fieldType.datetime': 'Date/Time',
'engine.fieldType.time': 'Time',
'engine.fieldType.boolean': 'Checkbox',
'engine.fieldType.toggle': 'Toggle',
'engine.fieldType.select': 'Picklist',
'engine.fieldType.multiselect': 'Multi-Select',
'engine.fieldType.radio': 'Radio',
'engine.fieldType.checkboxes': 'Checkboxes',
'engine.fieldType.lookup': 'Lookup',
'engine.fieldType.master_detail': 'Master-Detail',
'engine.fieldType.tree': 'Tree',
'engine.fieldType.image': 'Image',
'engine.fieldType.file': 'File',
'engine.fieldType.avatar': 'Avatar',
'engine.fieldType.video': 'Video',
'engine.fieldType.audio': 'Audio',
'engine.fieldType.formula': 'Formula',
'engine.fieldType.summary': 'Rollup',
'engine.fieldType.autonumber': 'Auto Number',
'engine.fieldType.composite': 'Composite',
'engine.fieldType.repeater': 'Repeater',
'engine.fieldType.location': 'Location',
'engine.fieldType.address': 'Address',
'engine.fieldType.code': 'Code',
'engine.fieldType.json': 'JSON',
'engine.fieldType.color': 'Color',
'engine.fieldType.rating': 'Rating',
'engine.fieldType.slider': 'Slider',
'engine.fieldType.signature': 'Signature',
'engine.fieldType.qrcode': 'QR Code',
'engine.fieldType.progress': 'Progress',
'engine.fieldType.tags': 'Tags',
'engine.fieldType.vector': 'Vector',
'engine.fieldCategory.text': 'Text',
'engine.fieldCategory.number': 'Number',
'engine.fieldCategory.date': 'Date / time',
'engine.fieldCategory.logic': 'Logic',
'engine.fieldCategory.selection': 'Selection',
'engine.fieldCategory.relation': 'Relation',
'engine.fieldCategory.media': 'Media',
'engine.fieldCategory.calculated': 'Calculated',
'engine.fieldCategory.advanced': 'Advanced',
'engine.studio.aiCopilot': 'AI copilot',
'engine.studio.pillar.data': 'Data',
'engine.studio.pillar.automations': 'Automations',
Expand Down Expand Up @@ -2742,6 +2797,61 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
'engine.studio.toggleRail': '切换侧栏',
'engine.studio.home': '返回主页',
// Pillar tab labels
'engine.fieldType.text': '单行文本',
'engine.fieldType.textarea': '多行文本',
'engine.fieldType.email': '邮箱',
'engine.fieldType.url': '网址',
'engine.fieldType.phone': '电话',
'engine.fieldType.password': '密码',
'engine.fieldType.markdown': 'Markdown',
'engine.fieldType.html': 'HTML',
'engine.fieldType.richtext': '富文本',
'engine.fieldType.number': '数字',
'engine.fieldType.currency': '货币',
'engine.fieldType.percent': '百分比',
'engine.fieldType.date': '日期',
'engine.fieldType.datetime': '日期时间',
'engine.fieldType.time': '时间',
'engine.fieldType.boolean': '复选框',
'engine.fieldType.toggle': '开关',
'engine.fieldType.select': '下拉选择',
'engine.fieldType.multiselect': '多选',
'engine.fieldType.radio': '单选',
'engine.fieldType.checkboxes': '复选组',
'engine.fieldType.lookup': '查找关系',
'engine.fieldType.master_detail': '主从关系',
'engine.fieldType.tree': '树形关系',
'engine.fieldType.image': '图片',
'engine.fieldType.file': '文件',
'engine.fieldType.avatar': '头像',
'engine.fieldType.video': '视频',
'engine.fieldType.audio': '音频',
'engine.fieldType.formula': '公式',
'engine.fieldType.summary': '汇总',
'engine.fieldType.autonumber': '自动编号',
'engine.fieldType.composite': '复合字段',
'engine.fieldType.repeater': '重复字段',
'engine.fieldType.location': '地理坐标',
'engine.fieldType.address': '地址',
'engine.fieldType.code': '代码',
'engine.fieldType.json': 'JSON',
'engine.fieldType.color': '颜色',
'engine.fieldType.rating': '评分',
'engine.fieldType.slider': '滑块',
'engine.fieldType.signature': '签名',
'engine.fieldType.qrcode': '二维码',
'engine.fieldType.progress': '进度条',
'engine.fieldType.tags': '标签',
'engine.fieldType.vector': '向量',
'engine.fieldCategory.text': '文本',
'engine.fieldCategory.number': '数值',
'engine.fieldCategory.date': '日期/时间',
'engine.fieldCategory.logic': '逻辑',
'engine.fieldCategory.selection': '选择',
'engine.fieldCategory.relation': '关系',
'engine.fieldCategory.media': '媒体',
'engine.fieldCategory.calculated': '计算',
'engine.fieldCategory.advanced': '高级',
'engine.studio.aiCopilot': 'AI 副驾',
'engine.studio.pillar.data': '数据',
'engine.studio.pillar.automations': '自动化',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,11 @@ import {
import {
FIELD_TYPE_META,
TYPES_BY_CATEGORY,
CATEGORY_LABEL_EN,
CATEGORY_LABEL_ZH,
type FieldTypeId,
} from '../previews/field-types';
import { CelPredicateField } from '../CelPredicateField';
import { t, tFormat } from '../i18n';

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

interface Option {
value: string;
Expand Down Expand Up @@ -162,13 +159,14 @@ function writePredicate(orig: unknown, next: string): unknown {
}

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

Expand Down Expand Up @@ -350,7 +348,7 @@ export function ObjectFieldInspector({
/>
);

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

return (
<InspectorShell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ import {
import {
FIELD_TYPE_META,
TYPES_BY_CATEGORY,
CATEGORY_LABEL_EN,
CATEGORY_LABEL_ZH,
CATEGORY_TONE,
type FieldTypeId,
type FieldTypeMeta,
Expand All @@ -77,12 +75,13 @@ import { FieldStub } from './FieldStub';
import { t, tFormat } from '../i18n';

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

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