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) { 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) => translate(locale, key, vars), [locale], ); return { t, locale }; } export function tStatic(key: I18nKey, vars?: Record) { 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); }