test(i18n): derive remaining locale counts from config and add reverse surface parity

This commit is contained in:
Markus Hartung
2026-09-02 04:49:02 -03:00
parent ba768e5312
commit b787670f8e
4 changed files with 63 additions and 7 deletions

View File

@@ -1,6 +1,6 @@
import test from "node:test";
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import i18nConfig from "../../config/i18n.json" with { type: "json" };
@@ -11,7 +11,9 @@ import i18nConfig from "../../config/i18n.json" with { type: "json" };
* docs mirror (README.md / llm.txt / CHANGELOG.md), README flag link and the
* docs/i18n/README.md index row — and the README headline count must match.
* Locales listed in `docsExcluded` (the English source) only need the two
* catalogs.
* catalogs. The reverse direction is guarded as well: every docs-surface entry
* (index row, README flag link, docs/i18n/ directory) must map back to a
* configured docs locale, so a retired locale cannot leave orphans behind.
*/
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
@@ -43,3 +45,44 @@ test("README headline count matches config/i18n.json", () => {
assert.ok(m, "README must carry the 'In N languages' headline");
assert.equal(Number(m[1]), i18nConfig.locales.length);
});
test("docs/i18n/README.md counts sentence matches config/i18n.json", () => {
const sentence = `into ${i18nConfig.locales.length - 1} languages; together with the English source, the UI supports ${i18nConfig.locales.length} locales`;
assert.ok(docsIndex.includes(sentence), `docs/i18n/README.md must contain "${sentence}"`);
});
test("no orphan locale surface outside config/i18n.json (reverse parity)", () => {
const docsLocales = new Set(
i18nConfig.locales.map(({ code }) => code).filter((code) => !docsExcluded.has(code))
);
const offenders: string[] = [];
// docs/i18n/README.md rows: "- … (`code`): [Docs Root](./code/README.md)"
for (const line of docsIndex.split("\n")) {
const row = line.match(/^- .*\(`([^`]+)`\): \[Docs Root\]\(\.\/([^/]+)\/README\.md\)/);
if (!row) continue;
const [, code, linkCode] = row;
if (!docsLocales.has(code)) offenders.push(`docs/i18n/README.md row: ${code}`);
if (linkCode !== code) {
offenders.push(`docs/i18n/README.md row ${code} links to ./${linkCode}/README.md`);
}
}
// Root README.md language block: docs/i18n/<code>/README.md flag links.
for (const [, code] of readme.matchAll(/docs\/i18n\/([^/\s"')]+)\/README\.md/g)) {
if (!docsLocales.has(code)) offenders.push(`README.md link: ${code}`);
}
// Every directory under docs/i18n/ must be a configured docs locale.
for (const entry of readdirSync(path.join(ROOT, "docs", "i18n"), { withFileTypes: true })) {
if (entry.isDirectory() && !docsLocales.has(entry.name)) {
offenders.push(`docs/i18n/ directory: ${entry.name}`);
}
}
assert.deepEqual(
offenders,
[],
`orphan locale surfaces that are not a configured docs locale (config/i18n.json minus docsExcluded) — retired locale left behind?: ${offenders.join("; ")}`
);
});

View File

@@ -22,6 +22,7 @@ import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import i18nConfig from "../../config/i18n.json" with { type: "json" };
const PAGE_PATH = path.resolve(process.cwd(), "src/app/(dashboard)/dashboard/radar/page.tsx");
const PAGE_SRC = fs.readFileSync(PAGE_PATH, "utf-8");
@@ -70,7 +71,7 @@ test("radar page: references the 5 new claim-section t(...) keys", () => {
}
});
test("radar page + all 43 locale files: no price/monetary value in the claim section copy (D14)", () => {
test(`radar page + all ${i18nConfig.locales.length} locale files: no price/monetary value in the claim section copy (D14)`, () => {
// D14: no pricing anywhere in the OSS repo, only a link to the plans page.
const PRICE_PATTERN =
/\$\s?\d|R\$\s?\d|\d+[.,]\d{2}\s?(USD|BRL|EUR)|\b(lifetime|life-time)\b.{0,20}\$/i;
@@ -78,7 +79,10 @@ test("radar page + all 43 locale files: no price/monetary value in the claim sec
const messagesDir = path.resolve(process.cwd(), "src/i18n/messages");
const files = fs.readdirSync(messagesDir).filter((f) => f.endsWith(".json"));
assert.ok(files.length >= 40, `expected ~43 locale files, found ${files.length}`);
assert.ok(
files.length >= i18nConfig.locales.length,
`expected the ${i18nConfig.locales.length} configured locales, found ${files.length}`
);
for (const file of files) {
const data = JSON.parse(fs.readFileSync(path.join(messagesDir, file), "utf-8"));

View File

@@ -26,6 +26,7 @@ import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import i18nConfig from "../../config/i18n.json" with { type: "json" };
const PAGE_PATH = path.resolve(process.cwd(), "src/app/(dashboard)/dashboard/radar/page.tsx");
const PAGE_SRC = fs.readFileSync(PAGE_PATH, "utf-8");
@@ -114,14 +115,17 @@ test("radar page: references the 4 new key-input t(...) keys", () => {
}
});
test("radar page + all 43 locale files: no price/monetary value in the key-input copy (D14)", () => {
test(`radar page + all ${i18nConfig.locales.length} locale files: no price/monetary value in the key-input copy (D14)`, () => {
const PRICE_PATTERN =
/\$\s?\d|R\$\s?\d|\d+[.,]\d{2}\s?(USD|BRL|EUR)|\b(lifetime|life-time)\b.{0,20}\$/i;
assert.ok(!PRICE_PATTERN.test(PAGE_SRC), "page.tsx must not contain a price/monetary value");
const messagesDir = path.resolve(process.cwd(), "src/i18n/messages");
const files = fs.readdirSync(messagesDir).filter((f) => f.endsWith(".json"));
assert.ok(files.length >= 40, `expected ~43 locale files, found ${files.length}`);
assert.ok(
files.length >= i18nConfig.locales.length,
`expected the ${i18nConfig.locales.length} configured locales, found ${files.length}`
);
for (const file of files) {
const data = JSON.parse(fs.readFileSync(path.join(messagesDir, file), "utf-8"));

View File

@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import i18nConfig from "../../config/i18n.json" with { type: "json" };
function radarCopy(locale: "en" | "pt-BR"): Record<string, string> {
const file = path.resolve(process.cwd(), `src/i18n/messages/${locale}.json`);
@@ -101,7 +102,11 @@ test("Brazilian Portuguese Radar opt-in copy states D32 without a PR-count grant
test("every UI locale carries the D32 keys and none retains the superseded 5+ PR promise", () => {
const messagesDir = path.resolve(process.cwd(), "src/i18n/messages");
const files = fs.readdirSync(messagesDir).filter((file) => file.endsWith(".json"));
assert.equal(files.length, 43, "expected the complete 43-locale UI catalog");
assert.equal(
files.length,
i18nConfig.locales.length,
`expected the complete ${i18nConfig.locales.length}-locale UI catalog`
);
for (const file of files) {
const messages = JSON.parse(fs.readFileSync(path.join(messagesDir, file), "utf8")) as {