Add Spanish and French interface languages

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Hieu Nguyen (Jack)andClaude Sonnet 5.5 committed 2026-10-07 13:05:36 +07:00
1 parent 07f4ac2f70
commit e04ddf1d7e
4 files changed
+3738 -10

No files matched your search

+1863
View File
File diff suppressed because it is too large. Load diff
+1863
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -14,6 +14,8 @@ type LocaleEntry = {
export const locales = {
en: { label: "English", load: async () => en },
"pt-BR": { label: "Português (Brasil)", load: async () => (await import("./pt-BR.json")).default },
es: { label: "Español", load: async () => (await import("./es.json")).default },
fr: { label: "Français", load: async () => (await import("./fr.json")).default },
} satisfies Record<string, LocaleEntry>;
export type Locale = keyof typeof locales;
+10 -10
View File
@@ -49,7 +49,7 @@ async function bundleI18n(outfile, extraLocale = false) {
setup(builder) {
if (extraLocale) builder.onLoad({ filter: /\/i18n\/locales\.ts$/ }, ({ path }) => ({
contents: readFileSync(path, "utf8").replace("export const locales = {", `export const locales = {
es: { label: "Español", dir: "rtl", load: async () => ({ ...en, "navigation.inbox": "Entrada", "navigation.inbox.one": "{count} entrada", "navigation.inbox.other": "{count} entradas", "language.label": "Idioma" }) },`),
xx: { label: "Testlandic", dir: "rtl", load: async () => ({ ...en, "navigation.inbox": "Entrada", "navigation.inbox.one": "{count} entrada", "navigation.inbox.other": "{count} entradas", "language.label": "Idioma" }) },`),
loader: "ts",
}));
builder.onResolve({ filter: /^(next\/headers|next\/font\/google|@\/components\/providers)$/ }, (args) => ({ path: args.path, namespace: "stub" }));
@@ -139,17 +139,17 @@ test("a locale registered once reaches resolution, translation, cookie, selector
const extraFile = join(outDir, "with-spanish.mjs");
await bundleI18n(extraFile, true);
const extended = await import(pathToFileURL(extraFile).href);
assert.equal(extended.resolveLocale("es"), "es");
assert.equal(extended.translate(await extended.loadMessages("es"), "navigation.inbox"), "Entrada");
assert.match(extended.serializeLocaleCookie("es", true), /^mailflare-locale=es;/);
const selector = await extended.renderLanguage("es");
assert.match(selector, /value="es" lang="es" selected="">Español<\/option>/);
assert.equal(extended.resolveLocale("xx"), "xx");
assert.equal(extended.translate(await extended.loadMessages("xx"), "navigation.inbox"), "Entrada");
assert.match(extended.serializeLocaleCookie("xx", true), /^mailflare-locale=xx;/);
const selector = await extended.renderLanguage("xx");
assert.match(selector, /value="xx" lang="xx" selected="">Testlandic<\/option>/);
assert.match(selector, />Entrada<\/span>/);
const layout = await extended.renderLayout("es");
assert.match(layout, /<html lang="es"/);
const layout = await extended.renderLayout("xx");
assert.match(layout, /<html lang="xx"/);
assert.match(layout, />Entrada<\/span>/);
assert.match(layout, /<html lang="es" dir="rtl"/);
const t = extended.createTranslator("es", await extended.loadMessages("es"));
assert.match(layout, /<html lang="xx" dir="rtl"/);
const t = extended.createTranslator("xx", await extended.loadMessages("xx"));
assert.equal(t("navigation.inbox", { count: 1 }), "1 entrada");
assert.equal(t("navigation.inbox", { count: 3 }), "3 entradas");
assert.equal(t("navigation.inbox"), "Entrada");