fix(i18n): complete command center texts and merge client fallbacks
This commit is contained in:
1 parent
8b59bd1d2d
commit
b12e405b34
5 files changed
+272
-5
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createTranslator } from "next-intl";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import en from "@/messages/en.json";
|
||||
import itMessages from "@/messages/it.json";
|
||||
import nl from "@/messages/nl.json";
|
||||
import { mergeMessages } from "./merge-messages";
|
||||
|
||||
const namespace = "pages.admin.commandocentrum";
|
||||
const keys = new Set(
|
||||
["page.tsx", "commandocentrum-controls.tsx"].flatMap((file) =>
|
||||
Array.from(
|
||||
readFileSync(`src/app/admin/commandocentrum/${file}`, "utf8").matchAll(
|
||||
/\bt\("([^"]+)"/g,
|
||||
),
|
||||
(match) => match[1],
|
||||
),
|
||||
),
|
||||
);
|
||||
for (const group of ["hotel", "players", "rewards", "advanced"])
|
||||
keys.add(`groups.${group}`);
|
||||
|
||||
describe("command center translations", () => {
|
||||
it.each([
|
||||
["en", en],
|
||||
["it", itMessages],
|
||||
] as const)("provides every rendered message in %s", (locale, messages) => {
|
||||
const errors: unknown[] = [];
|
||||
const t = createTranslator({
|
||||
locale,
|
||||
messages: messages as typeof en,
|
||||
namespace,
|
||||
onError: (error) => errors.push(error),
|
||||
});
|
||||
for (const key of keys) {
|
||||
expect(t.has(key as Parameters<typeof t>[0]), key).toBe(true);
|
||||
expect(
|
||||
t(key as Parameters<typeof t>[0], { count: 42, time: "12:34" }),
|
||||
key,
|
||||
).not.toBe("");
|
||||
}
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
it("supplies nested fallback messages to a client translator and formats values", () => {
|
||||
const messages = mergeMessages(en, nl) as typeof en;
|
||||
const t = createTranslator({ locale: "nl", messages, namespace });
|
||||
expect(t("title")).toBe(nl.pages.admin.commandocentrum.title);
|
||||
expect(t("totalAccounts", { count: 42 })).toBe("42 total accounts");
|
||||
expect(t("groups.players")).toBe("Players");
|
||||
expect(t("checkedAt", { time: "12:34" })).toBe("Checked at 12:34");
|
||||
expect(nl.pages.admin.commandocentrum).not.toHaveProperty("totalAccounts");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
export type MessageTree = { [key: string]: string | MessageTree };
|
||||
|
||||
/** Fill missing translations before sending messages to server and client consumers. */
|
||||
export function mergeMessages(
|
||||
fallback: MessageTree,
|
||||
localized: MessageTree,
|
||||
): MessageTree {
|
||||
const result: MessageTree = { ...fallback };
|
||||
for (const [key, value] of Object.entries(localized)) {
|
||||
const base = fallback[key];
|
||||
result[key] =
|
||||
typeof value === "object" && typeof base === "object"
|
||||
? mergeMessages(base, value)
|
||||
: value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+12
-3
@@ -1,5 +1,6 @@
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { mergeMessages } from "./merge-messages";
|
||||
|
||||
// i18n WITHOUT routing: the locale is chosen by a `NEXT_LOCALE` cookie, then
|
||||
// the Accept-Language header, and finally English as fallback.
|
||||
@@ -83,11 +84,19 @@ export default getRequestConfig(async () => {
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages,
|
||||
messages: mergeMessages(fallback, messages),
|
||||
// Never throw on a missing message — use the English value, or a humanised
|
||||
// key as a last resort.
|
||||
getMessageFallback({ key }: { key: string }) {
|
||||
const fromEnglish = key
|
||||
getMessageFallback({
|
||||
namespace,
|
||||
key,
|
||||
}: {
|
||||
namespace?: string;
|
||||
key: string;
|
||||
}) {
|
||||
const fromEnglish = [namespace, key]
|
||||
.filter(Boolean)
|
||||
.join(".")
|
||||
.split(".")
|
||||
.reduce<unknown>(
|
||||
(o, k) =>
|
||||
|
||||
Reference in new issue
Block a user