feat(packages): i18n (#1708)

This commit is contained in:
Sam Potts
2026-07-10 12:58:47 +10:00
committed by GitHub
parent 5f48fcc6d6
commit 028dadb385
561 changed files with 13059 additions and 1320 deletions
+128
View File
@@ -11,6 +11,7 @@
* 4. Package metadata — non-private packages have required fields
* 5. Release-please config — every versioned package is registered
* 6. Define imports — no bare side-effect imports from relative paths
* 7. i18n locales — tag lists match locale files and generated stubs
*/
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
@@ -324,6 +325,132 @@ function checkDefineImports() {
return { ok: warnings.length === 0, warnings };
}
// ── Check 7: i18n locale consistency ─────────────────────────────────────────
const GENERATED_I18N_HEADER = '/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */';
function parseLocaleTagArray(source, exportName) {
const match = source.match(new RegExp(`export const ${exportName} = \\[([\\s\\S]*?)\\] as const`));
if (!match) {
return undefined;
}
return [...match[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);
}
function localeAliases(tags) {
const counts = new Map();
for (const tag of tags) {
if (!tag.includes('-')) continue;
const lang = tag.split('-')[0];
counts.set(lang, (counts.get(lang) ?? 0) + 1);
}
return [...counts].filter(([, count]) => count > 1).map(([lang]) => lang);
}
function checkI18nLocales() {
const warnings = [];
const builtInPath = join(PACKAGES_DIR, 'core/src/core/i18n/locales.ts');
const builtInSource = readText(builtInPath);
const locales = parseLocaleTagArray(builtInSource, 'LOCALES');
if (locales === undefined) {
warnings.push('Could not parse LOCALES from packages/core/src/core/i18n/locales.ts');
return { ok: false, warnings };
}
const localeFiles = [...locales, ...localeAliases(locales)];
const coreLocalesDir = join(PACKAGES_DIR, 'core/src/core/i18n/locales');
if (!existsSync(coreLocalesDir)) {
warnings.push('Missing generated locale directory packages/core/src/core/i18n/locales');
return { ok: false, warnings };
}
const coreFiles = readdirSync(coreLocalesDir)
.filter((file) => file.endsWith('.ts'))
.map((file) => file.slice(0, -3));
const expectedCore = new Set(['all', 'en', ...localeFiles]);
for (const tag of localeFiles) {
if (!coreFiles.includes(tag)) {
warnings.push(`LOCALES tag "${tag}" has no packages/core/src/core/i18n/locales/${tag}.ts`);
}
}
for (const file of coreFiles) {
if (!expectedCore.has(file)) {
warnings.push(`Unexpected locale file packages/core/src/core/i18n/locales/${file}.ts (not in locales.ts)`);
}
}
const allPath = join(coreLocalesDir, 'all.ts');
if (!existsSync(allPath)) {
warnings.push('Missing generated locale bundle packages/core/src/core/i18n/locales/all.ts');
} else if (!readText(allPath).startsWith(GENERATED_I18N_HEADER)) {
warnings.push(
'packages/core/src/core/i18n/locales/all.ts is not generated — run pnpm -F @videojs/core generate:locales'
);
}
const loadLocalePath = join(PACKAGES_DIR, 'core/src/core/i18n/load-locale.ts');
if (!existsSync(loadLocalePath)) {
warnings.push('Missing generated locale loader packages/core/src/core/i18n/load-locale.ts');
} else {
const loadLocaleSource = readText(loadLocalePath);
if (!loadLocaleSource.startsWith(GENERATED_I18N_HEADER)) {
warnings.push(
'packages/core/src/core/i18n/load-locale.ts is not generated — run pnpm -F @videojs/core generate:locales'
);
} else {
const loaderTags = new Set(
[...loadLocaleSource.matchAll(/import\('\.\/locales\/([^']+)'\)/g)].map((match) => match[1])
);
for (const tag of localeFiles) {
if (!loaderTags.delete(tag)) {
warnings.push(`LOCALES tag "${tag}" has no lazy importer in packages/core/src/core/i18n/load-locale.ts`);
}
}
for (const tag of loaderTags) {
warnings.push(`Unexpected lazy importer packages/core/src/core/i18n/load-locale.ts for "${tag}"`);
}
}
}
for (const pkg of ['html', 'react']) {
const localesDir = join(PACKAGES_DIR, `${pkg}/src/i18n/locales`);
const expectedPlatform = new Set(['all', 'en', ...localeFiles]);
if (!existsSync(localesDir)) {
warnings.push(`Missing generated re-export directory packages/${pkg}/src/i18n/locales`);
continue;
}
for (const tag of expectedPlatform) {
const filePath = join(localesDir, `${tag}.ts`);
if (!existsSync(filePath)) {
warnings.push(`Missing generated re-export packages/${pkg}/src/i18n/locales/${tag}.ts`);
continue;
}
if (!readText(filePath).startsWith(GENERATED_I18N_HEADER)) {
warnings.push(
`packages/${pkg}/src/i18n/locales/${tag}.ts is not generated — run pnpm -F @videojs/core generate:locales`
);
}
}
for (const file of readdirSync(localesDir)) {
if (!file.endsWith('.ts')) continue;
const tag = file.slice(0, -3);
if (!expectedPlatform.has(tag)) {
warnings.push(`Unexpected locale re-export packages/${pkg}/src/i18n/locales/${file}`);
}
}
}
return { ok: warnings.length === 0, warnings };
}
// ── Main ────────────────────────────────────────────────────────────────────
const checks = [
@@ -334,6 +461,7 @@ const checks = [
{ name: 'Release-please config', fn: checkReleasePleaseConfig },
{ name: 'Bundled docs publishing', fn: checkBundledDocs },
{ name: 'Define imports', fn: checkDefineImports },
{ name: 'i18n locales', fn: checkI18nLocales },
];
let failed = 0;