mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 11:30:17 +00:00
Translate all out-of-game UI (start screen, lobbies, etc.) (#316)
This PR adds full translation support for all out-of-game UI elements, including the start screen, public/private lobby modals, and other pre-game interfaces. All static text has been externalized to en.json and ja.json for future language support. If you find any spots that are not yet translated (missing from the JSON), please let me know. Thanks a lot! This is a follow-up to PR [#305](https://github.com/openfrontio/OpenFrontIO/pull/305). --------- Co-authored-by: Cldprv <dubois.cnm@tutanota.com> Co-authored-by: jacks0n <rosty.west89@gmail.com>
This commit is contained in:
@@ -88,5 +88,82 @@
|
||||
"icon_crown": "Crown - This is the number 1 player in the leaderboard",
|
||||
"icon_traitor": "Crossed swords - Traitor. This player attacked an ally.",
|
||||
"icon_ally": "Handshake - Ally. This player is your ally."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Single Player",
|
||||
"map": "Map",
|
||||
"difficulty": "difficulty",
|
||||
"allow_alliances": "Allow alliances",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Disabled",
|
||||
"disable_nations": "Disable Nations",
|
||||
"instant_build": "Instant build",
|
||||
"infinite_gold": "Infinite gold",
|
||||
"infinite_troops": "Infinite troops",
|
||||
"disable_nukes": "Disable Nukes",
|
||||
"start": "Start Game"
|
||||
},
|
||||
"map": {
|
||||
"world": "World",
|
||||
"europe": "Europe",
|
||||
"mena": "MENA",
|
||||
"northamerica": "North America",
|
||||
"oceania": "Oceania",
|
||||
"blacksea": "Black Sea",
|
||||
"africa": "Africa",
|
||||
"asia": "Asia",
|
||||
"mars": "Mars",
|
||||
"southamerica": "South America",
|
||||
"britannia": "Britannia",
|
||||
"gatewaytotheatlantic": "Gateway to the Atlantic",
|
||||
"australia": "Australia",
|
||||
"iceland": "Iceland",
|
||||
"random": "Random"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Join Private Lobby",
|
||||
"enter_id": "Enter Lobby ID",
|
||||
"player": "Player",
|
||||
"players": "Players",
|
||||
"join_lobby": "Join Lobby",
|
||||
"checking": "Checking lobby...",
|
||||
"not_found": "Lobby not found. Please check the ID and try again.",
|
||||
"error": "An error occurred. Please try again.",
|
||||
"joined_waiting": "Joined successfully! Waiting for game to start..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Join next Game",
|
||||
"waiting": "players waiting"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Enter your username",
|
||||
"not_string": "Username must be a string.",
|
||||
"too_short": "Username must be at least {min} characters long.",
|
||||
"too_long": "Username must not exceed {max} characters.",
|
||||
"invalid_chars": "Username can only contain letters, numbers, spaces, underscores, and [square brackets]."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Private Lobby",
|
||||
"map": "Map",
|
||||
"difficulty": "Difficulty",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Disabled",
|
||||
"disable_nations": "Disable Nations",
|
||||
"instant_build": "Instant build",
|
||||
"infinite_gold": "Infinite gold",
|
||||
"infinite_troops": "Infinite troops",
|
||||
"disable_nukes": "Disable Nukes",
|
||||
"player": "Player",
|
||||
"players": "Players",
|
||||
"waiting": "Waiting for players...",
|
||||
"start": "Start Game"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Relaxed",
|
||||
"Balanced": "Balanced",
|
||||
"Intense": "Intense",
|
||||
"Impossible": "Impossible"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"main": {
|
||||
"join_discord": "Rejoignez le Discord !",
|
||||
"create_lobby": "Créer un Salon",
|
||||
"join_lobby": "Rejoindre un Salon",
|
||||
"single_player": "Jouer seul",
|
||||
"instructions": "Instructions",
|
||||
"how_to_play": "Comment jouer ?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"help_modal": {
|
||||
"hotkeys": "Raccourcis clavier",
|
||||
|
||||
"table_key": "Touche",
|
||||
|
||||
"table_action": "Action",
|
||||
|
||||
"action_alt_view": "Vue alternative (terrain/pays)",
|
||||
"action_attack_altclick": "Attaquer (quand le clic gauche est configuré pour ouvrir le menu)",
|
||||
"action_build": "Ouvrir le menu de construction",
|
||||
"action_center": "Recentrer la caméra",
|
||||
"action_zoom": "Zoom avant/arrière",
|
||||
"action_move_camera": "Déplacer la caméra",
|
||||
"action_ratio_change": "Diminuer/Augmenter le ratio d'attaque",
|
||||
"action_reset_gfx": "Réinitialiser les graphismes",
|
||||
|
||||
"ui_section": "Interface utilisateur du jeu",
|
||||
"ui_leaderboard": "Classement",
|
||||
"ui_leaderboard_desc": "Affiche les meilleurs joueurs du jeu avec leurs noms, le % de territoire possédé et l'or.",
|
||||
"ui_control": "Panneau de contrôle",
|
||||
"ui_control_desc": "Le panneau de contrôle contient les éléments suivants :",
|
||||
"ui_pop": "Pop - Le nombre d'unités que vous avez, votre population maximale et le taux auquel vous les obtenez.",
|
||||
"ui_gold": "Or - La quantité d'or que vous avez et le taux auquel vous l'obtenez.",
|
||||
"ui_troops_workers": "Troupes et Ouvriers - Le nombre de troupes et d'ouvriers alloués. Les troupes sont utilisées pour attaquer ou se défendre. Les ouvriers sont utilisés pour générer de l'or. Vous pouvez ajuster le nombre de troupes et d'ouvriers en utilisant le curseur.",
|
||||
"ui_attack_ratio": "Ratio d'attaque - Le nombre de troupes qui seront utilisées lorsque vous attaquez. Vous pouvez ajuster le ratio d'attaque en utilisant le curseur.",
|
||||
|
||||
"ui_options": "Options",
|
||||
"ui_options_desc": "Les éléments suivants peuvent être trouvés à l'intérieur :",
|
||||
"option_pause": "Mettre en pause/Reprendre le jeu - Disponible uniquement en mode solo.",
|
||||
"option_timer": "Chronomètre - Temps écoulé depuis le début du jeu.",
|
||||
"option_exit": "Bouton de sortie.",
|
||||
"option_settings": "Paramètres - Ouvrir le menu des paramètres. Vous pouvez y activer la Vue Alternative, le Mode Sombre, les Emojis et l'action sur le clic gauche.",
|
||||
|
||||
"radial_title": "Menu radial",
|
||||
"radial_desc": "Un clic droit (ou un toucher sur mobile) ouvre le menu radial. De là, vous pouvez :",
|
||||
"radial_build": "Ouvrir le menu de construction.",
|
||||
"radial_info": "Ouvrir le menu d'informations.",
|
||||
"radial_boat": "Envoyer un bateau pour attaquer à l'emplacement sélectionné (disponible uniquement si vous avez accès à l'eau).",
|
||||
"radial_close": "Fermer le menu.",
|
||||
|
||||
"info_title": "Menu d'informations",
|
||||
"info_enemy_desc": "Contient des informations telles que le nom du joueur sélectionné, l'or, les troupes, et si le joueur est un traître. Un traître est un joueur qui a trahi et attaqué un joueur qui était en alliance avec lui. Les icônes ci-dessous représentent les interactions suivantes :",
|
||||
"info_target": "Placer une marque cible sur le joueur, le marquant pour tous les alliés, utilisée pour coordonner les attaques.",
|
||||
"info_alliance": "Envoyer une demande d'alliance au joueur. Les alliés peuvent partager des ressources et des troupes, mais ne peuvent pas s'attaquer entre eux.",
|
||||
"info_emoji": "Envoyer un emoji au joueur.",
|
||||
|
||||
"info_ally_panel": "Panneau d'informations des alliés",
|
||||
"info_ally_desc": "Lorsque vous vous alliez avec un joueur, les nouvelles icônes suivantes deviennent disponibles :",
|
||||
"ally_betray": "Trahir un allié, mettant fin à l'alliance. Vous aurez alors une icône permanente à côté de votre nom. Les bots seront moins susceptibles de s'allier avec vous et les joueurs, à qui vous demanderez alliance feront plus attention.",
|
||||
"ally_donate": "Donner une partie de vos troupes à un allié. Utilisé lorsqu'ils manquent de troupes et sont attaqués, ou lorsqu'ils ont besoin de puissance supplémentaire pour écraser un ennemi.",
|
||||
|
||||
"build_menu_title": "Menu de construction",
|
||||
"build_name": "Nom",
|
||||
"build_icon": "Icône",
|
||||
"build_desc": "Description",
|
||||
|
||||
"build_city": "Ville",
|
||||
"build_city_desc": "Utile quand vous ne pouvez pas agrandir votre territoire ou quand vous atteignez votre limite de population.",
|
||||
"build_defense": "Poste de défense",
|
||||
"build_defense_desc": "Augmente les défenses autour des frontières proches. Les attaques des ennemis sont plus lentes et causent plus de pertes.",
|
||||
"build_port": "Port",
|
||||
"build_port_desc": "Envoie automatiquement des navires marchands en partant de vos ports vers d'autres pays (sauf si vous avez cliqué sur \"arrêter le commerce\" sur un joueur, ou s'ils ont cliqué sur \"arrêter le commerce\" sur vous), donnant de l'or aux deux côtés. Permet de construire des navires de guerre. Ne peut être construit qu'à proximité de l'eau.",
|
||||
"build_warship": "Navire de guerre",
|
||||
"build_warship_desc": "Patrouille dans une zone, capturant des navires marchands et détruisant des navires de guerre et des bateaux ennemis. Apparaît dans le port le plus proche et patrouille dans la zone cliquée.",
|
||||
"build_silo": "Silo à missiles",
|
||||
"build_silo_desc": "Permet de lancer des missiles.",
|
||||
"build_sam": "Lanceur SAM",
|
||||
"build_sam_desc": "A 75 % de chance d'intercepter les missiles ennemis dans sa portée de 100 pixels. Le SAM a un temps de recharge de 7,5 secondes et ne peut pas intercepter les MIRV.",
|
||||
"build_atom": "Bombe atomique",
|
||||
"build_atom_desc": "Petite bombe explosive qui détruit le territoire, les bâtiments, les navires et les bateaux. Apparaît depuis le Silo à missiles le plus proche et atterrit dans la zone cliquée.",
|
||||
"build_hydrogen": "Bombe à hydrogène",
|
||||
"build_hydrogen_desc": "Grande bombe explosive. Apparaît depuis le Silo à missiles le plus proche et atterrit dans la zone cliquée.",
|
||||
"build_mirv": "MIRV",
|
||||
"build_mirv_desc": "La bombe la plus puissante du jeu. Se divise en plus petites bombes qui couvriront une vaste zone de territoire. Ne fait des dégâts qu'au joueur sur lequel vous avez cliqué pour la construire. Apparaît depuis le Silo à missiles le plus proche et atterrit dans la zone cliquée.",
|
||||
|
||||
"player_icons": "Icônes des joueurs",
|
||||
"icon_desc": "Exemples de certaines icônes du jeu que vous rencontrerez et leur signification :",
|
||||
"icon_crown": "Couronne - Ce joueur est le numéro 1 du classement",
|
||||
"icon_traitor": "Épées croisées - Traître. Ce joueur a attaqué un allié.",
|
||||
"icon_ally": "Poignée de main - Allié. Ce joueur est votre allié."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Joueur seul",
|
||||
"map": "Carte",
|
||||
"difficulty": "Difficulté",
|
||||
"allow_alliances": "Autoriser les alliances",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots : ",
|
||||
"bots_disabled": "Désactivé",
|
||||
"disable_nations": "Désactiver les nations",
|
||||
"instant_build": "Construction instantanée",
|
||||
"infinite_gold": "Or infini",
|
||||
"infinite_troops": "Troupes infinies",
|
||||
"disable_nukes": "Désactiver les armes nucléaires",
|
||||
"start": "Commencer la partie"
|
||||
},
|
||||
"map": {
|
||||
"world": "Monde",
|
||||
"europe": "Europe",
|
||||
"mena": "MENA",
|
||||
"northamerica": "Amérique du Nord",
|
||||
"oceania": "Océanie",
|
||||
"blacksea": "Mer Noire",
|
||||
"africa": "Afrique",
|
||||
"asia": "Asie",
|
||||
"mars": "Mars",
|
||||
"southamerica": "Amérique du Sud",
|
||||
"britannia": " Grande-Bretagne",
|
||||
"gatewaytotheatlantic": "Porte de l'Atlantique",
|
||||
"australia": "Australie",
|
||||
"random": "Aléatoire"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Rejoindre un salon privé",
|
||||
"enter_id": "Entrez l'ID du salon",
|
||||
"player": "Joueur",
|
||||
"players": "Joueurs",
|
||||
"join_lobby": "Rejoindre le salon",
|
||||
"checking": "Vérification du salon...",
|
||||
"not_found": "Salon introuvable. Veuillez vérifier l'ID et réessayer.",
|
||||
"error": "Une erreur est survenue. Veuillez réessayer.",
|
||||
"joined_waiting": "Rejoint avec succès ! En attente du début de la partie..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Rejoindre la prochaine partie",
|
||||
"waiting": "joueurs en attente"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Entrez votre nom d'utilisateur",
|
||||
"not_string": "Le nom d'utilisateur doit être une chaîne de caractères.",
|
||||
"too_short": "Le nom d'utilisateur doit comporter au moins {min} caractères.",
|
||||
"too_long": "Le nom d'utilisateur ne doit pas dépasser {max} caractères.",
|
||||
"invalid_chars": "Le nom d'utilisateur ne peut contenir que des lettres, des chiffres, des espaces, des tirets bas et des [crochets]."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Salon privé",
|
||||
"map": "Carte",
|
||||
"difficulty": "Difficulté",
|
||||
"options_title": "Options",
|
||||
"bots": "Bots : ",
|
||||
"bots_disabled": "Désactivé",
|
||||
"disable_nations": "Désactiver les nations",
|
||||
"instant_build": "Construction instantanée",
|
||||
"infinite_gold": "Or infini",
|
||||
"infinite_troops": "Troupes infinies",
|
||||
"disable_nukes": "Désactiver les armes nucléaires",
|
||||
"player": "Joueur",
|
||||
"players": "Joueurs",
|
||||
"waiting": "En attente de joueurs...",
|
||||
"start": "Commencer la partie"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Détendu",
|
||||
"Balanced": "Équilibré",
|
||||
"Intense": "Intense",
|
||||
"Impossible": "Impossible"
|
||||
}
|
||||
}
|
||||
@@ -77,5 +77,83 @@
|
||||
"icon_crown": "王冠 - リーダーボード1位のプレイヤー",
|
||||
"icon_traitor": "交差した剣 - 裏切り者(同盟を攻撃)",
|
||||
"icon_ally": "握手 - 味方(同盟関係)"
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "シングルプレイヤー",
|
||||
"map": "マップ",
|
||||
"difficulty": "難易度",
|
||||
"allow_alliances": "同盟を許可",
|
||||
"options_title": "オプション",
|
||||
"bots": "ボット数: ",
|
||||
"bots_disabled": "無効",
|
||||
"disable_nations": "国家を無効化",
|
||||
"instant_build": "即時建設",
|
||||
"infinite_gold": "資金無限",
|
||||
"infinite_troops": "兵士無限",
|
||||
"disable_nukes": "核兵器使用禁止",
|
||||
"start": "ゲーム開始"
|
||||
},
|
||||
"map": {
|
||||
"world": "世界",
|
||||
"europe": "ヨーロッパ",
|
||||
"mena": "中東・北アフリカ",
|
||||
"northamerica": "北アメリカ",
|
||||
"oceania": "オセアニア",
|
||||
"blacksea": "黒海",
|
||||
"africa": "アフリカ",
|
||||
"asia": "アジア",
|
||||
"mars": "火星",
|
||||
"southamerica": "南アメリカ",
|
||||
"britannia": "ブリタニア",
|
||||
"gatewaytotheatlantic": "西ヨーロッパ",
|
||||
"australia": "オーストラリア",
|
||||
"iceland": "アイスランド",
|
||||
"random": "ランダム"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "プライベートゲームに参加",
|
||||
"enter_id": "ロビーIDを入力",
|
||||
"player": "人のプレイヤー",
|
||||
"players": "人のプレイヤー",
|
||||
"join_lobby": "ロビーに参加",
|
||||
"checking": "ロビーを確認中...",
|
||||
"not_found": "ロビーが見つかりません。IDを確認してもう一度お試しください。",
|
||||
"error": "エラーが発生しました。もう一度お試しください。",
|
||||
"joined_waiting": "参加に成功しました!ゲーム開始をお待ちください..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "次のゲームに参加",
|
||||
"waiting": "人が参加しています..."
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "ユーザー名を入力",
|
||||
"not_string": "ユーザー名は文字列で入力してください。",
|
||||
"too_short": "ユーザー名は{min}文字より長い必要があります。",
|
||||
"too_long": "ユーザー名は{max}文字より短い必要があります",
|
||||
"invalid_chars": "ユーザー名には英字、数字、スペース、アンダースコア、および [角括弧] のみ使用できます。"
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "プライベートロビー",
|
||||
"map": "マップ",
|
||||
"difficulty": "難易度",
|
||||
"allow_alliances": "同盟を許可",
|
||||
"options_title": "オプション",
|
||||
"bots": "ボット数: ",
|
||||
"bots_disabled": "無効",
|
||||
"disable_nations": "実在する国家を無効化",
|
||||
"instant_build": "即時建設",
|
||||
"infinite_gold": "資金無限",
|
||||
"infinite_troops": "兵士無限",
|
||||
"disable_nukes": "核兵器使用禁止",
|
||||
"player": "プレイヤー",
|
||||
"players": "プレイヤー",
|
||||
"waiting": "他のプレイヤーの参加を待っています...",
|
||||
"start": "ゲーム開始"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "簡単",
|
||||
"Balanced": "普通",
|
||||
"Intense": "難しい",
|
||||
"Impossible": "不可能"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
{
|
||||
"main": {
|
||||
"join_discord": "Word lid van de Discord!",
|
||||
"create_lobby": "Maak een Lobby aan",
|
||||
"join_lobby": "Treed een Lobby toe",
|
||||
"single_player": "Enkelspeler",
|
||||
"instructions": "Instructies",
|
||||
"how_to_play": "Hoe spelen?",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"help_modal": {
|
||||
"hotkeys": "Sneltoetsen",
|
||||
|
||||
"table_key": "Toets",
|
||||
|
||||
"table_action": "Actie",
|
||||
|
||||
"action_alt_view": "Alternatieve weergave (terrein/landen)",
|
||||
"action_attack_altclick": "Aanvallen (wanneer linkerklik is ingesteld om menu te openen)",
|
||||
"action_build": "Open bouwmenu",
|
||||
"action_center": "Camera centreren op de speler",
|
||||
"action_zoom": "In-/uitzoomen",
|
||||
"action_move_camera": "Camera bewegen",
|
||||
"action_ratio_change": "Aanvalsverhouding verlagen/verhogen",
|
||||
"action_reset_gfx": "Grafische instellingen herstellen",
|
||||
|
||||
"ui_section": "Spel-UI",
|
||||
"ui_leaderboard": "Leaderboard",
|
||||
"ui_leaderboard_desc": "Toont de top spelers van het spel en hun namen, % bezette land en goud.",
|
||||
"ui_control": "Controlepaneel",
|
||||
"ui_control_desc": "Het controlepaneel bevat de volgende elementen:",
|
||||
"ui_pop": "Pop - Jouw totale bevolking, jouw maximale bevolking en de snelheid waarmee je ze verwerft.",
|
||||
"ui_gold": "Goud - Het goud dat je hebt en de snelheid waarmee je het verwerft.",
|
||||
"ui_troops_workers": "Troepen en Werkers - Het aantal toegewezen troepen en werkers. Troepen worden gebruikt om aan te vallen of verdedigen. Werkers worden gebruikt om goud te genereren. Je kunt het aantal troepen en werkers aanpassen met de schuifbalk.",
|
||||
"ui_attack_ratio": "Aanvalsverhouding - Het aantal troepen dat wordt gebruikt bij een aanval. Je kunt de aanvalsverhouding aanpassen met de schuifbalk.",
|
||||
|
||||
"ui_options": "Opties",
|
||||
"ui_options_desc": "De volgende elementen zijn hierin te vinden:",
|
||||
"option_pause": "Spel pauzeren/hervatten - Alleen beschikbaar in enkelspelermodus.",
|
||||
"option_timer": "Timer - Tijd verstreken sinds het begin van het spel.",
|
||||
"option_exit": "Afsluitknop.",
|
||||
"option_settings": "Instellingen - Open het instellingenmenu. Daarin kun je Alternatieve Weergave, Donkere Modus, Emoji's en actie bij linkerklikken aan- of uitzetten.",
|
||||
|
||||
"radial_title": "Radiaal menu",
|
||||
"radial_desc": "Met rechtermuisklik (of tikken op mobiel) opent je het radiale menu. Van daaruit kun je:",
|
||||
"radial_build": "Het bouwmenu openen.",
|
||||
"radial_info": "Het Infomenu openen.",
|
||||
"radial_boat": "Een boot sturen om de geselecteerde locatie aan te vallen (alleen beschikbaar als je toegang hebt tot water).",
|
||||
"radial_close": "Het menu sluiten.",
|
||||
|
||||
"info_title": "Infomenu",
|
||||
"info_enemy_desc": "Bevat informatie zoals de naam van de geselecteerde speler, goud, troepen en of de speler een verrader is. Een verrader is een speler die een bondgenoot heeft aangevallen. De iconen hieronder staan voor de volgende interacties:",
|
||||
"info_target": "Plaats een doelmarkering op de speler, zichtbaar voor alle bondgenoten, wordt gebruikt om aanvallen te coördineren.",
|
||||
"info_alliance": "Stuur een bondgenootschapsverzoek naar de speler. Bondgenoten kunnen goud en troepen delen, maar kunnen elkaar niet aanvallen.",
|
||||
"info_emoji": "Stuur een emoji naar de speler.",
|
||||
|
||||
"info_ally_panel": "Bondgenoot-informatiepaneel",
|
||||
"info_ally_desc": "Wanneer je een bondgenootschap sluit met een speler, worden de volgende nieuwe iconen beschikbaar:",
|
||||
"ally_betray": "Verraad je bondgenoot, beëindig het bondgenootschap. Je krijgt dan een permanent icoon naast je naam. Bots zijn minder geneigd om met je een bondgenootschap aan te gaan en spelers zullen er tweemaal over nadenken.",
|
||||
"ally_donate": "Geef een deel van je troepen af aan een bondgenoot. Gebruikt wanneer ze weinig troepen hebben en worden aangevallen, of wanneer ze extra kracht nodig hebben om een vijand te verslaan.",
|
||||
|
||||
"build_menu_title": "Bouwmenu",
|
||||
"build_name": "Naam",
|
||||
"build_icon": "Icoon",
|
||||
"build_desc": "Beschrijving",
|
||||
|
||||
"build_city": "Stad",
|
||||
"build_city_desc": "Verhoogt je maximale bevolking. Handig wanneer je je gebied niet kunt uitbreiden of bijna je bevolkingslimiet heb bereikt.",
|
||||
"build_defense": "Verdedigingspost",
|
||||
"build_defense_desc": "Versterkt verdediging rond nabijgelegen grenzen. Aanvallen van vijanden zijn trager en hebben meer slachtoffers.",
|
||||
"build_port": "Haven",
|
||||
"build_port_desc": "Stuurt automatisch handelsschepen tussen havens van je land en andere landen (tenzij je \"stop handel\" hebt geklikt op hen of zij \"stop handel\" op jou hebben geklikt), wat goud oplevert voor beide partijen. Maakt het bouwen van Oorlogsschepen mogelijk. Kan alleen in de nabijheid van water worden gebouwd.",
|
||||
"build_warship": "Oorlogsschip",
|
||||
"build_warship_desc": "Patrouilleert in een gebied, vangt handelsschepen en vernietigt vijandelijke Oorlogsschepen en Boten. Spawnt vanuit de dichtstbijzijnde Haven en patrouilleert in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
"build_silo": "Raketsilo",
|
||||
"build_silo_desc": "Maakt het lanceren van raketten mogelijk.",
|
||||
"build_sam": "SAM-lanceerder",
|
||||
"build_sam_desc": "Heeft een 75% kans om vijandelijke raketten binnen een straal van 100 pixels te onderscheppen. De SAM heeft een afkoeltijd van 7,5 seconden en kan geen MIRV's onderscheppen.",
|
||||
"build_atom": "Atoombom",
|
||||
"build_atom_desc": "Kleine explosieve bom die gebied, gebouwen, schepen en boten vernietigt. Spawnt vanuit de dichtstbijzijnde Raketsilo en landt in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
"build_hydrogen": "Waterstofbom",
|
||||
"build_hydrogen_desc": "Grote explosieve bom. Spawnt vanuit de dichtstbijzijnde Raketsilo en landt in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
"build_mirv": "MIRV",
|
||||
"build_mirv_desc": "De krachtigste bom in het spel. Splitst zich op in kleinere bommen die een enorm gebied dekken. Schaadt alleen de speler waar je voor het eerst op hebt geklikt om het te bouwen. Spawnt vanuit de dichtstbijzijnde Raketsilo en landt in het gebied waar je voor het eerst hebt geklikt om het te bouwen.",
|
||||
|
||||
"player_icons": "Spelericonen",
|
||||
"icon_desc": "Voorbeelden van enkele ingame-iconen die je zult tegenkomen en wat ze betekenen:",
|
||||
"icon_crown": "Kroon - Dit is de nummer 1 speler op de leaderboard",
|
||||
"icon_traitor": "Gekruiste zwaarden - Verrader. Deze speler heeft een bondgenoot aangevallen.",
|
||||
"icon_ally": "Handdruk - Bondgenoot. Deze speler is je bondgenoot."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Enkelspeler",
|
||||
"map": "Kaart",
|
||||
"difficulty": "Moeilijkheidsgraad",
|
||||
"allow_alliances": "Bondgenootschappen toestaan",
|
||||
"options_title": "Opties",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Uitgeschakeld",
|
||||
"disable_nations": "Naties uitschakelen",
|
||||
"instant_build": "Bouw wachttijd afschaffen",
|
||||
"infinite_gold": "Oneindig goud",
|
||||
"infinite_troops": "Oneindig troepen",
|
||||
"disable_nukes": "Kernwapens uitschakelen",
|
||||
"start": "Start Spel"
|
||||
},
|
||||
"map": {
|
||||
"world": "Wereld",
|
||||
"europe": "Europa",
|
||||
"mena": "MENA",
|
||||
"northamerica": "Noord-Amerika",
|
||||
"oceania": "Oceanië",
|
||||
"blacksea": "Zwarte Zee",
|
||||
"africa": "Afrika",
|
||||
"asia": "Azië",
|
||||
"mars": "Mars",
|
||||
"southamerica": "Zuid-Amerika",
|
||||
"britannia": "Groot-Brittanië",
|
||||
"gatewaytotheatlantic": "Poort van de Atlantische Oceaan",
|
||||
"australia": "Australië",
|
||||
"random": "Willekeurig"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Treed een Privé Lobby toe",
|
||||
"enter_id": "Voer Lobby-ID in",
|
||||
"player": "Speler",
|
||||
"players": "Spelers",
|
||||
"join_lobby": "Treed een Lobby toe",
|
||||
"checking": "Lobby controleren...",
|
||||
"not_found": "Lobby niet gevonden. Controleer de ID en probeer het opnieuw.",
|
||||
"error": "Er is een fout opgetreden. Probeer het opnieuw.",
|
||||
"joined_waiting": "Succesvol toegetreden! Wachten tot het spel begint..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Treed de volgende spel binnen",
|
||||
"waiting": "spelers wachten"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Voer je gebruikersnaam in",
|
||||
"not_string": "Gebruikersnaam moet een tekenreeks zijn.",
|
||||
"too_short": "Gebruikersnaam moet minstens {min} tekens lang zijn.",
|
||||
"too_long": "Gebruikersnaam mag niet langer zijn dan {max} tekens.",
|
||||
"invalid_chars": "Gebruikersnaam mag alleen letters, cijfers, spaties, underscores en [vierkante haken] bevatten."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Privé Lobby",
|
||||
"map": "Kaart",
|
||||
"difficulty": "Moeilijkheidsgraad",
|
||||
"options_title": "Opties",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Uitgeschakeld",
|
||||
"disable_nations": "Naties uitschakelen",
|
||||
"instant_build": "Direct bouwen",
|
||||
"infinite_gold": "Oneindig goud",
|
||||
"infinite_troops": "Oneindig troepen",
|
||||
"disable_nukes": "Kernwapens uitschakelen",
|
||||
"player": "Speler",
|
||||
"players": "Spelers",
|
||||
"waiting": "Wachten op spelers...",
|
||||
"start": "Start Spel"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Ontspannen",
|
||||
"Balanced": "Gebalanceerd",
|
||||
"Intense": "Intens",
|
||||
"Impossible": "Onmogelijk"
|
||||
}
|
||||
}
|
||||
+156
-125
@@ -11,6 +11,7 @@ import randomMap from "../../resources/images/RandomMap.png";
|
||||
import { generateID } from "../core/Util";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
@customElement("host-lobby-modal")
|
||||
export class HostLobbyModal extends LitElement {
|
||||
@@ -37,7 +38,7 @@ export class HostLobbyModal extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<o-modal title="Private lobby">
|
||||
<o-modal title=${translateText("host_modal.title")}>
|
||||
<div class="lobby-id-box">
|
||||
<button
|
||||
class="lobby-id-button"
|
||||
@@ -45,30 +46,32 @@ export class HostLobbyModal extends LitElement {
|
||||
?disabled=${this.copySuccess}
|
||||
>
|
||||
<span class="lobby-id">${this.lobbyId}</span>
|
||||
${this.copySuccess
|
||||
? html`<span class="copy-success-icon">✓</span>`
|
||||
: html`
|
||||
<svg
|
||||
class="clipboard-icon"
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 512 512"
|
||||
height="18px"
|
||||
width="18px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M296 48H176.5C154.4 48 136 65.4 136 87.5V96h-7.5C106.4 96 88 113.4 88 135.5v288c0 22.1 18.4 40.5 40.5 40.5h208c22.1 0 39.5-18.4 39.5-40.5V416h8.5c22.1 0 39.5-18.4 39.5-40.5V176L296 48zm0 44.6l83.4 83.4H296V92.6zm48 330.9c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5h7.5v255.5c0 22.1 10.4 32.5 32.5 32.5H344v7.5zm48-48c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5H264v128h128v167.5z"
|
||||
></path>
|
||||
</svg>
|
||||
`}
|
||||
${
|
||||
this.copySuccess
|
||||
? html`<span class="copy-success-icon">✓</span>`
|
||||
: html`
|
||||
<svg
|
||||
class="clipboard-icon"
|
||||
stroke="currentColor"
|
||||
fill="currentColor"
|
||||
stroke-width="0"
|
||||
viewBox="0 0 512 512"
|
||||
height="18px"
|
||||
width="18px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M296 48H176.5C154.4 48 136 65.4 136 87.5V96h-7.5C106.4 96 88 113.4 88 135.5v288c0 22.1 18.4 40.5 40.5 40.5h208c22.1 0 39.5-18.4 39.5-40.5V416h8.5c22.1 0 39.5-18.4 39.5-40.5V176L296 48zm0 44.6l83.4 83.4H296V92.6zm48 330.9c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5h7.5v255.5c0 22.1 10.4 32.5 32.5 32.5H344v7.5zm48-48c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5H264v128h128v167.5z"
|
||||
></path>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div class="options-layout">
|
||||
<!-- Map Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Map</div>
|
||||
<div class="option-title">${translateText("host_modal.map")}</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(GameMapType)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -79,14 +82,17 @@ export class HostLobbyModal extends LitElement {
|
||||
.mapKey=${key}
|
||||
.selected=${!this.useRandomMap &&
|
||||
this.selectedMap === value}
|
||||
.translation=${translateText(
|
||||
`map.${key.toLowerCase()}`,
|
||||
)}
|
||||
></map-display>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
<div
|
||||
class="option-card random-map ${this.useRandomMap
|
||||
? "selected"
|
||||
: ""}"
|
||||
class="option-card random-map ${
|
||||
this.useRandomMap ? "selected" : ""
|
||||
}"
|
||||
@click=${this.handleRandomMapToggle}
|
||||
>
|
||||
<div class="option-image">
|
||||
@@ -96,14 +102,14 @@ export class HostLobbyModal extends LitElement {
|
||||
style="width:100%; aspect-ratio: 4/2; object-fit:cover; border-radius:8px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="option-card-title">Random</div>
|
||||
<div class="option-card-title">${translateText("map.random")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Difficulty Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Difficulty</div>
|
||||
<div class="option-title">${translateText("host_modal.difficulty")}</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(Difficulty)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -119,7 +125,9 @@ export class HostLobbyModal extends LitElement {
|
||||
.difficultyKey=${key}
|
||||
></difficulty-display>
|
||||
<p class="option-card-title">
|
||||
${DifficultyDescription[key]}
|
||||
${translateText(
|
||||
`difficulty.${DifficultyDescription[key]}`,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
@@ -127,103 +135,125 @@ export class HostLobbyModal extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Game Options -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Options</div>
|
||||
<div class="option-cards">
|
||||
<label for="private-lobby-bots-count" class="option-card">
|
||||
<input
|
||||
type="range"
|
||||
id="private-lobby-bots-count"
|
||||
min="0"
|
||||
max="400"
|
||||
step="1"
|
||||
@input=${this.handleBotsChange}
|
||||
@change=${this.handleBotsChange}
|
||||
.value="${String(this.bots)}"
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
Bots: ${this.bots == 0 ? "Disabled" : this.bots}
|
||||
</div>
|
||||
</label>
|
||||
<!-- Game Options -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${translateText("host_modal.options_title")}
|
||||
</div>
|
||||
<div class="option-cards">
|
||||
<label for="bots-count" class="option-card">
|
||||
<input
|
||||
type="range"
|
||||
id="bots-count"
|
||||
min="0"
|
||||
max="400"
|
||||
step="1"
|
||||
@input=${this.handleBotsChange}
|
||||
@change=${this.handleBotsChange}
|
||||
.value="${String(this.bots)}"
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
<span>${translateText("host_modal.bots")}</span>${
|
||||
this.bots == 0
|
||||
? translateText("host_modal.bots_disabled")
|
||||
: this.bots
|
||||
}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-disable-npcd"
|
||||
class="option-card ${this.disableNPCs ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-disable-npcd"
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
.checked=${this.disableNPCs}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nations</div>
|
||||
</label>
|
||||
<label
|
||||
for="disable-npcs"
|
||||
class="option-card ${this.disableNPCs ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-npcs"
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
.checked=${this.disableNPCs}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.disable_nations")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-instant-build"
|
||||
class="option-card ${this.instantBuild ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-instant-build"
|
||||
@change=${this.handleInstantBuildChange}
|
||||
.checked=${this.instantBuild}
|
||||
/>
|
||||
<div class="option-card-title">Instant build</div>
|
||||
</label>
|
||||
<label
|
||||
for="instant-build"
|
||||
class="option-card ${this.instantBuild ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="instant-build"
|
||||
@change=${this.handleInstantBuildChange}
|
||||
.checked=${this.instantBuild}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.instant_build")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-infinite-gold"
|
||||
class="option-card ${this.infiniteGold ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-infinite-gold"
|
||||
@change=${this.handleInfiniteGoldChange}
|
||||
.checked=${this.infiniteGold}
|
||||
/>
|
||||
<div class="option-card-title">Infinite gold</div>
|
||||
</label>
|
||||
<label
|
||||
for="infinite-gold"
|
||||
class="option-card ${this.infiniteGold ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="infinite-gold"
|
||||
@change=${this.handleInfiniteGoldChange}
|
||||
.checked=${this.infiniteGold}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.infinite_gold")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="private-lobby-infinite-troops"
|
||||
class="option-card ${this.infiniteTroops ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="private-lobby-infinite-troops"
|
||||
@change=${this.handleInfiniteTroopsChange}
|
||||
.checked=${this.infiniteTroops}
|
||||
/>
|
||||
<div class="option-card-title">Infinite troops</div>
|
||||
</label>
|
||||
<label
|
||||
for="private-lobby-disable-nukes"
|
||||
class="option-card ${this.disableNukes ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-nukes"
|
||||
@change=${this.handleDisableNukesChange}
|
||||
.checked=${this.disableNukes}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nukes</div>
|
||||
</label>
|
||||
<label
|
||||
for="infinite-troops"
|
||||
class="option-card ${this.infiniteTroops ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="infinite-troops"
|
||||
@change=${this.handleInfiniteTroopsChange}
|
||||
.checked=${this.infiniteTroops}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.infinite_troops")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
for="disable-nukes"
|
||||
class="option-card ${this.disableNukes ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-nukes"
|
||||
@change=${this.handleDisableNukesChange}
|
||||
.checked=${this.disableNukes}
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.disable_nukes")}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lobby Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${this.players.length === 1 ? "Player" : "Players"}
|
||||
</div>
|
||||
<!-- Lobby Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${
|
||||
this.players.length === 1
|
||||
? translateText("host_modal.player")
|
||||
: translateText("host_modal.players")
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="players-list">
|
||||
${this.players.map(
|
||||
@@ -231,17 +261,18 @@ export class HostLobbyModal extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<o-button
|
||||
.title=${this.players.length === 1
|
||||
? "Waiting for players..."
|
||||
: "Start Game"}
|
||||
?disable=${this.players.length < 2}
|
||||
|
||||
<button
|
||||
@click=${this.startGame}
|
||||
block
|
||||
?disabled=${this.players.length < 2}
|
||||
class="start-game-button"
|
||||
>
|
||||
</o-button>
|
||||
${
|
||||
this.players.length === 1
|
||||
? translateText("host_modal.waiting")
|
||||
: translateText("host_modal.start")
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</o-modal>
|
||||
`;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { consolex } from "../core/Consolex";
|
||||
import { GameInfo, GameRecord } from "../core/Schemas";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { translateText } from "../client/Utils";
|
||||
import "./components/baseComponents/Modal";
|
||||
import "./components/baseComponents/Button";
|
||||
|
||||
@@ -22,12 +23,12 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<o-modal title="Join Private Lobby">
|
||||
<o-modal title=${translateText("private_lobby.title")}>
|
||||
<div class="lobby-id-box">
|
||||
<input
|
||||
type="text"
|
||||
id="lobbyIdInput"
|
||||
placeholder="Enter Lobby ID"
|
||||
placeholder=${translateText("private_lobby.enter_id")}
|
||||
@keyup=${this.handleChange}
|
||||
/>
|
||||
<button
|
||||
@@ -58,7 +59,9 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
? html` <div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${this.players.length === 1 ? "Player" : "Players"}
|
||||
${this.players.length === 1
|
||||
? translateText("private_lobby.player")
|
||||
: translateText("private_lobby.players")}
|
||||
</div>
|
||||
|
||||
<div class="players-list">
|
||||
@@ -72,7 +75,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
<div class="flex justify-center">
|
||||
${!this.hasJoined
|
||||
? html` <o-button
|
||||
title="Join Lobby"
|
||||
title=${translateText("private_lobby.join_lobby")}
|
||||
block
|
||||
@click=${this.joinLobby}
|
||||
></o-button>`
|
||||
@@ -149,7 +152,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
private async joinLobby(): Promise<void> {
|
||||
const lobbyId = this.lobbyIdInput.value;
|
||||
consolex.log(`Joining lobby with ID: ${lobbyId}`);
|
||||
this.message = "Checking lobby...";
|
||||
this.message = `${translateText("private_lobby.checking")}`;
|
||||
|
||||
try {
|
||||
// First, check if the game exists in active lobbies
|
||||
@@ -160,10 +163,10 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
const archivedGame = await this.checkArchivedGame(lobbyId);
|
||||
if (archivedGame) return;
|
||||
|
||||
this.message = "Lobby not found. Please check the ID and try again.";
|
||||
this.message = `${translateText("private_lobby.not_found")}`;
|
||||
} catch (error) {
|
||||
consolex.error("Error checking lobby existence:", error);
|
||||
this.message = "An error occurred. Please try again.";
|
||||
this.message = `${translateText("private_lobby.error")}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
const gameInfo = await response.json();
|
||||
|
||||
if (gameInfo.exists) {
|
||||
this.message = "Joined successfully! Waiting for game to start...";
|
||||
this.message = translateText("private_lobby.joined_waiting");
|
||||
this.hasJoined = true;
|
||||
|
||||
this.dispatchEvent(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Difficulty, GameMapType, GameType } from "../core/game/Game";
|
||||
import { consolex } from "../core/Consolex";
|
||||
import { getMapsImage } from "./utilities/Maps";
|
||||
import { GameID, GameInfo } from "../core/Schemas";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
@customElement("public-lobby")
|
||||
export class PublicLobby extends LitElement {
|
||||
@@ -102,7 +103,9 @@ export class PublicLobby extends LitElement {
|
||||
? "opacity-70 cursor-not-allowed"
|
||||
: ""}"
|
||||
>
|
||||
<div class="text-lg md:text-2xl font-semibold mb-2">Join next Game</div>
|
||||
<div class="text-lg md:text-2xl font-semibold mb-2">
|
||||
${translateText("public_lobby.join")}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<img
|
||||
src="${getMapsImage(lobby.gameConfig.gameMap)}"
|
||||
@@ -115,13 +118,16 @@ export class PublicLobby extends LitElement {
|
||||
>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.gameConfig.gameMap}
|
||||
<!-- ${lobby.gameConfig.gameMap} -->
|
||||
${translateText(
|
||||
`map.${lobby.gameConfig.gameMap.toLowerCase().replace(/\s+/g, "")}`,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-start">
|
||||
<div class="text-md font-medium text-blue-100">
|
||||
${lobby.numClients} / ${lobby.gameConfig.maxPlayers} players
|
||||
waiting
|
||||
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
|
||||
${translateText("public_lobby.waiting")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
|
||||
@@ -11,6 +11,7 @@ import "./components/Maps";
|
||||
import randomMap from "../../resources/images/RandomMap.png";
|
||||
import { GameInfo } from "../core/Schemas";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
@customElement("single-player-modal")
|
||||
export class SinglePlayerModal extends LitElement {
|
||||
@@ -30,11 +31,11 @@ export class SinglePlayerModal extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<o-modal title="Single Player">
|
||||
<o-modal title=${translateText("single_modal.title")}>
|
||||
<div class="options-layout">
|
||||
<!-- Map Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Map</div>
|
||||
<div class="option-title">${translateText("single_modal.map")}</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(GameMapType)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -49,6 +50,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
.mapKey=${key}
|
||||
.selected=${!this.useRandomMap &&
|
||||
this.selectedMap === value}
|
||||
.translation=${translateText(
|
||||
`map.${key.toLowerCase()}`,
|
||||
)}
|
||||
></map-display>
|
||||
</div>
|
||||
`,
|
||||
@@ -66,14 +70,18 @@ export class SinglePlayerModal extends LitElement {
|
||||
style="width:100%; aspect-ratio: 4/2; object-fit:cover; border-radius:8px;"
|
||||
/>
|
||||
</div>
|
||||
<div class="option-card-title">Random</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("map.random")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Difficulty Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Difficulty</div>
|
||||
<div class="option-title">
|
||||
${translateText("single_modal.difficulty")}
|
||||
</div>
|
||||
<div class="option-cards">
|
||||
${Object.entries(Difficulty)
|
||||
.filter(([key]) => isNaN(Number(key)))
|
||||
@@ -89,7 +97,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
.difficultyKey=${key}
|
||||
></difficulty-display>
|
||||
<p class="option-card-title">
|
||||
${DifficultyDescription[key]}
|
||||
${translateText(
|
||||
`difficulty.${DifficultyDescription[key]}`,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
@@ -99,7 +109,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
|
||||
<!-- Game Options -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">Options</div>
|
||||
<div class="option-title">
|
||||
${translateText("single_modal.options_title")}
|
||||
</div>
|
||||
<div class="option-cards">
|
||||
<label for="bots-count" class="option-card">
|
||||
<input
|
||||
@@ -113,7 +125,10 @@ export class SinglePlayerModal extends LitElement {
|
||||
.value="${this.bots}"
|
||||
/>
|
||||
<div class="option-card-title">
|
||||
Bots: ${this.bots == 0 ? "Disabled" : this.bots}
|
||||
<span>${translateText("single_modal.bots")}</span>${this
|
||||
.bots == 0
|
||||
? translateText("single_modal.bots_disabled")
|
||||
: this.bots}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -128,7 +143,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleDisableNPCsChange}
|
||||
.checked=${this.disableNPCs}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nations</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.disable_nations")}
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
for="instant-build"
|
||||
@@ -141,7 +158,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleInstantBuildChange}
|
||||
.checked=${this.instantBuild}
|
||||
/>
|
||||
<div class="option-card-title">Instant build</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.instant_build")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -155,7 +174,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleInfiniteGoldChange}
|
||||
.checked=${this.infiniteGold}
|
||||
/>
|
||||
<div class="option-card-title">Infinite gold</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.infinite_gold")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -169,7 +190,9 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleInfiniteTroopsChange}
|
||||
.checked=${this.infiniteTroops}
|
||||
/>
|
||||
<div class="option-card-title">Infinite troops</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.infinite_troops")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label
|
||||
@@ -183,14 +206,16 @@ export class SinglePlayerModal extends LitElement {
|
||||
@change=${this.handleDisableNukesChange}
|
||||
.checked=${this.disableNukes}
|
||||
/>
|
||||
<div class="option-card-title">Disable Nukes</div>
|
||||
<div class="option-card-title">
|
||||
${translateText("single_modal.disable_nukes")}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<o-button
|
||||
title="Start Game"
|
||||
title=${translateText("single_modal.start")}
|
||||
@click=${this.startGame}
|
||||
blockDesktop
|
||||
></o-button>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
validateUsername,
|
||||
} from "../core/validations/username";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import { translateText } from "../client/Utils";
|
||||
|
||||
const usernameKey: string = "username";
|
||||
|
||||
@@ -40,7 +41,7 @@ export class UsernameInput extends LitElement {
|
||||
.value=${this.username}
|
||||
@input=${this.handleChange}
|
||||
@change=${this.handleChange}
|
||||
placeholder="Enter your username"
|
||||
placeholder="${translateText("username.enter_username")}"
|
||||
maxlength="${MAX_USERNAME_LENGTH}"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-xl shadow-sm text-2xl text-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:border-gray-300/60 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
|
||||
@@ -70,3 +70,30 @@ export function generateCryptoRandomUUID(): string {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function translateText(
|
||||
key: string,
|
||||
params: Record<string, string | number> = {},
|
||||
): string {
|
||||
const keys = key.split(".");
|
||||
let text: any = (window as any).translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
|
||||
if (!text && (window as any).defaultTranslations) {
|
||||
text = (window as any).defaultTranslations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) return key;
|
||||
}
|
||||
}
|
||||
|
||||
for (const [param, value] of Object.entries(params)) {
|
||||
text = text.replace(`{${param}}`, String(value));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const MapDescription: Record<keyof typeof GameMapType, string> = {
|
||||
export class MapDisplay extends LitElement {
|
||||
@property({ type: String }) mapKey = "";
|
||||
@property({ type: Boolean }) selected = false;
|
||||
@property({ type: String }) translation: string = "";
|
||||
|
||||
static styles = css`
|
||||
.option-card {
|
||||
@@ -90,7 +91,9 @@ export class MapDisplay extends LitElement {
|
||||
<p>${this.mapKey}</p>
|
||||
</div>`}
|
||||
<div class="option-card-title">
|
||||
${MapDescription[this.mapKey as keyof typeof GameMapType]}
|
||||
<!-- ${MapDescription[this.mapKey as keyof typeof GameMapType]}-->
|
||||
${this.translation ||
|
||||
MapDescription[this.mapKey as keyof typeof GameMapType]}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
+95
-1
@@ -304,7 +304,10 @@
|
||||
class="text-center appearance-none w-full bg-blue-100 hover:bg-blue-200 text-blue-900 p-3 sm:p-4 lg:p-5 font-medium text-sm sm:text-base lg:text-lg rounded-md border-none cursor-pointer transition-colors duration-300"
|
||||
>
|
||||
<option value="en">English</option>
|
||||
<option value="bg">Български</option>
|
||||
<option value="ja">日本語</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -394,7 +397,98 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="lang.js"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
const locale = new Intl.Locale(navigator.language);
|
||||
const defaultLang = locale.language;
|
||||
const userLang = localStorage.getItem("lang") || defaultLang;
|
||||
async function loadLanguage(lang) {
|
||||
try {
|
||||
const response = await fetch(`/lang/${lang}.json`);
|
||||
if (!response.ok)
|
||||
throw new Error(`Language file not found: ${lang}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("🚨 Translation load error:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
function applyTranslation(translations) {
|
||||
const components = [
|
||||
"single-player-modal",
|
||||
"host-lobby-modal",
|
||||
"join-private-lobby-modal",
|
||||
"emoji-table",
|
||||
"leader-board",
|
||||
"build-menu",
|
||||
"win-modal",
|
||||
"game-starting-modal",
|
||||
"top-bar",
|
||||
"player-panel",
|
||||
"help-modal",
|
||||
"username-input",
|
||||
"public-lobby",
|
||||
];
|
||||
|
||||
document.title = translations.main?.title || document.title;
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
if (!text && window.defaultTranslations) {
|
||||
let fallback = window.defaultTranslations;
|
||||
for (const k of keys) {
|
||||
fallback = fallback?.[k];
|
||||
if (!fallback) break;
|
||||
}
|
||||
text = fallback;
|
||||
}
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
console.warn(`Missing translation key: ${key}`);
|
||||
}
|
||||
});
|
||||
components.forEach((tagName) => {
|
||||
const el = document.querySelector(tagName);
|
||||
if (el && typeof el.requestUpdate === "function") {
|
||||
el.requestUpdate();
|
||||
} else {
|
||||
console.warn(
|
||||
`requestUpdate() not available on <${tagName}> or element not found.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
async function changeLanguage(lang) {
|
||||
// console.log(`Changing language to: ${lang}`);
|
||||
localStorage.setItem("lang", lang);
|
||||
const translations = await loadLanguage(lang);
|
||||
window.translations = translations;
|
||||
applyTranslation(translations);
|
||||
}
|
||||
const defaultTranslations = await loadLanguage("en");
|
||||
window.defaultTranslations = defaultTranslations;
|
||||
const translations = await loadLanguage(userLang);
|
||||
window.translations = translations;
|
||||
applyTranslation(translations);
|
||||
|
||||
const langSelector = document.getElementById("lang-selector");
|
||||
if (langSelector) {
|
||||
langSelector.value = userLang;
|
||||
}
|
||||
document
|
||||
.getElementById("lang-selector")
|
||||
.addEventListener("change", function (event) {
|
||||
changeLanguage(event.target.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
const defaultLang = navigator.language.startsWith("ja") ? "ja" : "en";
|
||||
const userLang = localStorage.getItem("lang") || defaultLang;
|
||||
|
||||
async function loadLanguage(lang) {
|
||||
try {
|
||||
const response = await fetch(`/lang/${lang}.json`);
|
||||
if (!response.ok) throw new Error(`Language file not found: ${lang}`);
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("🚨 Translation load error:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function applyTranslation(translations) {
|
||||
document.title = translations.main?.title || document.title;
|
||||
|
||||
document.querySelectorAll("[data-i18n]").forEach((element) => {
|
||||
const key = element.getAttribute("data-i18n");
|
||||
const keys = key.split(".");
|
||||
let text = translations;
|
||||
|
||||
for (const k of keys) {
|
||||
text = text?.[k];
|
||||
if (!text) break;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
element.innerHTML = text;
|
||||
} else {
|
||||
console.warn(`Missing translation key: ${key}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function changeLanguage(lang) {
|
||||
// console.log(`Changing language to: ${lang}`);
|
||||
localStorage.setItem("lang", lang);
|
||||
const translations = await loadLanguage(lang);
|
||||
applyTranslation(translations);
|
||||
}
|
||||
|
||||
const translations = await loadLanguage(userLang);
|
||||
applyTranslation(translations);
|
||||
|
||||
const langSelector = document.getElementById("lang-selector");
|
||||
if (langSelector) {
|
||||
langSelector.value = userLang;
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("lang-selector")
|
||||
.addEventListener("change", function (event) {
|
||||
changeLanguage(event.target.value);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
englishRecommendedTransformers,
|
||||
} from "obscenity";
|
||||
import { simpleHash } from "../Util";
|
||||
import { translateText } from "../../client/Utils";
|
||||
|
||||
const matcher = new RegExpMatcher({
|
||||
...englishDataset.build(),
|
||||
@@ -41,28 +42,33 @@ export function validateUsername(username: string): {
|
||||
error?: string;
|
||||
} {
|
||||
if (typeof username !== "string") {
|
||||
return { isValid: false, error: "Username must be a string." };
|
||||
return { isValid: false, error: translateText("username.not_string") };
|
||||
}
|
||||
|
||||
if (username.length < MIN_USERNAME_LENGTH) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Username must be at least ${MIN_USERNAME_LENGTH} characters long.`,
|
||||
error: translateText("username.too_short", {
|
||||
min: MIN_USERNAME_LENGTH,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (username.length > MAX_USERNAME_LENGTH) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: `Username must not exceed ${MAX_USERNAME_LENGTH} characters.`,
|
||||
error: translateText("username.too_long", {
|
||||
max: MAX_USERNAME_LENGTH,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (!validPattern.test(username)) {
|
||||
return {
|
||||
isValid: false,
|
||||
error:
|
||||
"Username can only contain letters, numbers, spaces, underscores, and [square brackets].",
|
||||
error: translateText("username.invalid_chars", {
|
||||
max: MAX_USERNAME_LENGTH,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user