6d36ec7459
node scripts/i18n-add-language.mjs <kod> <bcp47> "<eget namn>" Skapar locale-fil (sv-platshållare), registrerar i i18n.ts (import + SUPPORTED_LANGUAGES + resources), lägger språkväljar-etiketten i alla filer. Endast textinsättning → minimala diffar. Verifierat: testspråk -> typecheck+vakt gröna. Co-Authored-By: Claude <noreply@anthropic.com>
102 lines
4.4 KiB
JavaScript
102 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* i18n:add-language – lägg till ett nytt språk i ett kommando.
|
||
*
|
||
* node scripts/i18n-add-language.mjs <kod> <bcp47-tagg> "<eget namn>"
|
||
* ex: node scripts/i18n-add-language.mjs el el-GR "Ελληνικά"
|
||
*
|
||
* Gör ALL plumbing automatiskt:
|
||
* 1. Skapar src/locales/<kod>/common.json – kopia av svenska (platshållare),
|
||
* så appen funkar direkt och paritetsvakten är nöjd från start.
|
||
* 2. Registrerar språket i src/lib/i18n.ts (import + SUPPORTED_LANGUAGES + resources).
|
||
* 3. Lägger språkväljar-etiketten profile.language.<kod> = "<eget namn>" i ALLA
|
||
* locale-filer (annars faller vakten på saknad nyckel).
|
||
*
|
||
* Kvar för dig: översätt värdena i src/locales/<kod>/common.json och kör
|
||
* `pnpm --filter @app/mobile i18n:check` – den listar exakt vad som ev. fattas.
|
||
*
|
||
* Endast textinsättning (aldrig omsortering) → minimala diffar på befintliga filer.
|
||
*/
|
||
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
||
import { join } from "node:path";
|
||
|
||
const ROOT = process.cwd(); // apps/mobile
|
||
const LOC = join(ROOT, "src/locales");
|
||
const I18N = join(ROOT, "src/lib/i18n.ts");
|
||
|
||
const [code, tag, ...labelParts] = process.argv.slice(2);
|
||
const label = labelParts.join(" ");
|
||
if (!code || !tag || !label) {
|
||
console.error('Användning: node scripts/i18n-add-language.mjs <kod> <bcp47-tagg> "<eget namn>"');
|
||
console.error('Exempel: node scripts/i18n-add-language.mjs el el-GR "Ελληνικά"');
|
||
process.exit(1);
|
||
}
|
||
if (!/^[a-z]{2,3}$/.test(code)) {
|
||
console.error(`Ogiltig språkkod "${code}" – 2–3 gemener (t.ex. el, cs, uk).`);
|
||
process.exit(1);
|
||
}
|
||
if (!/^[a-z]{2,3}-[A-Za-z0-9]{2,4}$/.test(tag)) {
|
||
console.error(`Ogiltig BCP 47-tagg "${tag}" – t.ex. el-GR, cs-CZ, pt-BR.`);
|
||
process.exit(1);
|
||
}
|
||
if (existsSync(join(LOC, code, "common.json"))) {
|
||
console.error(`src/locales/${code}/common.json finns redan – avbryter.`);
|
||
process.exit(1);
|
||
}
|
||
|
||
const labelKey = `profile.language.${code}`;
|
||
const labelLine = ` ${JSON.stringify(labelKey)}: ${JSON.stringify(label)},`;
|
||
const ANCHOR = '"profile.language.sv":';
|
||
|
||
/** Sätt in etikettraden direkt efter profile.language.sv, annars först i objektet. */
|
||
function withLabel(raw) {
|
||
if (raw.includes(labelKey + '"') || raw.includes(labelKey + "'")) return raw; // finns redan
|
||
const lines = raw.split("\n");
|
||
let at = lines.findIndex((l) => l.includes(ANCHOR));
|
||
if (at === -1) at = lines.findIndex((l) => l.trim().startsWith("{")); // fallback: efter {
|
||
lines.splice(at + 1, 0, labelLine);
|
||
return lines.join("\n");
|
||
}
|
||
|
||
// ---- 1 + 3. Ny fil (kopia av sv + etikett) + etikett i alla befintliga -----
|
||
const svRaw = readFileSync(join(LOC, "sv/common.json"), "utf8");
|
||
mkdirSync(join(LOC, code), { recursive: true });
|
||
writeFileSync(join(LOC, code, "common.json"), withLabel(svRaw));
|
||
|
||
const existing = readdirSync(LOC).filter((d) => d !== code && statSync(join(LOC, d)).isDirectory());
|
||
for (const lang of existing) {
|
||
const p = join(LOC, lang, "common.json");
|
||
writeFileSync(p, withLabel(readFileSync(p, "utf8")));
|
||
}
|
||
|
||
// ---- 2. Registrera i i18n.ts (import + SUPPORTED_LANGUAGES + resources) -----
|
||
{
|
||
let lines = readFileSync(I18N, "utf8").split("\n");
|
||
const importRe = /^import \w+ from "\.\.\/locales\/\w+\/common\.json";$/;
|
||
let lastImport = -1;
|
||
lines.forEach((l, i) => importRe.test(l) && (lastImport = i));
|
||
lines.splice(lastImport + 1, 0, `import ${code} from "../locales/${code}/common.json";`);
|
||
|
||
const arrEnd = lines.findIndex((l) => l.trim() === "] as const;");
|
||
lines.splice(arrEnd, 0, ` { code: "${code}", tag: "${tag}", labelKey: "${labelKey}" },`);
|
||
|
||
const resRe = /^\s+\w+: \{ common: \w+ \},$/;
|
||
let lastRes = -1;
|
||
lines.forEach((l, i) => resRe.test(l) && (lastRes = i));
|
||
lines.splice(lastRes + 1, 0, ` ${code}: { common: ${code} },`);
|
||
|
||
writeFileSync(I18N, lines.join("\n"));
|
||
}
|
||
|
||
const keyCount = svRaw.split("\n").filter((l) => l.includes('": ')).length;
|
||
console.log(`\n✓ La till "${code}" (${tag}, "${label}").`);
|
||
console.log(
|
||
` • src/locales/${code}/common.json skapad (~${keyCount} nycklar, svenska platshållare)`,
|
||
);
|
||
console.log(` • registrerad i src/lib/i18n.ts (import + SUPPORTED_LANGUAGES + resources)`);
|
||
console.log(` • ${labelKey} = "${label}" i alla locale-filer`);
|
||
console.log(`\nNästa steg:`);
|
||
console.log(` 1. Översätt värdena i src/locales/${code}/common.json`);
|
||
console.log(` 2. pnpm --filter @app/mobile i18n:check`);
|
||
console.log(` 3. pnpm --filter @app/mobile typecheck`);
|