133 lines
4.9 KiB
TypeScript
133 lines
4.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import { getLanguageTag, setLocale, t } from "../src/lib/i18n";
|
||
import { formatMinor } from "../src/lib/money";
|
||
import sv from "../src/locales/sv/common.json";
|
||
import en from "../src/locales/en/common.json";
|
||
import es from "../src/locales/es/common.json";
|
||
import it_ from "../src/locales/it/common.json";
|
||
import de from "../src/locales/de/common.json";
|
||
import fr from "../src/locales/fr/common.json";
|
||
import da from "../src/locales/da/common.json";
|
||
import nb from "../src/locales/nb/common.json";
|
||
import fi from "../src/locales/fi/common.json";
|
||
import nl from "../src/locales/nl/common.json";
|
||
import pl from "../src/locales/pl/common.json";
|
||
import pt from "../src/locales/pt/common.json";
|
||
|
||
describe("i18n (i18n-spec §5, §7–8 – M4, D-031)", () => {
|
||
it("ALLA 12 språk har samma basnycklar (pluralvarianter får skilja per språk)", () => {
|
||
const baseKeys = (obj: Record<string, string>) =>
|
||
[
|
||
...new Set(
|
||
Object.keys(obj).map((k) => {
|
||
const suffix = k.split("_").pop();
|
||
return suffix && ["one", "few", "many", "other"].includes(suffix)
|
||
? k.slice(0, k.lastIndexOf("_"))
|
||
: k;
|
||
}),
|
||
),
|
||
].sort();
|
||
const base = baseKeys(sv);
|
||
const locales = { en, es, it: it_, de, fr, da, nb, fi, nl, pl, pt };
|
||
for (const [name, locale] of Object.entries(locales)) {
|
||
expect(baseKeys(locale), `språk: ${name}`).toEqual(base);
|
||
}
|
||
});
|
||
|
||
it("interpolerar {brand} från konfig – aldrig hårdkodat namn", () => {
|
||
setLocale("sv-SE");
|
||
const text = t("memory.title");
|
||
expect(text).not.toContain("{brand}");
|
||
expect(text.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
it("plural via Intl.PluralRules: 1 dag / 3 dagar", () => {
|
||
setLocale("sv-SE");
|
||
expect(t("home.expiresIn", { days: 1, count: 1 })).toBe("1 dag kvar");
|
||
expect(t("home.expiresIn", { days: 3, count: 3 })).toBe("3 dagar kvar");
|
||
});
|
||
|
||
it("språkbyte utan omstart + fallback till sv för okända språk", () => {
|
||
setLocale("en-US");
|
||
expect(t("tabs.home")).toBe("At home");
|
||
expect(getLanguageTag()).toBe("en-US");
|
||
setLocale("zz-ZZ"); // stöds inte -> sv
|
||
expect(t("tabs.home")).toBe("Hemma");
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("pengar formatteras per språk ur minor units (i18n-spec §20)", () => {
|
||
setLocale("sv-SE");
|
||
const svText = formatMinor(7900, "SEK");
|
||
expect(svText).toMatch(/79/);
|
||
setLocale("en-US");
|
||
const enText = formatMinor(7900, "SEK");
|
||
expect(enText).toMatch(/79/);
|
||
expect(enText).not.toBe(svText); // olika locale -> olika formatering
|
||
setLocale("sv-SE");
|
||
});
|
||
});
|
||
|
||
describe("språkpolicy D-031 – automatik + manuellt val", () => {
|
||
it("spanska väljs och pluralformer fungerar", () => {
|
||
setLocale("es-ES");
|
||
expect(t("tabs.whatToEat")).toBe("¿Qué comemos?");
|
||
expect(t("home.expiresIn", { days: 1, count: 1 })).toBe("Queda 1 día");
|
||
expect(t("home.expiresIn", { days: 3, count: 3 })).toBe("Quedan 3 días");
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("regional variant faller till primärspråket (es-MX -> es)", () => {
|
||
setLocale("es-MX");
|
||
expect(t("tabs.home")).toBe("En casa");
|
||
expect(getLanguageTag()).toBe("es-MX"); // Intl behåller regionen
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("italienska, tyska och franska svarar på sina språk", () => {
|
||
setLocale("it-IT");
|
||
expect(t("tabs.whatToEat")).toBe("Cosa mangiamo?");
|
||
setLocale("de-DE");
|
||
expect(t("tabs.whatToEat")).toBe("Was essen wir?");
|
||
setLocale("fr-FR");
|
||
expect(t("tabs.whatToEat")).toBe("On mange quoi ?");
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("polska pluralformer: one/few/many via Intl.PluralRules", () => {
|
||
setLocale("pl-PL");
|
||
expect(t("recipe.portions", { count: 1 })).toBe("1 porcja");
|
||
expect(t("recipe.portions", { count: 3 })).toBe("3 porcje");
|
||
expect(t("recipe.portions", { count: 5 })).toBe("5 porcji");
|
||
expect(t("recipe.portions", { count: 22 })).toBe("22 porcje");
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("nordiska språk + nederländska/portugisiska svarar rätt", () => {
|
||
setLocale("da-DK");
|
||
expect(t("tabs.whatToEat")).toBe("Hvad skal vi spise?");
|
||
setLocale("nb-NO");
|
||
expect(t("tabs.whatToEat")).toBe("Hva skal vi spise?");
|
||
setLocale("fi-FI");
|
||
expect(t("tabs.whatToEat")).toBe("Mitä syödään?");
|
||
setLocale("nl-NL");
|
||
expect(t("tabs.whatToEat")).toBe("Wat eten we?");
|
||
setLocale("pt-PT");
|
||
expect(t("tabs.whatToEat")).toBe("O que vamos comer?");
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("ostött språk faller till svenska i t(), utan krasch", () => {
|
||
setLocale("el-GR"); // grekiska stöds inte ännu
|
||
expect(t("tabs.home")).toBe("Hemma");
|
||
setLocale("sv-SE");
|
||
});
|
||
|
||
it("pt-BR får portugisiska resurser med brasiliansk Intl-formattering", () => {
|
||
setLocale("pt-BR");
|
||
expect(t("tabs.home")).toBe("Em casa");
|
||
expect(getLanguageTag()).toBe("pt-BR");
|
||
setLocale("sv-SE");
|
||
});
|
||
});
|