Skip to content

Commit 4dcd52a

Browse files
yinlianghuiclaude
andauthored
fix(i18n): 八包补上 console.objectView 两个 key 的真译,systemViewReadonly 按 en 当前的 read-only 语义 (#3582) (#3627)
`console.objectView.systemViewReadonly` / `expandToPage` 在 ja/ko/de/fr/es/pt/ ru/ar 八个包里存的是英文;`systemViewReadonly` 存的还是 en 自己已经废弃的旧话 (`- duplicate to customize.`),把八种语言的用户指向一条产品已不提供的操作路径。 八包按 en 当前语义重译,en / zh 一字节不动,不增删任何 key。 译文取自各包自己的既有措辞而非自创:`view.readonlyTooltip` 供「系统视图」、 `console.objectView.cannotEditMetaView`(#3583 落的)供「由代码定义」; `expandToPage` 对齐同包 `detail.openAsFullPage` —— 那是隔壁命名空间里 一模一样的英文句子,十包早已译好,en/zh 两个 key 本就逐字节相同。 新增 objectView-value-language-3582.test.ts:钉住 en 字面量(下次改 en 文案 即红,逼九个译文包同批跟改)、断言除 en 外无包等于新旧任一句英文、要求 zh/ja/ko/ru/ar 的值含本文字系统字符。all-locales-key-parity 修前修后都绿 —— 它比的是键集合与占位符形状,这正是它看不见本缺陷的原因。 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2904a7c commit 4dcd52a

10 files changed

Lines changed: 203 additions & 16 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
`console.objectView.systemViewReadonly` and `console.objectView.expandToPage`
6+
are translated in the eight packs that stored English for them, so a Japanese,
7+
Korean, German, French, Spanish, Portuguese, Russian or Arabic session reads
8+
the system-view hint and the expand affordance in its own language (#3582).
9+
10+
This is a different defect class from #3546's ledger, and no gate in the repo
11+
could see it. There the key was *missing* from a pack and `fallbackLng: 'en'`
12+
rendered English; here the key was **present in all ten** and eight of them
13+
stored English as the value. `all-locales-key-parity` compares key *sets* and
14+
placeholder *shape* — identical before and after this change, and neither key
15+
interpolates anything. `scripts/check-i18n-call-site-keys.mjs` and its baseline
16+
ratchet ask whether a `t()` key exists in `en`; it did, so these two were never
17+
in the 258.
18+
19+
`systemViewReadonly` carried the sharper half: the eight packs did not hold
20+
`en`'s sentence, they held one `en` had already abandoned. `en` says the view
21+
is read-only; the eight said `System view defined in code - duplicate to
22+
customize.` — pointing eight locales at a duplicate-to-customize path the
23+
product no longer presents. They are translated against `en`'s **current**
24+
read-only meaning, not against the stale English they replaced.
25+
26+
Each value is built from terminology the same pack already uses rather than
27+
invented: `view.readonlyTooltip` supplies "system view" and
28+
`console.objectView.cannotEditMetaView` (landed in #3583) supplies "defined in
29+
code", so the new hint agrees with the copy beside it in every pack. For
30+
`expandToPage`, `detail.openAsFullPage` is the identical English sentence one
31+
namespace over and was already translated everywhere — `en` and `zh` hold their
32+
two byte-identical to each other, so the eight now do too, and one locale
33+
cannot end up with two different words for one action.
34+
35+
`en` and `zh` are unchanged, byte for byte. No key is added or removed —
36+
the diff is 16 values in 8 files. A new
37+
`objectView-value-language-3582.test.ts` pins the `en` literal (so the next
38+
rewording of `en` fails loudly instead of silently orphaning nine
39+
translations), asserts that no pack but `en` serves either English spelling,
40+
and requires the zh/ja/ko/ru/ar values to contain characters of their own
41+
script. The repo-wide "no ASCII English sentence in a non-Latin pack" gate that
42+
#3582 also sketched is deliberately **not** here; it is a separate, lands-green
43+
change.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* `console.objectView.systemViewReadonly` / `.expandToPage` hold real
3+
* translations in all ten packs — not English (objectui#3582).
4+
*
5+
* This is a different defect class from objectui#3546's ledger. There the key
6+
* was MISSING from a pack; here the key was PRESENT in all ten and eight of
7+
* them stored the English string as their "translation". Every existing gate
8+
* compares key *sets* or placeholder *shape*, so all of them stayed green:
9+
*
10+
* - `all-locales-key-parity` — key sets are identical, and neither key
11+
* interpolates anything, so its placeholder check has nothing to compare.
12+
* It is green before AND after this fix, by construction; that is exactly
13+
* why it could not see the bug.
14+
* - `scripts/check-i18n-call-site-keys.mjs` and its baseline ratchet — both
15+
* ask whether the key exists in `en`. It does. These two keys were never
16+
* in the ratchet.
17+
*
18+
* `systemViewReadonly` carried the sharper half: the eight packs did not hold
19+
* `en`'s current sentence, they held a STALE one `en` itself had already
20+
* abandoned — "duplicate to customize", a workflow the product no longer
21+
* offers. So eight locales were not merely untranslated, they described a
22+
* path that does not exist. The `en` pin below is the guard against a repeat:
23+
* change `en`'s copy again and this file goes red, which forces the eight to
24+
* be revisited in the same PR instead of drifting for another release.
25+
*/
26+
import { describe, it, expect } from 'vitest';
27+
import { builtInLocales } from '../locales';
28+
29+
const LANGS = Object.keys(builtInLocales);
30+
31+
const objectViewOf = (lang: string) =>
32+
(builtInLocales[lang] as any)?.console?.objectView as Record<string, string>;
33+
34+
const readonlyOf = (lang: string) => objectViewOf(lang)?.systemViewReadonly;
35+
const expandOf = (lang: string) => objectViewOf(lang)?.expandToPage;
36+
37+
/**
38+
* Both English spellings that must not survive outside `en`: the CURRENT `en`
39+
* copy, and the STALE sentence the eight packs stored before objectui#3582.
40+
* Checking only the current one would let the original defect back in
41+
* verbatim.
42+
*/
43+
const EN_CURRENT = {
44+
systemViewReadonly: 'System view defined in code — read-only.',
45+
expandToPage: 'Open as full page',
46+
} as const;
47+
const EN_STALE_READONLY = 'System view defined in code - duplicate to customize.';
48+
49+
/**
50+
* Script ranges for the packs where "is this value in the pack's language?" is
51+
* mechanically decidable. de/fr/es/pt share the Latin alphabet with `en`, so
52+
* they get the equality checks below instead — there is no clean positive
53+
* criterion for them, which is the reason objectui#3582 recorded a repo-wide
54+
* ASCII gate as a SEPARATE, non-acceptance item rather than folding it in here.
55+
*/
56+
const NATIVE_SCRIPT: Record<string, RegExp> = {
57+
zh: /\p{Script=Han}/u,
58+
ja: /\p{Script=Hiragana}|\p{Script=Katakana}|\p{Script=Han}/u,
59+
ko: /\p{Script=Hangul}/u,
60+
ru: /\p{Script=Cyrillic}/u,
61+
ar: /\p{Script=Arabic}/u,
62+
};
63+
64+
describe('console.objectView value-language coverage (objectui#3582)', () => {
65+
it('covers all ten built-in packs', () => {
66+
expect(LANGS).toHaveLength(10);
67+
});
68+
69+
it.each(LANGS)('%s defines both keys as non-empty strings', (lang) => {
70+
for (const key of ['systemViewReadonly', 'expandToPage'] as const) {
71+
const value = objectViewOf(lang)?.[key];
72+
expect(typeof value, `${lang}.console.objectView.${key}`).toBe('string');
73+
expect(value!.trim().length, `${lang}.console.objectView.${key} is empty`).toBeGreaterThan(0);
74+
}
75+
});
76+
77+
it('the en pack still says read-only, not "duplicate to customize"', () => {
78+
// The semantic the other nine were translated against. If this literal
79+
// changes, the nine translations below are stale by definition — that is
80+
// the failure this pin exists to make loud, and it is precisely what went
81+
// unnoticed when `en` was last reworded.
82+
expect(readonlyOf('en')).toBe(EN_CURRENT.systemViewReadonly);
83+
expect(expandOf('en')).toBe(EN_CURRENT.expandToPage);
84+
});
85+
86+
it('no pack but en serves either English spelling of systemViewReadonly', () => {
87+
const english = LANGS.filter(
88+
(l) =>
89+
l !== 'en' &&
90+
(readonlyOf(l) === EN_CURRENT.systemViewReadonly || readonlyOf(l) === EN_STALE_READONLY),
91+
);
92+
expect(english, `packs serving English: ${english.join(', ')}`).toEqual([]);
93+
});
94+
95+
it('no pack but en serves the English spelling of expandToPage', () => {
96+
const english = LANGS.filter((l) => l !== 'en' && expandOf(l) === EN_CURRENT.expandToPage);
97+
expect(english, `packs serving English: ${english.join(', ')}`).toEqual([]);
98+
});
99+
100+
it('the retired "duplicate to customize" workflow is named by no pack', () => {
101+
// Scoped to these two keys on purpose. The same stale claim still lives in
102+
// `view.readonlyTooltip`, TRANSLATED into all eight languages — objectui#3625.
103+
// Widening this regex to the whole pack would red on that one, which is a
104+
// separate fix; and note no equality-with-English or ASCII heuristic can
105+
// see it, because those eight values ARE idiomatic translations. They just
106+
// translate a sentence `en` retired.
107+
const leaked = LANGS.filter((l) => /duplicate to customi[sz]e/i.test(readonlyOf(l)!));
108+
expect(leaked).toEqual([]);
109+
});
110+
111+
it.each(Object.keys(NATIVE_SCRIPT))('%s writes both values in its own script', (lang) => {
112+
const re = NATIVE_SCRIPT[lang];
113+
expect(re.test(readonlyOf(lang)!), `${lang} systemViewReadonly has no native script`).toBe(true);
114+
expect(re.test(expandOf(lang)!), `${lang} expandToPage has no native script`).toBe(true);
115+
});
116+
117+
it('all ten packs differ from one another on both keys', () => {
118+
// The tightest statement of "nobody pasted one language into another".
119+
// These two sentences are legitimately distinct in all ten today; a future
120+
// translation that genuinely collides is a reason to relax this line with
121+
// a note, not to widen it silently.
122+
for (const [key, read] of [
123+
['systemViewReadonly', readonlyOf],
124+
['expandToPage', expandOf],
125+
] as const) {
126+
const values = LANGS.map(read);
127+
expect(new Set(values).size, `${key} has duplicate values across packs`).toBe(LANGS.length);
128+
}
129+
});
130+
131+
it('expandToPage matches its own pack detail.openAsFullPage, in every pack', () => {
132+
// `detail.openAsFullPage` is the SAME English sentence in a neighbouring
133+
// namespace ("Open as full page"), and it was already translated in all
134+
// ten while `console.objectView.expandToPage` was not. en and zh already
135+
// held the two byte-identical to each other; this keeps the eight that way
136+
// too, so the icon button and the expand affordance cannot drift into two
137+
// different words for one action in a single locale.
138+
for (const lang of LANGS) {
139+
expect(expandOf(lang), `${lang} expandToPage vs detail.openAsFullPage`).toBe(
140+
(builtInLocales[lang] as any).detail.openAsFullPage,
141+
);
142+
}
143+
});
144+
});

‎packages/i18n/src/locales/ar.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const ar = {
15271527
viewNameHelp: "مفتاح برمجي (snake_case). يُملأ تلقائيًا من العنوان — عدّله إن أردت.",
15281528
viewNameRequired: "أدخل مفتاحًا (أحرف إنجليزية صغيرة وأرقام وشرطة سفلية). لا يُملأ تلقائيًا للعناوين غير اللاتينية.",
15291529
viewNameInvalid: "استخدم أحرفًا إنجليزية صغيرة وأرقامًا وشرطات سفلية؛ وابدأ بحرف أو شرطة سفلية.",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "عرض النظام معرَّف في الكود — للقراءة فقط.",
15311531
cannotEditMetaView: "هذا عرض مدمج معرَّف في الكود — لا يمكن تعديله.",
15321532
cannotDeleteMetaView: "هذا عرض مدمج معرَّف في الكود — لا يمكن حذفه.",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "فتح كصفحة كاملة",
15341534
objectNotFound: "الكائن غير موجود",
15351535
objectNotFoundDescription: "الكائن \"{{objectName}}\" غير موجود في الإعدادات الحالية.",
15361536
objectNotFoundHint: "تحقق من إعدادات التنقل في تطبيقك أو اختر كائنًا آخر من الشريط الجانبي.",

‎packages/i18n/src/locales/de.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const de = {
15271527
viewNameHelp: "Technischer Schlüssel (snake_case). Wird aus dem Titel automatisch gefüllt — bei Bedarf anpassen.",
15281528
viewNameRequired: "Geben Sie einen Schlüssel ein (Kleinbuchstaben, Ziffern, Unterstrich). Bei nicht-lateinischen Titeln wird er nicht automatisch gefüllt.",
15291529
viewNameInvalid: "Verwenden Sie Kleinbuchstaben, Ziffern und Unterstriche; beginnen Sie mit einem Buchstaben oder Unterstrich.",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "Systemansicht — im Code definiert, schreibgeschützt.",
15311531
cannotEditMetaView: "Diese Ansicht ist integriert und im Code definiert – sie kann nicht geändert werden.",
15321532
cannotDeleteMetaView: "Diese Ansicht ist integriert und im Code definiert – sie kann nicht gelöscht werden.",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "Als ganze Seite öffnen",
15341534
objectNotFound: "Objekt nicht gefunden",
15351535
objectNotFoundDescription: "Das Objekt „{{objectName}}\" existiert in der aktuellen Konfiguration nicht.",
15361536
objectNotFoundHint: "Überprüfen Sie Ihre App-Navigationseinstellungen oder wählen Sie ein anderes Objekt aus der Seitenleiste.",

‎packages/i18n/src/locales/es.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,10 +1532,10 @@ const es = {
15321532
viewNameHelp: "Clave técnica (snake_case). Se rellena automáticamente a partir del título — edítela si lo desea.",
15331533
viewNameRequired: "Introduzca una clave (minúsculas, números, guion bajo). No se rellena automáticamente con títulos no latinos.",
15341534
viewNameInvalid: "Use minúsculas, números y guiones bajos; empiece por una letra o un guion bajo.",
1535-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1535+
systemViewReadonly: "Vista del sistema definida en el código: solo lectura.",
15361536
cannotEditMetaView: "Esta vista está integrada y definida en el código: no se puede modificar.",
15371537
cannotDeleteMetaView: "Esta vista está integrada y definida en el código: no se puede eliminar.",
1538-
expandToPage: 'Open as full page',
1538+
expandToPage: "Abrir como página completa",
15391539
objectNotFound: "Objeto no encontrado",
15401540
objectNotFoundDescription: "El objeto \"{{objectName}}\" no existe en la configuración actual.",
15411541
objectNotFoundHint: "Verifique la configuración de navegación de su aplicación o seleccione un objeto diferente en la barra lateral.",

‎packages/i18n/src/locales/fr.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const fr = {
15271527
viewNameHelp: "Clé technique (snake_case). Remplie automatiquement à partir du titre — modifiable si vous le souhaitez.",
15281528
viewNameRequired: "Saisissez une clé (minuscules, chiffres, tiret bas). Non remplie automatiquement pour les titres non latins.",
15291529
viewNameInvalid: "Utilisez des minuscules, des chiffres et des tirets bas ; commencez par une lettre ou un tiret bas.",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "Vue système définie dans le code — en lecture seule.",
15311531
cannotEditMetaView: "Cette vue est intégrée et définie dans le code — elle ne peut pas être modifiée.",
15321532
cannotDeleteMetaView: "Cette vue est intégrée et définie dans le code — elle ne peut pas être supprimée.",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "Ouvrir en pleine page",
15341534
objectNotFound: "Objet introuvable",
15351535
objectNotFoundDescription: "L'objet « {{objectName}} » n'existe pas dans la configuration actuelle.",
15361536
objectNotFoundHint: "Vérifiez les paramètres de navigation de votre application ou sélectionnez un autre objet dans la barre latérale.",

‎packages/i18n/src/locales/ja.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const ja = {
15271527
viewNameHelp: "マシンキー(snake_case)。タイトルから自動入力されます — 必要に応じて編集してください。",
15281528
viewNameRequired: "キーを入力してください(英小文字、数字、アンダースコア)。ラテン文字以外のタイトルでは自動入力されません。",
15291529
viewNameInvalid: "英小文字、数字、アンダースコアを使用し、先頭は文字かアンダースコアにしてください。",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "システムビューはコードで定義されており、読み取り専用です。",
15311531
cannotEditMetaView: "このビューはコードで定義された組み込みビューのため、変更できません。",
15321532
cannotDeleteMetaView: "このビューはコードで定義された組み込みビューのため、削除できません。",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "フルページで開く",
15341534
objectNotFound: "オブジェクトが見つかりません",
15351535
objectNotFoundDescription: "オブジェクト「{{objectName}}」は現在の設定に存在しません。",
15361536
objectNotFoundHint: "アプリのナビゲーション設定を確認するか、サイドバーから別のオブジェクトを選択してください。",

‎packages/i18n/src/locales/ko.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const ko = {
15271527
viewNameHelp: "머신 키(snake_case)입니다. 제목에서 자동으로 채워집니다 — 원하면 수정하세요.",
15281528
viewNameRequired: "키를 입력하세요(소문자, 숫자, 밑줄). 비라틴 문자 제목에서는 자동으로 채워지지 않습니다.",
15291529
viewNameInvalid: "소문자, 숫자, 밑줄을 사용하고 문자 또는 밑줄로 시작하세요.",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "시스템 보기는 코드에 정의되어 있어 읽기 전용입니다.",
15311531
cannotEditMetaView: "이 보기는 코드에 정의된 기본 제공 보기이므로 변경할 수 없습니다.",
15321532
cannotDeleteMetaView: "이 보기는 코드에 정의된 기본 제공 보기이므로 삭제할 수 없습니다.",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "전체 페이지로 열기",
15341534
objectNotFound: "객체를 찾을 수 없음",
15351535
objectNotFoundDescription: "객체 \"{{objectName}}\"이(가) 현재 구성에 존재하지 않습니다.",
15361536
objectNotFoundHint: "앱 탐색 설정을 확인하거나 사이드바에서 다른 객체를 선택하세요.",

‎packages/i18n/src/locales/pt.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const pt = {
15271527
viewNameHelp: "Chave técnica (snake_case). Preenchida automaticamente a partir do título — edite se quiser.",
15281528
viewNameRequired: "Informe uma chave (letras minúsculas, números, sublinhado). Não é preenchida automaticamente para títulos não latinos.",
15291529
viewNameInvalid: "Use letras minúsculas, números e sublinhados; comece com uma letra ou sublinhado.",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "Exibição do sistema definida no código — somente leitura.",
15311531
cannotEditMetaView: "Esta exibição é integrada e definida no código — não é possível alterá-la.",
15321532
cannotDeleteMetaView: "Esta exibição é integrada e definida no código — não é possível excluí-la.",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "Abrir como página inteira",
15341534
objectNotFound: "Objeto não encontrado",
15351535
objectNotFoundDescription: "O objeto \"{{objectName}}\" não existe na configuração atual.",
15361536
objectNotFoundHint: "Verifique as configurações de navegação do seu aplicativo ou selecione um objeto diferente na barra lateral.",

‎packages/i18n/src/locales/ru.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,10 +1527,10 @@ const ru = {
15271527
viewNameHelp: "Машинный ключ (snake_case). Заполняется автоматически из заголовка — при желании измените.",
15281528
viewNameRequired: "Введите ключ (строчные буквы, цифры, подчёркивание). Для нелатинских заголовков автозаполнение не работает.",
15291529
viewNameInvalid: "Используйте строчные латинские буквы, цифры и подчёркивания; начните с буквы или подчёркивания.",
1530-
systemViewReadonly: 'System view defined in code - duplicate to customize.',
1530+
systemViewReadonly: "Системное представление определено в коде — только для чтения.",
15311531
cannotEditMetaView: "Это встроенное представление, определённое в коде, — изменить его нельзя.",
15321532
cannotDeleteMetaView: "Это встроенное представление, определённое в коде, — удалить его нельзя.",
1533-
expandToPage: 'Open as full page',
1533+
expandToPage: "Открыть на всю страницу",
15341534
objectNotFound: "Объект не найден",
15351535
objectNotFoundDescription: "Объект «{{objectName}}» не существует в текущей конфигурации.",
15361536
objectNotFoundHint: "Проверьте настройки навигации приложения или выберите другой объект на боковой панели.",

0 commit comments

Comments
 (0)