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) =>
|
||||
|
||||
+95
-1
@@ -2449,7 +2449,101 @@
|
||||
"readFailed": "Data could not be loaded",
|
||||
"emulatorConfig": "Emulator connection",
|
||||
"emailConfig": "Email delivery",
|
||||
"configured": "Configured"
|
||||
"configured": "Configured",
|
||||
"playersOnline": "Players online",
|
||||
"totalAccounts": "{count} total accounts",
|
||||
"dbUnavailable": "Database unavailable",
|
||||
"emulator": "Emulator",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"rconReachable": "Emulator connection available",
|
||||
"rconNotResponding": "Emulator is not responding",
|
||||
"database": "Database",
|
||||
"connected": "Connected",
|
||||
"down": "Unavailable",
|
||||
"queriesResponding": "Database queries are responding",
|
||||
"cannotReachDb": "Cannot connect to the database",
|
||||
"emulatorErrors": "Emulator errors",
|
||||
"last50Logged": "Up to 50 latest records",
|
||||
"serverInfo": "Server information",
|
||||
"node": "Node.js",
|
||||
"platform": "Platform",
|
||||
"appUptime": "Application uptime",
|
||||
"heapUsed": "Heap memory used",
|
||||
"rss": "Process memory (RSS)",
|
||||
"hostUptime": "Host uptime",
|
||||
"load1m": "Load average (1 min)",
|
||||
"cpus": "Logical CPUs",
|
||||
"systemDiagnostics": "System diagnostics",
|
||||
"connectionHealthy": "Connection is healthy",
|
||||
"unreachable": "Unreachable",
|
||||
"emulatorRcon": "Emulator connection",
|
||||
"respondingToPing": "Responding to health checks",
|
||||
"noResponse": "No response",
|
||||
"notConfigured": "Not configured",
|
||||
"twoFaEncryptionKey": "2FA encryption key",
|
||||
"appKeyConfigured": "Encryption key configured",
|
||||
"appKeyMissing": "Encryption key missing",
|
||||
"maintenanceMode": "Maintenance mode",
|
||||
"siteIsInMaintenance": "The site is in maintenance mode",
|
||||
"siteIsLive": "The site is open",
|
||||
"recentStaffActivity": "Recent staff activity",
|
||||
"noStaffActivity": "No recent staff activity",
|
||||
"colWhen": "Date and time",
|
||||
"colStaff": "Staff member",
|
||||
"colAction": "Action",
|
||||
"colDescription": "Description",
|
||||
"recentEmulatorErrors": "Recent emulator errors",
|
||||
"noEmulatorErrors": "No emulator errors recorded",
|
||||
"colType": "Type",
|
||||
"colVersion": "Version",
|
||||
"colStacktrace": "Error details",
|
||||
"truncated": "Further lines omitted",
|
||||
"empty": "No details available",
|
||||
"defaultGiftMessage": "A gift for you!",
|
||||
"emulatorControls": "Emulator commands",
|
||||
"updateCatalog": "Reload catalog",
|
||||
"updateCatalogDesc": "Reload the catalog from the database.",
|
||||
"updateWordFilter": "Reload word filter",
|
||||
"updateWordFilterDesc": "Apply the latest word filter settings.",
|
||||
"updateNavigator": "Reload navigator",
|
||||
"updateNavigatorDesc": "Reload the room navigator data.",
|
||||
"hotelAlert": "Hotel announcement",
|
||||
"hotelAlertDesc": "Send a message to all online players.",
|
||||
"messageToBroadcast": "Message for all players",
|
||||
"sendAlert": "Send announcement",
|
||||
"disconnectDesc": "Disconnect the selected player from the hotel.",
|
||||
"userId": "User ID",
|
||||
"usernameRequired": "Username (required)",
|
||||
"alertDesc": "Send a private alert to a player.",
|
||||
"alertMessage": "Alert message",
|
||||
"forwardToRoom": "Move player to room",
|
||||
"forwardDesc": "Send the selected player to a specific room.",
|
||||
"roomId": "Room ID",
|
||||
"forwardUser": "Move player",
|
||||
"giveCredits": "Give credits",
|
||||
"giveCreditsDesc": "Add credits to the selected player’s balance.",
|
||||
"amount": "Amount",
|
||||
"giveDuckets": "Give duckets",
|
||||
"giveDucketsDesc": "Add duckets to the selected player’s balance.",
|
||||
"giveDiamonds": "Give diamonds",
|
||||
"giveDiamondsDesc": "Add diamonds to the selected player’s balance.",
|
||||
"giveBadge": "Give badge",
|
||||
"giveBadgeDesc": "Give a badge to the selected player.",
|
||||
"badgeCode": "Badge code",
|
||||
"setMotto": "Change motto",
|
||||
"setMottoDesc": "Update the selected player’s motto.",
|
||||
"newMotto": "New motto",
|
||||
"setRank": "Change rank",
|
||||
"setRankDesc": "Update the selected player’s rank.",
|
||||
"rankPlaceholder": "Rank (1–10)",
|
||||
"executeCommand": "Execute emulator command",
|
||||
"executeCommandDesc": "Execute a command for the selected player.",
|
||||
"commandPlaceholder": "Command",
|
||||
"sendGift": "Send gift",
|
||||
"sendGiftDesc": "Send a furniture item as a gift to the selected player.",
|
||||
"furniId": "Furniture ID",
|
||||
"giftMessage": "Gift message"
|
||||
},
|
||||
"emulator": {
|
||||
"title": "Emulator Config",
|
||||
|
||||
+95
-1
@@ -2331,7 +2331,101 @@
|
||||
"readFailed": "Impossibile caricare i dati",
|
||||
"emulatorConfig": "Connessione emulatore",
|
||||
"emailConfig": "Invio email",
|
||||
"configured": "Configurato"
|
||||
"configured": "Configurato",
|
||||
"playersOnline": "Giocatori online",
|
||||
"totalAccounts": "{count} account totali",
|
||||
"dbUnavailable": "Database non disponibile",
|
||||
"emulator": "Emulatore",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"rconReachable": "Connessione all’emulatore disponibile",
|
||||
"rconNotResponding": "L’emulatore non risponde",
|
||||
"database": "Database",
|
||||
"connected": "Connesso",
|
||||
"down": "Non disponibile",
|
||||
"queriesResponding": "Il database risponde alle richieste",
|
||||
"cannotReachDb": "Impossibile connettersi al database",
|
||||
"emulatorErrors": "Errori emulatore",
|
||||
"last50Logged": "Fino agli ultimi 50 eventi registrati",
|
||||
"serverInfo": "Informazioni server",
|
||||
"node": "Node.js",
|
||||
"platform": "Piattaforma",
|
||||
"appUptime": "Tempo di attività applicazione",
|
||||
"heapUsed": "Memoria heap utilizzata",
|
||||
"rss": "Memoria processo (RSS)",
|
||||
"hostUptime": "Tempo di attività server",
|
||||
"load1m": "Carico medio (1 min)",
|
||||
"cpus": "CPU logiche",
|
||||
"systemDiagnostics": "Diagnostica sistema",
|
||||
"connectionHealthy": "Connessione funzionante",
|
||||
"unreachable": "Non raggiungibile",
|
||||
"emulatorRcon": "Connessione emulatore",
|
||||
"respondingToPing": "Risponde ai controlli di stato",
|
||||
"noResponse": "Nessuna risposta",
|
||||
"notConfigured": "Non configurato",
|
||||
"twoFaEncryptionKey": "Chiave di cifratura 2FA",
|
||||
"appKeyConfigured": "Chiave di cifratura configurata",
|
||||
"appKeyMissing": "Chiave di cifratura mancante",
|
||||
"maintenanceMode": "Modalità manutenzione",
|
||||
"siteIsInMaintenance": "Il sito è in manutenzione",
|
||||
"siteIsLive": "Il sito è aperto",
|
||||
"recentStaffActivity": "Attività recenti dello staff",
|
||||
"noStaffActivity": "Nessuna attività recente dello staff",
|
||||
"colWhen": "Data e ora",
|
||||
"colStaff": "Membro dello staff",
|
||||
"colAction": "Azione",
|
||||
"colDescription": "Descrizione",
|
||||
"recentEmulatorErrors": "Errori recenti dell’emulatore",
|
||||
"noEmulatorErrors": "Nessun errore dell’emulatore registrato",
|
||||
"colType": "Tipo",
|
||||
"colVersion": "Versione",
|
||||
"colStacktrace": "Dettagli errore",
|
||||
"truncated": "Ulteriori righe omesse",
|
||||
"empty": "Nessun dettaglio disponibile",
|
||||
"defaultGiftMessage": "Un regalo per te!",
|
||||
"emulatorControls": "Comandi emulatore",
|
||||
"updateCatalog": "Ricarica catalogo",
|
||||
"updateCatalogDesc": "Ricarica il catalogo dal database.",
|
||||
"updateWordFilter": "Ricarica filtro parole",
|
||||
"updateWordFilterDesc": "Applica le ultime impostazioni del filtro parole.",
|
||||
"updateNavigator": "Ricarica navigatore",
|
||||
"updateNavigatorDesc": "Ricarica i dati del navigatore stanze.",
|
||||
"hotelAlert": "Avviso hotel",
|
||||
"hotelAlertDesc": "Invia un messaggio a tutti i giocatori online.",
|
||||
"messageToBroadcast": "Messaggio per tutti i giocatori",
|
||||
"sendAlert": "Invia avviso",
|
||||
"disconnectDesc": "Disconnetti il giocatore selezionato dall’hotel.",
|
||||
"userId": "ID utente",
|
||||
"usernameRequired": "Nome utente (obbligatorio)",
|
||||
"alertDesc": "Invia un avviso privato a un giocatore.",
|
||||
"alertMessage": "Messaggio di avviso",
|
||||
"forwardToRoom": "Sposta giocatore in stanza",
|
||||
"forwardDesc": "Invia il giocatore selezionato in una stanza specifica.",
|
||||
"roomId": "ID stanza",
|
||||
"forwardUser": "Sposta giocatore",
|
||||
"giveCredits": "Assegna crediti",
|
||||
"giveCreditsDesc": "Aggiungi crediti al saldo del giocatore selezionato.",
|
||||
"amount": "Quantità",
|
||||
"giveDuckets": "Assegna duckets",
|
||||
"giveDucketsDesc": "Aggiungi duckets al saldo del giocatore selezionato.",
|
||||
"giveDiamonds": "Assegna diamanti",
|
||||
"giveDiamondsDesc": "Aggiungi diamanti al saldo del giocatore selezionato.",
|
||||
"giveBadge": "Assegna distintivo",
|
||||
"giveBadgeDesc": "Assegna un distintivo al giocatore selezionato.",
|
||||
"badgeCode": "Codice distintivo",
|
||||
"setMotto": "Modifica motto",
|
||||
"setMottoDesc": "Aggiorna il motto del giocatore selezionato.",
|
||||
"newMotto": "Nuovo motto",
|
||||
"setRank": "Modifica rank",
|
||||
"setRankDesc": "Aggiorna il rank del giocatore selezionato.",
|
||||
"rankPlaceholder": "Rank (1–10)",
|
||||
"executeCommand": "Esegui comando emulatore",
|
||||
"executeCommandDesc": "Esegui un comando per il giocatore selezionato.",
|
||||
"commandPlaceholder": "Comando",
|
||||
"sendGift": "Invia regalo",
|
||||
"sendGiftDesc": "Invia un furno in regalo al giocatore selezionato.",
|
||||
"furniId": "ID furno",
|
||||
"giftMessage": "Messaggio del regalo"
|
||||
},
|
||||
"emulator": {
|
||||
"title": "Configurazione emulatore",
|
||||
|
||||
Reference in new issue
Block a user