Skip to content

Commit e8534a8

Browse files
committed
refactor(lint): move the i18n translatable-key walk from the CLI into @objectstack/lint
collectExpectedEntries and its helpers move, by rename, from packages/cli/src/utils/i18n-extract.ts to packages/lint/src/i18n-walk.ts and are exported from @objectstack/lint, so a browser (Studio's translation editor) can run the same walk the CLI runs. The CLI keeps bundle assembly and serialization in utils/i18n-bundle-extract.ts, which re-exports the walk names, so the module surface is unchanged (pinned). Liveness evidence, the walk-parity gate and the dispatch-gates specimen follow the move. Claude-Session: https://claude.ai/code/session_01WGufzfqCqBH59yrHmuqXH3 Co-authored-by: Claude <noreply@anthropic.com>
1 parent cfb602c commit e8534a8

59 files changed

Lines changed: 863 additions & 682 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/cli/src/commands/i18n/extract.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
countTranslationLeaves,
3131
type FillStrategy,
3232
type TranslationModuleKind,
33-
} from '../../utils/i18n-extract.js';
33+
} from '../../utils/i18n-bundle-extract.js';
3434
import { authoringRuleUnionStack } from '../../utils/stack-collections.js';
3535

3636
const FILL_STRATEGIES: FillStrategy[] = ['empty', 'default', 'todo'];

‎packages/cli/src/utils/i18n-bundle-extract.ts‎

Lines changed: 631 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
* actually attached to it, and reports the ones that are missing or empty.
88
*
99
* The expected set is **not** computed here. It comes from
10-
* {@link collectExpectedEntries} in `i18n-extract.ts` — the same walker that
11-
* scaffolds bundles for `os i18n extract`. That sharing is the point: this
10+
* {@link collectExpectedEntries} in `@objectstack/lint` (`packages/lint/src/i18n-walk.ts`)
11+
* — the same walker that scaffolds bundles for `os i18n extract`. That sharing is the point: this
1212
* detector used to keep its own parallel walk, and the two drifted until whole
1313
* declared surfaces were extractable but ungated — most visibly the action
1414
* labels declared *inline on an object* (`sys_approval_request`'s
@@ -44,7 +44,7 @@
4444

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

4949
export type CoverageSeverity = 'error' | 'warning';
5050

‎packages/cli/test/i18n-block-inline-title-walk.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// them in English.
1414

1515
import { describe, it, expect } from 'vitest';
16-
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-extract.js';
16+
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-bundle-extract.js';
1717
import { computeI18nCoverage } from '../src/utils/i18n-coverage.js';
1818
import { TranslationDataSchema } from '@objectstack/spec/system';
1919

‎packages/cli/test/i18n-book-walk.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// measure a debt of zero while the sidebar shipped in the authoring language.
99

1010
import { describe, it, expect } from 'vitest';
11-
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-extract.js';
11+
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-bundle-extract.js';
1212
import { computeI18nCoverage } from '../src/utils/i18n-coverage.js';
1313
import { TranslationDataSchema } from '@objectstack/spec/system';
1414

‎packages/cli/test/i18n-bulk-action-coverage.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
// `helpText` here would scaffold keys `.strict()` rejects.
2020

2121
import { describe, it, expect } from 'vitest';
22-
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-extract.js';
22+
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-bundle-extract.js';
2323
import { computeI18nCoverage } from '../src/utils/i18n-coverage.js';
2424
import { ObjectTranslationDataSchema } from '@objectstack/spec/system';
2525

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* The translatable-key walk moved to `@objectstack/lint` (`i18n-walk.ts`) so a
5+
* browser can run it; the CLI's extractor module kept everything else and
6+
* re-exports the walk. This pins that the module still offers exactly what
7+
* `utils/i18n-extract.ts` offered before the move, and that the walk names are
8+
* lint's own functions, not a second copy that could drift from them.
9+
*/
10+
11+
import { describe, it, expect } from 'vitest';
12+
import * as lint from '@objectstack/lint';
13+
import * as bundleExtract from '../src/utils/i18n-bundle-extract.js';
14+
import type {
15+
ExpectedEntry,
16+
ExpectedEntryOptions,
17+
ExtractOptions,
18+
ExtractResult,
19+
FillStrategy,
20+
TranslationModuleKind,
21+
} from '../src/utils/i18n-bundle-extract.js';
22+
23+
/** The runtime exports of `packages/cli/src/utils/i18n-extract.ts` before the move. */
24+
const RUNTIME_EXPORTS_BEFORE_THE_MOVE = [
25+
'authorWarnedTranslationGroups',
26+
'collectExpectedEntries',
27+
'countTranslationLeaves',
28+
'extractTranslations',
29+
'humanizeFieldPath',
30+
'narrowToCommittedSections',
31+
'parseSourceHashModule',
32+
'renderSourceHashModule',
33+
'renderTranslationModule',
34+
'stackAuthoredSubtree',
35+
'translationModulePayload',
36+
'translationModuleSections',
37+
];
38+
39+
// The type exports, held by type-check: removing one fails `typecheck`.
40+
type TypeExportsBeforeTheMove = [
41+
ExpectedEntry,
42+
ExpectedEntryOptions,
43+
ExtractOptions,
44+
ExtractResult,
45+
FillStrategy,
46+
TranslationModuleKind,
47+
];
48+
49+
describe('the CLI extractor module after the walk moved to @objectstack/lint', () => {
50+
it('exports exactly the runtime names it exported before', () => {
51+
expect(Object.keys(bundleExtract).sort()).toEqual(RUNTIME_EXPORTS_BEFORE_THE_MOVE);
52+
});
53+
54+
it("re-exports lint's walk functions themselves", () => {
55+
expect(bundleExtract.collectExpectedEntries).toBe(lint.collectExpectedEntries);
56+
expect(bundleExtract.authorWarnedTranslationGroups).toBe(lint.authorWarnedTranslationGroups);
57+
expect(bundleExtract.humanizeFieldPath).toBe(lint.humanizeFieldPath);
58+
});
59+
60+
it('still offers its type exports, with the extract options extending the walk options', () => {
61+
const none: TypeExportsBeforeTheMove | undefined = undefined;
62+
const options: ExtractOptions = { warnedGroups: new Set<string>() };
63+
const walkOptions: ExpectedEntryOptions = options;
64+
expect(none).toBeUndefined();
65+
expect(walkOptions.warnedGroups?.size).toBe(0);
66+
});
67+
});

‎packages/cli/test/i18n-contributed-nav-coverage.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { describe, it, expect } from 'vitest';
2222
import { composeStacks, defineStack } from '@objectstack/spec';
2323
import { TRANSLATION_TARGET_UNKNOWN, validateTranslationReferences } from '@objectstack/lint';
2424

25-
import { collectExpectedEntries } from '../src/utils/i18n-extract.js';
25+
import { collectExpectedEntries } from '../src/utils/i18n-bundle-extract.js';
2626
import { computeI18nCoverage } from '../src/utils/i18n-coverage.js';
2727
import { authoringRuleUnionStack } from '../src/utils/stack-collections.js';
2828

‎packages/cli/test/i18n-dataset-coverage.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// otherwise be unaddressable.
2121

2222
import { describe, it, expect } from 'vitest';
23-
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-extract.js';
23+
import { collectExpectedEntries, extractTranslations } from '../src/utils/i18n-bundle-extract.js';
2424
import { computeI18nCoverage } from '../src/utils/i18n-coverage.js';
2525
import { TranslationDataSchema } from '@objectstack/spec/system';
2626

‎packages/cli/test/i18n-declared-surface-gate.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import { describe, it, expect } from 'vitest';
2121
import { computeI18nCoverage } from '../src/utils/i18n-coverage';
22-
import { collectExpectedEntries } from '../src/utils/i18n-extract';
22+
import { collectExpectedEntries } from '../src/utils/i18n-bundle-extract';
2323

2424
/** Only the user's own metadata; the platform metadata-form baseline is folded away by `os lint`. */
2525
const userIssues = (report: { issues: Array<{ source: string }> }) =>

0 commit comments

Comments
 (0)