Skip to content

Commit 0f920b9

Browse files
fix(i18n): a Traditional Chinese session reads and edits the Traditional entry of an inline label map (#31068)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoKnw5nKcC7y1QqoHK4wxk2W)_ Fixes #30910 Clause-②: no Metadata-protocol: none Metadata-protocol is `none` because what an author can write is unchanged: the same `InlineLocaleMapSchema` keys and values are accepted, and only which of two existing entries a Chinese session reads and edits moves. ## 维护者速读 - 改了什么:内联多语言标签同时写了简体和繁体时,繁体中文会话(zh-Hant / zh-TW / zh-HK)现在读到、编辑的都是繁体那一条;简体会话同理只取简体。客户端 `pickLocalized`(含写入侧 `setLocalized` / `clearLocalized`)和 spec 的 `resolveI18nLabel` 一起改,规则保持一致。 - 为什么改:之前两边都取作者写下的第一个 `zh-*` 键,台湾、香港用户看到简体,在 Studio 里保存还会覆盖简体作者的文字,繁体那条反而不更新。 - 风险与回滚:只影响同一张标签里同时有两种中文书写的情况;只有一种书写的标签、以及所有非中文语言,结果与改前逐字节相同(有测试钉住)。回滚即还原本 PR。 - 先看:`ui/packages/i18n/src/pickLocalized.ts` 的 `localizedEntryKey`(读写共用的一处);`packages/spec/src/ui/i18n-label-resolver.ts` 的 `scriptEntryKey`;`ui/packages/plugin-list/src/__tests__/i18nLabel-resolver-parity.test.ts` 新增的两侧一致性行。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/i18n/src/__tests__/pickLocalized.test.ts`,预期全绿,其中 “an entry of the session's own script first” 一组覆盖每种繁简拼写。 - 需要你做的:无,席位落地。 - **席位意见**:ui 席 2 对照 head `6e8b6389` 复核:14 个文件、+498 / −65,无受治理路径,因碰 `packages/spec/src` 走 CONTRACT_REVIEW_TIER 契约复审:首轮 FAIL(6700222070,Studio 导航设计器的写入键 `ownEntryKey` 仍按旧规则,会在双书写标签上改错条目),修复后 delta 复审 PASS(6700837928),之后只加了一条 changeset(Regen-provenance 已落)。现场:zh-Hant 会话侧栏显示繁体条目。Studio 设计器语言只有 zh-CN / en-US 两值,繁体会话仍按简体设计,已另立 #31074。CI 全绿,同意落地。 ## What changes - `ui/packages/i18n/src/pickLocalized.ts`: one private `localizedEntryKey` now answers the first three limbs for both `pickLocalized` (read) and `localeWriteKey` (write, and so `setLocalized` / `clearLocalized`). Where the platform locale table splits a language by script, the base and sibling limbs prefer an entry of the session's own script before any other sibling. - `packages/spec/src/ui/i18n-label-resolver.ts`: `resolveI18nLabel` takes the same limb (`scriptEntryKey`), so the spec twin, which the nav, app shell, components and service-analytics call, keeps agreeing with `pickLocalized` limb for limb. - **How the script split is derived:** both readings come from `@objectstack/spec/system`, with no local table. `platformLocaleTagOf(code)` says which table tag a code means (`zh-TW`, `zh-HK`, `zh-MO` and the `zh-Hant-*` spellings mean `zh-Hant`; `zh`, `zh-CN`, `zh-SG` and the `zh-Hans-*` spellings mean `zh-Hans`). A base is script-split when `PLATFORM_LOCALE_TAGS` holds more than one tag with that base, which today is only `zh`. For any other language the new limb is skipped, so `pt`, `es`, `en`, `fr`, `ja` and the rest keep authoring order on the sibling limb, byte-identical to `main`. - Limb order where the split applies: exact tag, then the bare base key when it means the session's tag, then the first sibling that means it, then the base key whatever it means, then the first sibling. A map with no entry of the session's script resolves as before. - Tests: new `describe` blocks in `pickLocalized.test.ts`, `setLocalized.test.ts` and `clearLocalized.test.ts` (five Simplified and five Traditional spellings over six two-script maps, plus byte-identical controls for one-script maps and for `pt`, `es`, `en`, `fr`, `ja`); new rows in the plugin-list parity table; the spec test's verbatim reference copy re-pinned to the new blob and given script vectors. - Two existing vectors pinned the defect itself (`{ 'zh-TW', 'zh-CN' }` at `zh` read the Traditional entry). Both now expect the Simplified entry. The spec row was replaced by a `pt-PT` / `pt-BR` row that still pins "first sibling in authoring order" for a language the table names once. - Changesets: `@object-ui/i18n` patch, `@objectstack/spec` patch, `@objectstack/cli` patch. - Review widening (contract review 6700222070, claim amendment 6700234158): `ui/packages/app-shell/src/views/metadata-admin/previews/navItemLabel.ts` `ownEntryKey` now owns the entry `resolveI18nLabel` reads among the map's same-base own entries, so Studio's nav rename and clear act on the entry the designer shows (`7422324200`); `packages/cli/src/utils/i18n-coverage.ts` `inlineLocaleText` narrows to the reader's language and returns what `resolveI18nLabel` picks, with a two-script fixture in `packages/cli/test/i18n-inline-locale-map-coverage.test.ts` (`4b4e003685`); resolver prose follows the limb order and the one-repo home (`2d5cae2c8d`). ## Tests Fix commit `7228816bf5`, then `origin/main` merged (it carries #30902's `@object-ui/i18n` → `@objectstack/spec` dependency). Suite and gate readings are on the merge head `4d0fb81ba5`. The ablation was taken on `7228816bf5`, where the same two source files hold the same blobs. <details><summary>Before / after (unit level, read value and write key)</summary> On `main` `0e882086`: ``` zh-CN+zh-TW lang=zh-Hant read=客户 writeKey=zh-CN zh-Hans+zh-Hant lang=zh-TW read=客户 writeKey=zh-Hans zh-TW+zh-CN lang=zh-Hans read=客戶 writeKey=zh-TW zh-TW+zh-CN lang=zh read=客戶 writeKey=zh-TW spec resolveI18nLabel zh-CN+zh-TW at zh-Hant read=客户 ``` On this branch the same rows read 客戶 / zh-TW, 客戶 / zh-Hant, 客户 / zh-CN, 客户 / zh-CN, and the spec resolver reads 客戶. These are pinned in the new describe blocks. One-script maps (`zh` only, `zh-HK` only) read and write the same key as on `main`. </details> <details><summary>Suites</summary> - `pnpm --filter @objectstack/spec exec vitest run src/ui/i18n-label-resolver.test.ts src/system/platform-locales.test.ts`: 2 files, 184 tests passed. - `pnpm --dir ui exec vitest run packages/i18n/src/__tests__/pickLocalized.test.ts packages/i18n/src/__tests__/setLocalized.test.ts packages/i18n/src/__tests__/clearLocalized.test.ts packages/plugin-list/src/__tests__/i18nLabel-resolver-parity.test.ts scripts/__tests__/check-phantom-dependencies.test.ts`: 5 files, 276 tests passed. - `pnpm --filter @objectstack/spec typecheck`: exit 0. `pnpm --filter @object-ui/i18n type-check` after building its closure: exit 0. </details> <details><summary>Ablation (one-time proof, mutations restored, blob equals HEAD)</summary> Every leg used `scripts/ablation-replace.mjs`. - Client limb (`if (script !== undefined)` changed to never match) in `pickLocalized.ts`: 32 tests red across the three i18n files, all of them new #30910 pins. 0 byte-identical controls red, as expected, because the mutation is the old behaviour. - Spec limb (`if (sameScript !== undefined) return …` changed to never match), spec source suite: 5 red (4 script vectors plus the one-pass agreement test). The reference-copy column stayed green. - The same spec mutation, rebuilt into `dist` (`ablation-dist-preflight` found the marker in 2 built files): 6 red in the plugin-list parity test (5 new rows plus "has no rule divergence left"). After restoring and rebuilding, the preflight `--absent` reports the marker gone from all 592 built files and the tree clean. </details> <details><summary>Derived gates (`dispatch-gates.mjs --commands`, 83 families)</summary> `--ran` reconciliation on `4d0fb81ba5`: 83 derived, 83 run, all exit 0, 0 NOT-MEASURED. Before `main` was merged (on `7228816bf5`), `check:undeclared-dep-imports` and the ui `scripts/` phantom-deps test were red only because `@object-ui/i18n` did not yet declare `@objectstack/spec`. #30902 added that declaration, and both are green on the merge head. </details> ## Acceptance notes - **Live check (head `6e8b6389`, showcase backend + console dev server, `nav_projects` relabelled locally to `{ en, zh: '简体项目', 'zh-TW': '繁體專案' }`, reverted after):** the sidebar shows 繁體專案 in `zh-Hant`, 简体项目 in `zh-CN`, Proj EN in `en`. Studio's nav designer in a `zh-Hant` session lists 简体项目, because Studio's designer locale is two-valued (`useMetadataLocale`, `metadata-admin/i18n.ts:955`, unchanged here): filed as #31074 with the one-script write and the CLI empty-entry caveat. The main-side control is NOT MEASURED (it needs a `main` spec dist rebuild). - Case: the script preference asks `platformLocaleTagOf`, which folds case, so `zh-hant` also prefers `zh-TW`. It only reorders keys the case-sensitive base comparison already admitted, so `ZH-HANT` still matches no `zh-*` key (pinned). - A script-subtag spelling the alias table does not name (`zh-Hant-US`) is not guessed at and is not treated as Traditional (pinned). This matches the spec table's closed-list rule. ## Out of scope - Finding, not filed: the base-language comparison in both twins is case-sensitive on `main` (`ZH-HANT` matches no `zh-*` key), while the new script limb reads through `platformLocaleTagOf`, which folds case. Folding the base comparison would change matching for every language, so it is outside #30910 and is left for the contract reviewer to weigh. ## Verification Readings as above, on `4d0fb81ba5`; on `2d5cae2c8d` 87 derived gates exit 0 (`--ran` 0 NOT-MEASURED), navItemLabel ablation reds 4 pins and 0 controls, CLI ablation reds 2. `6e8b6389` adds the cli changeset only. The contract review (CONTRACT_REVIEW_TIER) on the head is the seat's, because the diff touches `packages/spec/src`. <!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e11a575 commit 0f920b9

14 files changed

Lines changed: 498 additions & 65 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
fix(cli): i18n coverage reads a two-script inline label map the way the renderer does
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
For an inline locale map that carries both Chinese scripts, the text the coverage report reads for a reader locale now equals what the renderer shows: a `zh-Hant` reader of `{ en, 'zh-CN', 'zh-TW' }` is read from `zh-TW`, not from the first `zh-*` key. Every other map and language reads as before.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@object-ui/i18n': patch
3+
---
4+
5+
fix(i18n): a Traditional Chinese session reads and edits the Traditional entry of an inline label map
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`pickLocalized`, `setLocalized` and `clearLocalized` now prefer an entry of the session's own script where the platform locale table splits a language by script. In a `zh-Hant` (or `zh-TW`, `zh-HK`) session, a label written as `{ en: 'Customer', 'zh-CN': '客户', 'zh-TW': '客戶' }` shows `客戶` and an edit replaces the `zh-TW` entry; before, both took the first `zh-*` key in authoring order, so a Traditional session showed the Simplified text and an edit overwrote it. A `zh-Hans`, `zh-CN` or bare `zh` session prefers the Simplified entry the same way.
11+
12+
Which tag a key means comes from `platformLocaleTagOf` in `@objectstack/spec/system`, and the preference applies only to a language the table names more than once (`zh` today). A map without an entry of the session's script, and every other language, resolve exactly as before. What an author can write is unchanged.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `resolveI18nLabel` prefers an inline label entry of the locale's own script
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Where the platform locale table splits a language by script (`zh-Hans`, `zh-Hant`), `resolveI18nLabel` now reads an entry of the requested locale's script before an entry of the other one: `zh-Hant`, `zh-TW` or `zh-HK` over `{ 'zh-CN': '客户', 'zh-TW': '客戶' }` answers `客戶` in any authoring order, and `zh`, `zh-CN` or `zh-Hans` answers `客户`. It used to take the first `zh-*` key. The script a key means is read from `platformLocaleTagOf`. A map without an entry of the locale's script, and every language the table names once, resolve exactly as before. This keeps the resolver limb for limb with objectui's `pickLocalized`, which takes the same change. The `InlineLocaleMapSchema` keys an author can write are unchanged.

‎packages/cli/src/utils/i18n-coverage.ts‎

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444

4545
import { deepMerge } from '@objectstack/core';
4646
import type { TranslationBundle, TranslationData } from '@objectstack/spec/system';
47+
import { resolveI18nLabel } from '@objectstack/spec/ui';
4748
import { collectExpectedEntries, type ExpectedEntry } from './i18n-bundle-extract.js';
4849

4950
export type CoverageSeverity = 'error' | 'warning';
@@ -518,15 +519,16 @@ function collectExpectedKeys(config: any): ExpectedKey[] {
518519
* as covered the moment a map exists, which is the mirror image of the bug
519520
* this function was added to fix: one answer standing for two opposite facts.
520521
*
521-
* So only the **tag-matching** limbs count as coverage, in the reference's own
522-
* order and with its own case rules:
523-
*
524-
* 1. the exact tag — `zh-CN` reads the key `zh-CN`;
525-
* 2. the base language — `zh-CN` reads the key `zh`;
526-
* 3. the first region-qualified sibling sharing that base — `zh` reads
527-
* `zh-CN`; `zh-TW` reads `zh-CN` too. Same language, other region: what
528-
* the renderer really shows, and a translation into the language asked
529-
* for.
522+
* So only the **tag-matching** limbs count as coverage: the entries whose
523+
* language subtag is the locale's (the exact tag, the bare language, a
524+
* region-qualified sibling). Which of them the reader sees is asked of
525+
* `resolveI18nLabel` itself, over the map cut down to those entries, so this
526+
* function keeps no copy of the resolver's limb order: `zh-CN` reads `zh-CN`,
527+
* else `zh`; `zh` reads `zh-CN`; `zh-TW` reads `zh-CN` when the map has no
528+
* Traditional entry, and `zh-TW` over `zh-CN` when it has both (the script
529+
* preference where the platform table splits a language). Same language,
530+
* other region: what the renderer really shows, and a translation into the
531+
* language asked for.
530532
*
531533
* The fallback limbs are excluded because falling back **is** what an
532534
* untranslated locale looks like: a `ja-JP` reader shown the `en` entry is
@@ -536,7 +538,8 @@ function collectExpectedKeys(config: any): ExpectedKey[] {
536538
* `i18n-label-resolver.ts` is the authority on the limb rule and on the case
537539
* asymmetry mirrored here (region case is irrelevant because limb 3 compares
538540
* language subtags only; language case is significant, because the reference
539-
* folds neither side). `inline-locale-coverage-parity.test.ts` pins this
541+
* folds neither side). The describe block "coverage reads the map the way the
542+
* renderer does" in `test/i18n-inline-locale-map-coverage.test.ts` pins this
540543
* function against `resolveI18nLabel`: whenever this says "covered", the
541544
* renderer really shows that entry.
542545
*
@@ -555,18 +558,14 @@ export function inlineLocaleText(
555558
return typeof value === 'string' && value.length > 0 ? value : undefined;
556559
};
557560
const tag = (locale || 'en').trim();
558-
const exact = read(tag);
559-
if (exact !== undefined) return exact;
560561
const base = tag.split('-')[0];
561-
const baseHit = read(base);
562-
if (baseHit !== undefined) return baseHit;
562+
const sameLanguage: Record<string, string> = {};
563563
for (const key of Object.keys(map)) {
564-
if (key.split('-')[0] === base) {
565-
const sibling = read(key);
566-
if (sibling !== undefined) return sibling;
567-
}
564+
const value = read(key);
565+
if (value !== undefined && key.split('-')[0] === base) sameLanguage[key] = value;
568566
}
569-
return undefined;
567+
if (Object.keys(sameLanguage).length === 0) return undefined;
568+
return resolveI18nLabel(sameLanguage, tag);
570569
}
571570

572571
/**

‎packages/cli/test/i18n-inline-locale-map-coverage.test.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,13 @@ describe('coverage reads the map the way the renderer does', () => {
272272
{ 'zh-CN': '成员' },
273273
{ default: 'Members', 'zh-CN': '成员' },
274274
{ en: 'Members' },
275+
// Both Chinese scripts, in either authoring order (#30910): the renderer
276+
// reads the entry of the reader's own script, and so does coverage.
277+
{ en: 'Members', 'zh-CN': '成员', 'zh-TW': '成員' },
278+
{ en: 'Members', 'zh-TW': '成員', 'zh-CN': '成员' },
279+
{ en: 'Members', zh: '成员', 'zh-Hant': '成員' },
275280
];
276-
const locales = ['en', 'zh', 'zh-CN', 'zh-TW', 'ja-JP', 'es-ES'];
281+
const locales = ['en', 'zh', 'zh-CN', 'zh-TW', 'zh-Hans', 'zh-Hant', 'zh-HK', 'ja-JP', 'es-ES'];
277282

278283
it('a covered locale is the entry the renderer picks', () => {
279284
for (const map of maps) {
@@ -300,4 +305,11 @@ describe('coverage reads the map the way the renderer does', () => {
300305
expect(inlineLocaleText({ 'zh-CN': '成员' }, 'zh-TW')).toBe('成员');
301306
expect(inlineLocaleText({ zh: '成员' }, 'zh-CN')).toBe('成员');
302307
});
308+
309+
it("a map with both Chinese scripts covers each reader with its own script's entry (#30910)", () => {
310+
expect(inlineLocaleText({ 'zh-CN': '成员', 'zh-TW': '成員' }, 'zh-Hant')).toBe('成員');
311+
expect(inlineLocaleText({ 'zh-TW': '成員', 'zh-CN': '成员' }, 'zh')).toBe('成员');
312+
// An empty entry of the reader's script is no translation: the other script still covers.
313+
expect(inlineLocaleText({ 'zh-CN': '成员', 'zh-TW': '' }, 'zh-Hant')).toBe('成员');
314+
});
303315
});

‎packages/spec/src/ui/i18n-label-resolver.test.ts‎

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@
1414
* ## How the expectations were derived
1515
*
1616
* `pickLocalizedReference` below is a VERBATIM copy of the reference
17-
* implementation, taken from
17+
* implementation (with its two private helpers), taken from
1818
*
19-
* repo objectstack-ai/objectui
20-
* path packages/i18n/src/pickLocalized.ts
21-
* rev origin/main d8d0d665dceba53dada4994f1eeef9f83bf1cf91
22-
* blob 30fcb0a86343b9b937432a1b2ea89e0a78321f46
23-
* last touched by objectui PR #4359 / objectui#3907 (2026-08-11)
19+
* repo objectstack-ai/objectstack
20+
* path ui/packages/i18n/src/pickLocalized.ts
21+
* blob 37b88c07aebfca42ce375c1e5b653f8ea26765c8
22+
* last touched by #30910 (the script-preference limb)
2423
*
2524
* Copied rather than imported because `@objectstack/spec` must not take a
2625
* workspace dependency on objectui — spec sits UNDER objectui in the dependency
@@ -48,11 +47,37 @@
4847
import { describe, it, expect } from 'vitest';
4948
import { resolveI18nLabel } from './i18n-label-resolver';
5049
import type { I18nLabel } from './i18n.zod';
50+
import { PLATFORM_LOCALE_TAGS, platformLocaleTagOf } from '../system/platform-locales';
5151

5252
// ---------------------------------------------------------------------------
5353
// The reference implementation — verbatim, see the header for provenance. Only
54-
// the NAME differs, so that a reader of a failing assertion can tell at a glance
55-
// which side is the copy.
54+
// the NAMES differ (a `Reference` suffix), so that a reader of a failing
55+
// assertion can tell at a glance which side is the copy.
56+
function splitScriptTagOfReference(lang: string): string | undefined {
57+
const tag = platformLocaleTagOf(lang);
58+
if (tag === undefined) return undefined;
59+
const base = tag.split('-')[0];
60+
return PLATFORM_LOCALE_TAGS.filter((t) => t.split('-')[0] === base).length > 1 ? tag : undefined;
61+
}
62+
63+
function localizedEntryKeyReference(
64+
lang: string,
65+
usable: (key: string) => boolean,
66+
keys: readonly string[],
67+
): string | undefined {
68+
if (usable(lang)) return lang;
69+
const base = lang.split('-')[0];
70+
const siblings = keys.filter((k) => k.split('-')[0] === base && usable(k));
71+
const script = splitScriptTagOfReference(lang);
72+
if (script !== undefined) {
73+
if (usable(base) && platformLocaleTagOf(base) === script) return base;
74+
const sameScript = siblings.find((k) => platformLocaleTagOf(k) === script);
75+
if (sameScript !== undefined) return sameScript;
76+
}
77+
if (usable(base)) return base;
78+
return siblings[0];
79+
}
80+
5681
function pickLocalizedReference(value: unknown, language: string | undefined | null): string {
5782
if (value == null) return '';
5883
if (typeof value === 'string') return value;
@@ -67,14 +92,11 @@ function pickLocalizedReference(value: unknown, language: string | undefined | n
6792
return typeof entry === 'string' ? entry : undefined;
6893
};
6994
const lang = (language || 'en').trim();
70-
const base = lang.split('-')[0];
7195
// Runtime language is often a bare base code ('zh') while metadata authors
72-
// write full BCP-47 tags ('zh-CN') — upgrade to any key sharing the base.
73-
const regional = Object.keys(o).find((k) => k.split('-')[0] === base && readReference(k) !== undefined);
96+
// write full BCP-47 tags ('zh-CN') — upgrade to a key sharing the base.
97+
const entry = localizedEntryKeyReference(lang, (k) => readReference(k) !== undefined, Object.keys(o));
7498
const pick =
75-
readReference(lang) ??
76-
readReference(base) ??
77-
(regional !== undefined ? readReference(regional) : undefined) ??
99+
(entry !== undefined ? readReference(entry) : undefined) ??
78100
readReference('default') ??
79101
readReference('en') ??
80102
Object.values(o).find((v): v is string => typeof v === 'string');
@@ -123,10 +145,62 @@ const PARITY_VECTORS: readonly ParityVector[] = [
123145
{ limb: '3 base key wins over the region upgrade', label: { zh: '基础', 'zh-CN': '区域' }, locale: 'zh', pick: '基础' },
124146
{
125147
limb: '3 first sibling in key order wins, not the "best" region',
148+
label: { 'pt-PT': 'Proprietário (PT)', 'pt-BR': 'Proprietário' },
149+
locale: 'pt',
150+
pick: 'Proprietário (PT)',
151+
},
152+
{
153+
limb: '3 first sibling in key order wins for a language the table names once (es-ES aliases es)',
154+
label: { 'es-MX': 'Propietario (MX)', 'es-ES': 'Propietario' },
155+
locale: 'es',
156+
pick: 'Propietario (MX)',
157+
},
158+
159+
// Limbs 2–3, script first (#30910) — where the platform table splits the
160+
// language by script (`zh-Hans`, `zh-Hant`), an entry of the locale's own
161+
// script wins over the other script, whatever the authoring order.
162+
{
163+
limb: '2–3 bare zh means zh-Hans: the Simplified sibling wins over an earlier Traditional one',
126164
label: { 'zh-TW': '擁有者', 'zh-CN': '负责人' },
127165
locale: 'zh',
166+
pick: '负责人',
167+
},
168+
{
169+
limb: '2–3 zh-Hant reads zh-TW over an earlier zh-CN',
170+
label: { en: 'Owner', 'zh-CN': '负责人', 'zh-TW': '擁有者' },
171+
locale: 'zh-Hant',
128172
pick: '擁有者',
129173
},
174+
{
175+
limb: '2–3 zh-HK reads zh-Hant over an earlier zh-Hans',
176+
label: { en: 'Owner', 'zh-Hans': '负责人', 'zh-Hant': '擁有者' },
177+
locale: 'zh-HK',
178+
pick: '擁有者',
179+
},
180+
{
181+
limb: '2–3 zh-TW skips a bare zh key (it means zh-Hans) for zh-Hant',
182+
label: { zh: '负责人', 'zh-Hant': '擁有者' },
183+
locale: 'zh-TW',
184+
pick: '擁有者',
185+
},
186+
{
187+
limb: '2–3 zh-CN keeps the bare zh key when it means the same script',
188+
label: { 'zh-Hans': '简体', zh: '负责人' },
189+
locale: 'zh-CN',
190+
pick: '负责人',
191+
},
192+
{
193+
limb: '2–3 no entry of the locale\'s script ⇒ the old limbs answer',
194+
label: { en: 'Owner', 'zh-CN': '负责人' },
195+
locale: 'zh-Hant',
196+
pick: '负责人',
197+
},
198+
{
199+
limb: '2–3 a script-subtag spelling the alias table does not name is not guessed at',
200+
label: { 'zh-CN': '负责人', 'zh-Hant-US': '擁有者' },
201+
locale: 'zh-Hant',
202+
pick: '负责人',
203+
},
130204
{
131205
limb: '3 runs BEFORE default — a wrong-region hit beats the untagged entry',
132206
label: { default: 'Owner', 'fr-FR': 'Propriétaire' },

0 commit comments

Comments
 (0)