Skip to content

Commit 28e747a

Browse files
committed
wip(ui): extractor home cases, zh census reads the namespace
Claude-Session: https://claude.ai/code/session_01ADHkmRuDqEXC3CybUtz5d1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent bfa6a40 commit 28e747a

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

‎ui/packages/app-shell/src/views/metadata-admin/zh-base-term.i18n-13131.test.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
* question. Regex: the whole Latin word `base`, case-insensitive, matched in the
1212
* row VALUE only — keys, identifiers such as `baseObject` (no word boundary)
1313
* and comments are out of scope.
14+
*
15+
* The rows that moved into `@object-ui/i18n`'s `studio` namespace
16+
* (objectstack#25922) are walked too, from the namespace's own `zh` catalogue,
17+
* so the census still covers the whole designer table wherever a row lives.
1418
*/
1519

1620
import { describe, it, expect } from 'vitest';
@@ -19,6 +23,7 @@ import path from 'node:path';
1923
import { fileURLToPath } from 'node:url';
2024
// @ts-expect-error — plain-JS shared helper, intentionally untyped (`allowJs: false`)
2125
import { maskComments } from '../../../../../scripts/js-comment-mask.mjs';
26+
import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio';
2227
import { t } from './i18n';
2328

2429
const mask: (source: string) => string = maskComments;
@@ -33,9 +38,12 @@ function zhRows(): Array<[string, string]> {
3338
const rows: Array<[string, string]> = [];
3439
const row = /'((?:[^'\\\n]|\\.)*)'\s*:\s*'((?:[^'\\\n]|\\.)*)'/g;
3540
for (const m of block.matchAll(row)) rows.push([m[1], m[2]]);
36-
return rows;
41+
return [...rows, ...Object.entries(studioZh)];
3742
}
3843

44+
/** The namespace's `zh` rows (objectstack#25922), resident before any case runs. */
45+
const studioZh = await loadStudioCatalogue('zh');
46+
3947
const BARE_BASE = /\bbase\b/i;
4048

4149
describe('zh "base" (writable package) is translated — objectui#13131', () => {

‎ui/scripts/__tests__/check-i18n-dead-keys.test.ts‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { fileURLToPath } from 'node:url';
66

77
import {
88
ANALYSED_PACK_OBJECT_IMPORTERS,
9+
DESIGNER_NAMESPACE_HOMES,
10+
DESIGNER_NAMESPACE_SPECIFIER,
911
DESIGNER_TABLE,
1012
collectDesignerKeys,
1113
collectIndirectTemplateHeads,
@@ -792,6 +794,62 @@ describe('collectDesignerKeys() — the optional second argument', () => {
792794
});
793795
});
794796

797+
/**
798+
* objectstack#25922 — the table's second home, the `studio` namespace. Its
799+
* catalogues are read as members of the table they belong to, but only when the
800+
* table module imports the namespace: the edge the runtime follows.
801+
*/
802+
describe('collectDesignerKeys() — the namespace homes', () => {
803+
const [EN_HOME, ZH_HOME] = DESIGNER_NAMESPACE_HOMES;
804+
const IMPORTING_TABLE = `import { studioString } from '${DESIGNER_NAMESPACE_SPECIFIER}';\n${DESIGNER_TABLE_FIXTURE}`;
805+
const homes = (en: string, zh: string) => ({
806+
[EN_HOME.file]: `const ${EN_HOME.decl}: Record<string, string> = { ${en} };\nexport default ${EN_HOME.decl};\n`,
807+
[ZH_HOME.file]: `const ${ZH_HOME.decl}: Record<string, string> = { ${zh} };\nexport default ${ZH_HOME.decl};\n`,
808+
});
809+
const MOVED = homes("'designer.fx.moved': 'Moved'", "'designer.fx.moved': '已迁移'");
810+
811+
it('merges a home into its table when the table imports the namespace', () => {
812+
const { tables, values } = collectDesignerKeys(designerRoot({ [DESIGNER_TABLE]: IMPORTING_TABLE, ...MOVED }), {
813+
withValues: true,
814+
});
815+
expect(tables.get('ENGINE_STRINGS_EN')!.size).toBe(10);
816+
expect(tables.get('ENGINE_STRINGS_ZH')!.has('designer.fx.moved')).toBe(true);
817+
expect(values!.get('ENGINE_STRINGS_ZH')!.get('designer.fx.moved')).toBe('已迁移');
818+
});
819+
820+
it('reads no home when the table does not import the namespace, even if the files exist', () => {
821+
const { tables } = collectDesignerKeys(designerRoot(MOVED));
822+
expect(tables.get('ENGINE_STRINGS_EN')!.has('designer.fx.moved')).toBe(false);
823+
});
824+
825+
it('throws when the table imports the namespace and a home cannot be read', () => {
826+
expect(() => collectDesignerKeys(designerRoot({ [DESIGNER_TABLE]: IMPORTING_TABLE }))).toThrow(/cannot be read/);
827+
});
828+
829+
it('throws on a key in both homes', () => {
830+
const root = designerRoot({
831+
[DESIGNER_TABLE]: IMPORTING_TABLE,
832+
...homes("'engine.fx.dead': 'Copy'", "'engine.fx.dead': '副本'"),
833+
});
834+
expect(() => collectDesignerKeys(root)).toThrow(/exactly one home/);
835+
});
836+
837+
it('a caller that passes `source` reads homes through `readHome`, or throws', () => {
838+
const root = designerRoot(MOVED);
839+
expect(() => collectDesignerKeys(root, { source: IMPORTING_TABLE })).toThrow(/readHome/);
840+
const read = collectDesignerKeys(root, {
841+
source: IMPORTING_TABLE,
842+
readHome: (file: string) => fs.readFileSync(path.join(root, file), 'utf8'),
843+
});
844+
expect(read.corpus.has('designer.fx.moved')).toBe(true);
845+
});
846+
847+
it("the dead-keys sweep does not read a home's own literal as a reader of its keys", () => {
848+
const result = sweepDesignerTable(designerRoot({ [DESIGNER_TABLE]: IMPORTING_TABLE, ...MOVED }));
849+
expect(keysOf(result.confirmed)).toContain('designer.fx.moved');
850+
});
851+
});
852+
795853
describe('sweepDesignerTable()', () => {
796854
it('excludes a key spelled as a literal at a call site', () => {
797855
const result = sweepDesignerTable(designerRoot());

0 commit comments

Comments
 (0)