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
38 changes: 38 additions & 0 deletions .changeset/pack-rule-scans-the-real-zh-cn-tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
---

Test-only — this PR releases nothing to HotCRM users, so the frontmatter above is
deliberately empty (the sanctioned "releases nothing" declaration that
`.github/workflows/changeset-check.yml` documents, on par with the
`skip-changeset` label). Nothing under `src/` changed: no object, field, view,
label, page, hook or translation string. The one file this PR touches is
`test/docs-object-term-consistency.test.ts`.

The `#802` term guard has two rules that reach the Chinese language pack by two
different mechanisms, and only one of them was broken. The label-derivation rule
resolves the pack through the stack and has been catching defects all along; it
is untouched here. The other rule — "no retired spelling survives in the language
pack" — named one file, `src/translations/zh-CN.ts`, which was the whole pack
when the rule was written. The `#1311` locale split then moved every string out
of it into `src/translations/zh-CN/`, leaving a 77-line barrel of re-exports, and
the rule kept scanning the barrel: a file that cannot contain a label. It could
no longer fire.

That is the worst failure direction, because it is silent. The suite kept
reporting 33 tests passed, and a rule gone quiet reads as coverage. It was
measured rather than argued: with `#1529`'s defect re-injected verbatim into the
live pack, the old surface still exits 0 with 33 passed, while the repaired
surface exits 1 and names the offending file and line.

The scan surface is now derived the way the three surfaces that survived the same
split are — walk a root, filter by suffix — rather than naming a file. That
covers `_shared.ts`, which holds real pack strings and is reached only through
the family files, and it means the next module added under `zh-CN/` is picked up
with no edit to the guard. The suite's existing "the ledger and the scan surface
are real" check is extended to the pack in the same shape it already applies to
the pages and the test ledgers: the files being scanned must carry the label the
other rules resolve through the stack, so a surface that comes loose from the
pack again goes red instead of going quiet.

No retired spelling is live in the pack today — the repaired rule is green on
`main`'s content.
40 changes: 37 additions & 3 deletions test/docs-object-term-consistency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,28 @@ const SPARED: readonly string[] = [

const DOCS_ROOT = join(REPO_ROOT, 'content/docs');
const TEST_ROOT = join(REPO_ROOT, 'test');
const PACK_FILE = 'src/translations/zh-CN.ts';

/**
* The zh-CN pack, as a TREE rather than one path (#1755).
*
* This was a single literal, `'src/translations/zh-CN.ts'`, and it was correct
* when written. The #1311 split then moved every string out from under it into
* `./zh-CN/`, leaving a 77-line barrel of `import`/re-export rows. Measured on
* `2e0f4dd1`: that barrel carries ZERO occurrences of either canonical term
* while the tree it re-exports carries them 65 and 12 times. The pack rule kept
* scanning the barrel, so it could not fire — and it did not: #1530's dev
* re-injected #1529's defect verbatim into the live pack and this suite still
* reported 33 passed. A rule that goes quiet reads as coverage.
*
* The surface is therefore derived the way the three that SURVIVED that split
* are — walk a root, filter by suffix — instead of naming a file. `_shared.ts`
* is why a walk beats resolving the barrel's imports: it holds real pack
* strings and the barrel does not import it, the family files do. The next
* module added under `zh-CN/` is picked up with no edit here, which is exactly
* the property the literal lacked.
*/
const PACK_BARREL = 'src/translations/zh-CN.ts';
const PACK_ROOT = join(REPO_ROOT, 'src/translations/zh-CN');

const walk = (dir: string): string[] =>
readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
Expand All @@ -263,6 +284,7 @@ const read = (f: string): string => readFileSync(join(REPO_ROOT, f), 'utf8');
const HANS_PAGES = walk(DOCS_ROOT).filter((f) => f.endsWith('.zh-Hans.mdx')).map(rel);
const HANT_PAGES = walk(DOCS_ROOT).filter((f) => f.endsWith('.zh-Hant.mdx')).map(rel);
const TEST_FILES = walk(TEST_ROOT).filter((f) => f.endsWith('.test.ts')).map(rel);
const PACK_FILES = [PACK_BARREL, ...walk(PACK_ROOT).filter((f) => f.endsWith('.ts')).map(rel)];
const ZH_PAGES = [...HANS_PAGES, ...HANT_PAGES];

/** MDX ships whole; TypeScript ships only its string literals. */
Expand Down Expand Up @@ -370,6 +392,18 @@ describe.each(Object.entries(TERMS))('%s is one Chinese word everywhere (#837)',
expect(HANS_PAGES.length, 'no zh-Hans pages found').toBeGreaterThan(50);
expect(HANT_PAGES.length, 'no zh-Hant pages found').toBeGreaterThan(50);
expect(TEST_FILES.length, 'no test files found').toBeGreaterThan(100);
// The pack scan is the one that went quiet (#1755), and a file COUNT
// would not have caught it — the barrel it used to name is still one
// real file. So the surface is proved against the pack the rules above
// resolve through the stack: the files scanned must be the files that
// carry this object's live label. On the pre-fix surface this reads 0,
// which is the reading that was missing for the whole life of the bug.
expect(
carrying(PACK_FILES, hans).length,
`no file in the zh-CN pack surface carries ${name}'s label ${hans} — the surface has ` +
'come loose from the pack again, so the retired-spelling rule below scans nothing. ' +
'Re-derive the surface against the tree; do not delete the rule.',
).toBeGreaterThan(0);
});

it('comment stripping leaves the code it is meant to judge', () => {
Expand Down Expand Up @@ -403,10 +437,10 @@ describe.each(Object.entries(TERMS))('%s is one Chinese word everywhere (#837)',
});

it('from the language pack itself', () => {
const bad = offenders([PACK_FILE], allRetired);
const bad = offenders(PACK_FILES, allRetired);
expect(
bad,
`${PACK_FILE} still spells something with a retired word — the pack IS the product ` +
'the zh-CN pack still spells something with a retired word — the pack IS the product ' +
'UI, so a label, an option or a `help` sentence left behind puts the old word back ' +
`on screen and sends the reader looking for a status that does not exist:\n ` +
bad.join('\n '),
Expand Down
Loading