import assert from "node:assert/strict"; import { test } from "node:test"; import fs from "node:fs"; import path from "node:path"; import ts from "typescript"; import russian from "../shared/locales/ru.json"; import { detectLocale, resolveLocale, translator } from "../shared/i18n"; test("browser language preferences support regional tags and weighted fallbacks", () => { assert.equal(detectLocale("ru-RU,ru;q=0.9,en;q=0.8"), "ru"); assert.equal(detectLocale("en-US;q=0.3,ru;q=0.9"), "ru"); assert.equal(detectLocale("de-DE,ru;q=0.7,en;q=0.5"), "ru"); assert.equal(detectLocale("ru;q=0,en-GB;q=0.8"), "en"); assert.equal(detectLocale("ru;q=invalid,en"), "en"); assert.equal(detectLocale(null), "en"); assert.equal(detectLocale("fr-FR"), "en"); }); test("an explicit language choice overrides automatic detection", () => { assert.equal(resolveLocale("en", "ru-RU"), "en"); assert.equal(resolveLocale("ru", "en-US"), "ru"); assert.equal(resolveLocale("auto", "ru"), "ru"); assert.equal(resolveLocale("unexpected", "ru"), "ru"); }); test("translations interpolate values without changing player content", () => { const ru = translator("ru"), en = translator("en"); assert.equal(ru("Log in"), "Войти"); assert.equal(en("Log in"), "Log in"); assert.equal( ru("Keep {location}", { location: "Steve's Castle" }), "Оставить: Steve's Castle", ); assert.equal(ru("Map authored by Steve"), "Map authored by Steve"); assert.equal(ru("Keep {location}"), "Оставить: {location}"); }); test("all literal interface messages have Russian translations with matching placeholders", () => { const missing: string[] = [], mismatches: string[] = []; const dictionary = russian as Record; const placeholders = (s: string) => [...s.matchAll(/\{(\w+)\}/g)].map((match) => match[1]).sort(); function walk(directory: string) { for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const file = path.join(directory, entry.name); if (entry.isDirectory()) walk(file); else if (file.endsWith(".tsx")) { const source = ts.createSourceFile( file, fs.readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX, ); function visit(node: ts.Node) { if ( ts.isCallExpression(node) && node.expression.getText(source) === "t" && node.arguments[0] && ts.isStringLiteral(node.arguments[0]) ) { const message = node.arguments[0].text; if (!dictionary[message]) missing.push(`${file}: ${message}`); else if ( JSON.stringify(placeholders(message)) !== JSON.stringify(placeholders(dictionary[message])) ) mismatches.push(message); } ts.forEachChild(node, visit); } visit(source); } } } walk("app"); walk("components"); assert.deepEqual(missing, []); assert.deepEqual(mismatches, []); });