77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { isSupportedLocale, resolveInitialLocale, resolveLocaleFromNavigator } from "./locales.ts";
|
|
import { messages } from "./messages.ts";
|
|
import { readStoredLocale, writeStoredLocale } from "./storage.ts";
|
|
import { defaultLocale, supportedLocales } from "./types.ts";
|
|
|
|
function flattenKeys(value: unknown, prefix = ""): string[] {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return [prefix];
|
|
}
|
|
|
|
return Object.entries(value as Record<string, unknown>).flatMap(([key, nestedValue]) =>
|
|
flattenKeys(nestedValue, prefix ? `${prefix}.${key}` : key),
|
|
);
|
|
}
|
|
|
|
describe("i18n locale model", () => {
|
|
it("supports Chinese, English, and Thai", () => {
|
|
assert.deepEqual([...supportedLocales], ["zh-CN", "en-US", "th-TH"]);
|
|
assert.equal(defaultLocale, "zh-CN");
|
|
});
|
|
|
|
it("accepts only supported locale codes", () => {
|
|
assert.equal(isSupportedLocale("zh-CN"), true);
|
|
assert.equal(isSupportedLocale("en-US"), true);
|
|
assert.equal(isSupportedLocale("th-TH"), true);
|
|
assert.equal(isSupportedLocale("en"), false);
|
|
assert.equal(isSupportedLocale("fr-FR"), false);
|
|
});
|
|
|
|
it("maps browser languages to supported locales", () => {
|
|
assert.equal(resolveLocaleFromNavigator(["zh-Hans-CN"]), "zh-CN");
|
|
assert.equal(resolveLocaleFromNavigator(["en-GB"]), "en-US");
|
|
assert.equal(resolveLocaleFromNavigator(["th"]), "th-TH");
|
|
assert.equal(resolveLocaleFromNavigator(["fr-FR"]), defaultLocale);
|
|
});
|
|
|
|
it("prefers stored locale over browser language", () => {
|
|
assert.equal(resolveInitialLocale({ storedLocale: "th-TH", navigatorLanguages: ["en-US"] }), "th-TH");
|
|
});
|
|
|
|
it("falls back to browser language when stored locale is invalid", () => {
|
|
assert.equal(resolveInitialLocale({ storedLocale: "invalid", navigatorLanguages: ["en-US"] }), "en-US");
|
|
});
|
|
|
|
it("keeps translation key structure consistent across locales", () => {
|
|
const referenceKeys = flattenKeys(messages["zh-CN"]).sort();
|
|
|
|
for (const locale of supportedLocales) {
|
|
assert.deepEqual(flattenKeys(messages[locale]).sort(), referenceKeys, locale);
|
|
}
|
|
});
|
|
|
|
it("does not throw when browser storage is unavailable", () => {
|
|
const descriptor = Object.getOwnPropertyDescriptor(globalThis, "localStorage");
|
|
|
|
Object.defineProperty(globalThis, "localStorage", {
|
|
configurable: true,
|
|
get() {
|
|
throw new Error("storage blocked");
|
|
},
|
|
});
|
|
|
|
try {
|
|
assert.equal(readStoredLocale(), null);
|
|
assert.doesNotThrow(() => writeStoredLocale("zh-CN"));
|
|
} finally {
|
|
if (descriptor) {
|
|
Object.defineProperty(globalThis, "localStorage", descriptor);
|
|
} else {
|
|
Reflect.deleteProperty(globalThis, "localStorage");
|
|
}
|
|
}
|
|
});
|
|
});
|