feat: add multilingual interface support
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const root = path.resolve(__dirname, "..");
|
||||
|
||||
test("the Docker image includes every application script referenced by the page", () => {
|
||||
const html = fs.readFileSync(path.join(root, "index.html"), "utf8");
|
||||
const dockerfile = fs.readFileSync(path.join(root, "Dockerfile"), "utf8");
|
||||
const applicationScripts = [...html.matchAll(/<script src="\.\/([^"]+\.js)"/g)]
|
||||
.map((match) => match[1])
|
||||
.filter((scriptPath) => !scriptPath.startsWith("vendor/"));
|
||||
|
||||
assert.ok(applicationScripts.length > 0);
|
||||
applicationScripts.forEach((scriptPath) => {
|
||||
assert.ok(fs.existsSync(path.join(root, scriptPath)), `${scriptPath} must exist`);
|
||||
assert.match(
|
||||
dockerfile,
|
||||
new RegExp(`^COPY ${scriptPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} \\.\\/$`, "m"),
|
||||
`${scriptPath} must be copied by the Dockerfile`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
DEFAULT_LANGUAGE,
|
||||
TRANSLATIONS,
|
||||
createI18n,
|
||||
detectLanguage,
|
||||
normalizeLanguage,
|
||||
} = require("../i18n.js");
|
||||
|
||||
function createStorage(initialValue) {
|
||||
let value = initialValue;
|
||||
return {
|
||||
getItem() {
|
||||
return value;
|
||||
},
|
||||
setItem(key, nextValue) {
|
||||
value = nextValue;
|
||||
},
|
||||
removeItem() {
|
||||
value = undefined;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("normalizes exact and base language tags", () => {
|
||||
assert.equal(normalizeLanguage("pt-PT"), "pt-PT");
|
||||
assert.equal(normalizeLanguage("pt-BR"), "pt-PT");
|
||||
assert.equal(normalizeLanguage("en-US"), "en");
|
||||
assert.equal(normalizeLanguage("fr"), null);
|
||||
});
|
||||
|
||||
test("prefers a stored language over browser preferences", () => {
|
||||
const storage = createStorage("en");
|
||||
assert.equal(detectLanguage({ storage, languages: ["pt-PT"] }), "en");
|
||||
});
|
||||
|
||||
test("uses the first supported browser language and otherwise falls back", () => {
|
||||
assert.equal(detectLanguage({ storage: createStorage(), languages: ["fr", "pt-BR"] }), "pt-PT");
|
||||
assert.equal(detectLanguage({ storage: createStorage(), languages: ["es-MX"] }), "es");
|
||||
assert.equal(detectLanguage({ storage: createStorage(), languages: ["fr"] }), DEFAULT_LANGUAGE);
|
||||
});
|
||||
|
||||
test("persists language changes and interpolates translated values", () => {
|
||||
const storage = createStorage();
|
||||
const i18n = createI18n({ storage, languages: ["en"] });
|
||||
|
||||
assert.equal(i18n.setLanguage("pt"), "pt-PT");
|
||||
assert.equal(storage.getItem(), "pt-PT");
|
||||
assert.equal(
|
||||
i18n.t("noParcelFound", { parcelNumber: "57" }),
|
||||
"Não foi encontrada a parcela com o número “57”.",
|
||||
);
|
||||
});
|
||||
|
||||
test("browser-default mode removes the preference and follows the browser language", () => {
|
||||
const storage = createStorage("en");
|
||||
const i18n = createI18n({ storage, languages: ["pt-PT"] });
|
||||
|
||||
assert.equal(i18n.setLanguage(null), "pt-PT");
|
||||
assert.equal(i18n.preference, null);
|
||||
assert.equal(storage.getItem(), undefined);
|
||||
});
|
||||
|
||||
test("uses English for missing translations and the key for unknown messages", () => {
|
||||
const storage = createStorage("pt-PT");
|
||||
const originalTranslation = TRANSLATIONS["pt-PT"].zoomIn;
|
||||
delete TRANSLATIONS["pt-PT"].zoomIn;
|
||||
|
||||
try {
|
||||
const i18n = createI18n({ storage, languages: [] });
|
||||
assert.equal(i18n.t("zoomIn"), TRANSLATIONS.en.zoomIn);
|
||||
assert.equal(i18n.t("notARealMessage"), "notARealMessage");
|
||||
} finally {
|
||||
TRANSLATIONS["pt-PT"].zoomIn = originalTranslation;
|
||||
}
|
||||
});
|
||||
|
||||
test("all shipped languages contain the default translation keys", () => {
|
||||
const defaultKeys = Object.keys(TRANSLATIONS[DEFAULT_LANGUAGE]).sort();
|
||||
Object.entries(TRANSLATIONS).forEach(([language, translations]) => {
|
||||
assert.deepEqual(Object.keys(translations).sort(), defaultKeys, language);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user