CoompanionApp/front/play/i18n.ts
2026-02-03 13:48:56 +01:00

421 lines
17 KiB
TypeScript

import { useCallback, useMemo } from "react";
type Locale = "en" | "fr";
const translations = {
en: {
"app.name": "Negopoly Bank",
"common.loading": "Loading...",
"common.notice": "Notice:",
"common.reset": "Reset",
"common.guest": "Guest",
"common.dummy": "Dummy",
"common.player": "Player",
"common.banker": "Banker",
"common.bank": "Bank",
"common.online": "online",
"common.offline": "Offline",
"common.name": "Name",
"common.startingBalance": "Starting balance",
"common.selectPlayer": "Select player",
"common.reason": "Reason",
"common.amount": "Amount",
"common.note": "Note",
"common.noReason": "No reason provided",
"common.from": "From",
"common.to": "To",
"common.apply": "Apply",
"common.force": "Force",
"common.trigger": "Trigger",
"common.send": "Send",
"common.download": "Download",
"common.load": "Load",
"common.save": "Save",
"common.transactions": "Transactions",
"common.noActivity": "No activity yet.",
"common.connecting": "Connecting to session {id}...",
"common.sessionLive": "Session {code} · live",
"common.continue": "Continue",
"tabs.dashboard": "Dashboard",
"tabs.tools": "Tools",
"tabs.home": "Home",
"tabs.transfers": "Transfers",
"tabs.chat": "Chat",
"entry.tagline": "Open a lobby or join the city.",
"entry.liveSessions": "Live sessions",
"entry.bankerControlled": "Banker controlled",
"entry.createTitle": "Create a session",
"entry.createSubtitle": "Become the banker and control the flow of money.",
"entry.bankerName": "Banker name",
"entry.openVault": "Open the vault",
"entry.joinTitle": "Join a session",
"entry.joinSubtitle": "Enter a session code to continue.",
"entry.sessionCode": "Session code",
"entry.codePlaceholder": "Enter code",
"entry.newPlayerLabel": "Create a new player",
"entry.playerName": "Player name",
"entry.joinAsNew": "Join as new player",
"entry.takeoverTitle": "Take over a dummy",
"entry.alreadyConnected": "You are already connected to this session.",
"entry.selectDummy": "Select dummy",
"entry.yourNameOptional": "Your name (optional)",
"entry.requestTakeover": "Request takeover",
"entry.noDummies": "No dummies available to take over yet.",
"entry.changeCode": "Change code",
"entry.alert.enterCode": "Enter a session code",
"entry.alert.sessionNotFound": "Session not found",
"entry.alert.selectDummy": "Select a dummy player",
"lobby.title": "Negopoly Lobby",
"lobby.sessionLabel": "Session {id}",
"lobby.joinTitle": "Join this lobby",
"lobby.loadingInfo": "Loading session info...",
"lobby.waitingState": "Waiting for the lobby state.",
"lobby.header": "Lobby · Session {code}",
"lobby.statusLine": "Status: {status} · {count} players",
"lobby.roster": "Lobby roster",
"lobby.startGame": "Start the game",
"lobby.waitingBanker": "Waiting for the banker to start the game.",
"lobby.sessionClosed": "Session closed.",
"lobby.inviteQr": "Invite QR",
"lobby.scanToJoin": "Scan to join this lobby instantly.",
"lobby.addDummyTitle": "Add dummy player",
"lobby.addDummySubtitle":
"Create a player for someone without the app. Dummies can be taken over later.",
"lobby.enterDummyName": "Enter a dummy name",
"lobby.addDummyButton": "Add dummy",
"lobby.errorLoadInfo": "Unable to load session info",
"banker.consoleTitle": "Banker Console",
"banker.controlsTitle": "Banker controls",
"banker.tools.playersTab": "Players",
"banker.tools.adminTab": "Admin",
"banker.playersTitle": "Players",
"banker.playerOverview": "Player overview",
"banker.noPlayers": "No players yet.",
"banker.adminControls": "Session controls",
"banker.adjustBalance": "Adjust balance",
"banker.adjustAmountPlaceholder": "+/- amount",
"banker.forceTransfer": "Force transfer",
"banker.createDummy": "Create dummy",
"banker.dummyName": "Dummy name",
"banker.addDummy": "Add dummy",
"banker.blackout": "EMP",
"banker.blackoutToggle": "Toggle EMP",
"banker.blackoutEnable": "Enable EMP",
"banker.blackoutDisable": "Disable EMP",
"banker.blackoutReason": "EMP reason",
"banker.endSession": "End session",
"banker.takeoverApprovals": "Takeover approvals",
"banker.wants": "Wants {name}",
"banker.approve": "Approve",
"banker.stateTitle": "GameState",
"banker.stateSubtitle": "Export, import, or resume a session from a saved snapshot.",
"banker.downloadState": "Download current GameState",
"banker.loadFromFile": "Load GameState from file",
"banker.loadFromStorage": "Load from browser storage",
"banker.stateDownloaded": "GameState downloaded.",
"banker.stateDownloadError": "Unable to download GameState.",
"banker.stateLoaded": "GameState loaded.",
"banker.stateLoadError": "Unable to load GameState.",
"banker.stateLoadInvalid": "Invalid GameState file.",
"banker.autosaveTitle": "AutoSave",
"banker.autosaveSubtitle": "Keep rolling backups in this browser.",
"banker.autosaveToggle": "Enable AutoSave",
"banker.autosaveEnabled": "AutoSave is enabled",
"banker.autosaveInterval": "Minutes between saves",
"banker.autosaveMinutes": "e.g. 3",
"banker.autosaveKeep": "Snapshots to keep",
"banker.autosaveCount": "e.g. 5",
"banker.autosaveNow": "Save now",
"banker.autosaveSaved": "AutoSave captured.",
"banker.autosaveFailed": "AutoSave failed.",
"banker.noAutosaves": "No autosaves yet.",
"banker.savedAt": "Saved {time}",
"player.deskTitle": "Player Desk",
"player.quickTransfer": "Quick transfer",
"player.sendTo": "Send to",
"player.noteOptional": "Note (optional)",
"player.notePlaceholder": "For what?",
"player.sendFunds": "Send funds",
"transfers.error": "Choose a player and a valid amount.",
"player.lastUpdated": "Last updated {time}",
"home.balance": "Balance",
"blackout.title": "EMP",
"blackout.defaultReason": "EMP in effect",
"blackout.active": "EMP active",
"chat.title": "Chats",
"chat.global": "Global chat",
"chat.conversationCount": "{count} conversations",
"chat.conversationCountOne": "1 conversation",
"chat.searchPlaceholder": "Search chats",
"chat.newTitle": "New chat",
"chat.newSubtitle": "Start a direct or group conversation",
"chat.direct": "Direct",
"chat.group": "Group",
"chat.groupName": "Group name",
"chat.groupPlaceholder": "e.g. Negotiators",
"chat.choosePlayers": "Choose players",
"chat.noPlayers": "No other players available yet.",
"chat.back": "Back",
"chat.backChats": "Chats",
"chat.noMessages": "No messages yet.",
"chat.startConversation": "Start the conversation.",
"chat.messagePlaceholder": "Message",
"chat.startChat": "Start chat",
"chat.everyone": "Everyone in the session",
"chat.directMessage": "Direct message",
"chat.memberCount": "{count} members",
"chat.memberCountOne": "1 member",
"chat.error.direct": "Choose one person to start a direct chat.",
"chat.error.groupName": "Give the group a name.",
"chat.error.member": "Select at least one member.",
"transaction.transfer": "Transfer",
"transaction.banker_adjust": "Banker adjustment",
"transaction.banker_force_transfer": "Forced transfer",
"status.lobby": "Lobby",
"status.active": "Active",
"status.ended": "Ended",
"connection.idle": "idle",
"connection.connecting": "connecting",
"connection.open": "connected",
"connection.error": "error",
"error.parseResponse": "Unable to parse server response",
"error.createSession": "Unable to create session",
"error.joinSession": "Unable to join session",
"error.connectionNotReady": "Connection not ready",
},
fr: {
"app.name": "Banque Negopoly",
"common.loading": "Chargement...",
"common.notice": "Info :",
"common.reset": "Réinitialiser",
"common.guest": "Invité",
"common.dummy": "Dummy",
"common.player": "Joueur",
"common.banker": "Banquier",
"common.bank": "Banque",
"common.online": "en ligne",
"common.offline": "Hors ligne",
"common.name": "Nom",
"common.startingBalance": "Solde de départ",
"common.selectPlayer": "Choisir un joueur",
"common.reason": "Raison",
"common.amount": "Montant",
"common.note": "Note",
"common.noReason": "Aucune raison fournie",
"common.from": "De",
"common.to": "À",
"common.apply": "Appliquer",
"common.force": "Forcer",
"common.trigger": "Déclencher",
"common.send": "Envoyer",
"common.download": "Télécharger",
"common.load": "Charger",
"common.save": "Enregistrer",
"common.transactions": "Transactions",
"common.noActivity": "Aucune activité.",
"common.connecting": "Connexion à la session {id}...",
"common.sessionLive": "Session {code} · en direct",
"common.continue": "Continuer",
"tabs.dashboard": "Tableau",
"tabs.tools": "Outils",
"tabs.home": "Accueil",
"tabs.transfers": "Transferts",
"tabs.chat": "Chat",
"entry.tagline": "Ouvrez un lobby ou rejoignez la ville.",
"entry.liveSessions": "Sessions en direct",
"entry.bankerControlled": "Contrôlé par le banquier",
"entry.createTitle": "Créer une session",
"entry.createSubtitle": "Devenez banquier et contrôlez le flux d'argent.",
"entry.bankerName": "Nom du banquier",
"entry.openVault": "Ouvrir le coffre",
"entry.joinTitle": "Rejoindre une session",
"entry.joinSubtitle": "Entrez un code pour continuer.",
"entry.sessionCode": "Code de session",
"entry.codePlaceholder": "Entrez le code",
"entry.newPlayerLabel": "Créer un nouveau joueur",
"entry.playerName": "Nom du joueur",
"entry.joinAsNew": "Rejoindre comme nouveau joueur",
"entry.takeoverTitle": "Reprendre un dummy",
"entry.alreadyConnected": "Vous êtes déjà connecté à cette session.",
"entry.selectDummy": "Choisir un dummy",
"entry.yourNameOptional": "Votre nom (optionnel)",
"entry.requestTakeover": "Demander la reprise",
"entry.noDummies": "Aucun dummy disponible pour le moment.",
"entry.changeCode": "Changer de code",
"entry.alert.enterCode": "Entrez un code de session",
"entry.alert.sessionNotFound": "Session introuvable",
"entry.alert.selectDummy": "Sélectionnez un dummy",
"lobby.title": "Lobby Negopoly",
"lobby.sessionLabel": "Session {id}",
"lobby.joinTitle": "Rejoindre ce lobby",
"lobby.loadingInfo": "Chargement des infos de session...",
"lobby.waitingState": "En attente de l'état du lobby.",
"lobby.header": "Lobby · Session {code}",
"lobby.statusLine": "Statut : {status} · {count} joueurs",
"lobby.roster": "Liste des joueurs",
"lobby.startGame": "Démarrer la partie",
"lobby.waitingBanker": "En attente du banquier pour démarrer.",
"lobby.sessionClosed": "Session terminée.",
"lobby.inviteQr": "QR d'invitation",
"lobby.scanToJoin": "Scannez pour rejoindre instantanément.",
"lobby.addDummyTitle": "Ajouter un dummy",
"lobby.addDummySubtitle":
"Créez un joueur pour quelqu'un sans l'application. Les dummies peuvent être repris.",
"lobby.enterDummyName": "Entrez un nom de dummy",
"lobby.addDummyButton": "Ajouter un dummy",
"lobby.errorLoadInfo": "Impossible de charger les infos de session",
"banker.consoleTitle": "Console banquier",
"banker.controlsTitle": "Contrôles banquier",
"banker.tools.playersTab": "Joueurs",
"banker.tools.adminTab": "Admin",
"banker.playersTitle": "Joueurs",
"banker.playerOverview": "Vue joueur",
"banker.noPlayers": "Pas encore de joueurs.",
"banker.adminControls": "Contrôles de session",
"banker.adjustBalance": "Ajuster le solde",
"banker.adjustAmountPlaceholder": "Montant +/-",
"banker.forceTransfer": "Forcer un transfert",
"banker.createDummy": "Créer un dummy",
"banker.dummyName": "Nom du dummy",
"banker.addDummy": "Ajouter un dummy",
"banker.blackout": "EMP",
"banker.blackoutToggle": "Basculer l'EMP",
"banker.blackoutEnable": "Activer l'EMP",
"banker.blackoutDisable": "Désactiver l'EMP",
"banker.blackoutReason": "Raison de l'EMP",
"banker.endSession": "Terminer la session",
"banker.takeoverApprovals": "Approbations de reprise",
"banker.wants": "Veut {name}",
"banker.approve": "Approuver",
"banker.stateTitle": "État de partie",
"banker.stateSubtitle": "Exportez, importez ou reprenez une partie depuis une sauvegarde.",
"banker.downloadState": "Télécharger l'état actuel",
"banker.loadFromFile": "Charger un état depuis un fichier",
"banker.loadFromStorage": "Charger depuis le navigateur",
"banker.stateDownloaded": "État téléchargé.",
"banker.stateDownloadError": "Impossible de télécharger l'état.",
"banker.stateLoaded": "État chargé.",
"banker.stateLoadError": "Impossible de charger l'état.",
"banker.stateLoadInvalid": "Fichier d'état invalide.",
"banker.autosaveTitle": "AutoSave",
"banker.autosaveSubtitle": "Conservez des sauvegardes dans ce navigateur.",
"banker.autosaveToggle": "Activer AutoSave",
"banker.autosaveEnabled": "AutoSave activé",
"banker.autosaveInterval": "Minutes entre sauvegardes",
"banker.autosaveMinutes": "ex. 3",
"banker.autosaveKeep": "Sauvegardes à conserver",
"banker.autosaveCount": "ex. 5",
"banker.autosaveNow": "Sauvegarder maintenant",
"banker.autosaveSaved": "Sauvegarde effectuée.",
"banker.autosaveFailed": "Échec de la sauvegarde.",
"banker.noAutosaves": "Aucune sauvegarde.",
"banker.savedAt": "Sauvé {time}",
"player.deskTitle": "Bureau joueur",
"player.quickTransfer": "Transfert rapide",
"player.sendTo": "Envoyer à",
"player.noteOptional": "Note (optionnel)",
"player.notePlaceholder": "Pour quoi ?",
"player.sendFunds": "Envoyer les fonds",
"transfers.error": "Choisissez un joueur et un montant valide.",
"player.lastUpdated": "Mis à jour {time}",
"home.balance": "Solde",
"blackout.title": "EMP",
"blackout.defaultReason": "EMP en cours",
"blackout.active": "EMP actif",
"chat.title": "Chats",
"chat.global": "Chat global",
"chat.conversationCount": "{count} conversations",
"chat.conversationCountOne": "1 conversation",
"chat.searchPlaceholder": "Rechercher un chat",
"chat.newTitle": "Nouveau chat",
"chat.newSubtitle": "Démarrer une conversation directe ou de groupe",
"chat.direct": "Direct",
"chat.group": "Groupe",
"chat.groupName": "Nom du groupe",
"chat.groupPlaceholder": "ex. Négociateurs",
"chat.choosePlayers": "Choisir des joueurs",
"chat.noPlayers": "Aucun autre joueur disponible.",
"chat.back": "Retour",
"chat.backChats": "Chats",
"chat.noMessages": "Aucun message.",
"chat.startConversation": "Démarrez la conversation.",
"chat.messagePlaceholder": "Message",
"chat.startChat": "Démarrer le chat",
"chat.everyone": "Tout le monde dans la session",
"chat.directMessage": "Message direct",
"chat.memberCount": "{count} membres",
"chat.memberCountOne": "1 membre",
"chat.error.direct": "Choisissez une personne pour un chat direct.",
"chat.error.groupName": "Donnez un nom au groupe.",
"chat.error.member": "Sélectionnez au moins un membre.",
"transaction.transfer": "Transfert",
"transaction.banker_adjust": "Ajustement banquier",
"transaction.banker_force_transfer": "Transfert forcé",
"status.lobby": "Lobby",
"status.active": "Active",
"status.ended": "Terminée",
"connection.idle": "inactif",
"connection.connecting": "connexion",
"connection.open": "connecté",
"connection.error": "erreur",
"error.parseResponse": "Impossible de lire la réponse du serveur",
"error.createSession": "Impossible de créer la session",
"error.joinSession": "Impossible de rejoindre la session",
"error.connectionNotReady": "Connexion non prête",
},
} as const;
type I18nKey = keyof typeof translations.en;
export function getLocale(): Locale {
if (typeof navigator !== "undefined") {
const raw = (navigator.languages?.[0] ?? navigator.language ?? "en").toLowerCase();
return raw.startsWith("fr") ? "fr" : "en";
}
return "en";
}
function translate(locale: Locale, key: I18nKey, vars?: Record<string, string | number>) {
const table = translations[locale] ?? translations.en;
let template = table[key] ?? translations.en[key] ?? key;
if (vars) {
Object.entries(vars).forEach(([name, value]) => {
template = template.replace(new RegExp(`\\{${name}\\}`, "g"), String(value));
});
}
return template;
}
export function useI18n() {
const locale = useMemo(getLocale, []);
const t = useCallback(
(key: I18nKey, vars?: Record<string, string | number>) => translate(locale, key, vars),
[locale],
);
return { t, locale };
}
export function tStatic(key: I18nKey, vars?: Record<string, string | number>) {
return translate(getLocale(), key, vars);
}
export function formatTransactionKind(
kind: "transfer" | "banker_adjust" | "banker_force_transfer",
t: (key: I18nKey) => string,
) {
return t(`transaction.${kind}` as I18nKey);
}
export function formatStatus(
status: "lobby" | "active" | "ended",
t: (key: I18nKey) => string,
) {
return t(`status.${status}` as I18nKey);
}
export function formatConnectionState(
state: "idle" | "connecting" | "open" | "error",
t: (key: I18nKey) => string,
) {
return t(`connection.${state}` as I18nKey);
}