Merge branch 'main' into embeddedurlfix

This commit is contained in:
Ryan
2026-02-01 12:17:01 +00:00
committed by GitHub
73 changed files with 3648 additions and 885 deletions
+14 -10
View File
@@ -75,11 +75,11 @@
defer
></script>
<!-- Publift/Fuse ads -->
<script
async
src="https://cdn.fuseplatform.net/publift/tags/2/4121/fuse.js"
></script>
<script data-cfasync="false">
window.ramp = window.ramp || {};
window.ramp.que = window.ramp.que || [];
window.ramp.passiveMode = true;
</script>
<script>
window.googletag = window.googletag || { cmd: [] };
@@ -189,11 +189,6 @@
inline
class="hidden w-full h-full page-content"
></territory-patterns-modal>
<matchmaking-modal
id="page-matchmaking"
inline
class="hidden w-full h-full page-content"
></matchmaking-modal>
<user-setting
id="page-settings"
inline
@@ -271,6 +266,8 @@
<player-panel></player-panel>
<spawn-timer></spawn-timer>
<immunity-timer></immunity-timer>
<in-game-header-ad></in-game-header-ad>
<spawn-video-ad></spawn-video-ad>
<game-info-modal></game-info-modal>
<alert-frame></alert-frame>
<chat-modal></chat-modal>
@@ -366,5 +363,12 @@
data-cf-beacon='{"token": "03d93e6fefb349c28ee69b408fa25a13"}'
></script>
<script type="module" src="/src/client/Main.ts"></script>
<footer>
<script
data-cfasync="false"
async
src="//cdn.intergient.com/1025558/75940/ramp.js"
></script>
</footer>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

@@ -0,0 +1,18 @@
{
"name": "TheBox",
"nations": [
{ "coordinates": [10, 10], "flag": "", "name": "King of the Corner" },
{ "coordinates": [1024, 300], "flag": "", "name": "Suspicious Ally" },
{ "coordinates": [1650, 400], "flag": "", "name": "Evan The Dev" },
{ "coordinates": [1024, 1024], "flag": "", "name": "Middle Defender" },
{ "coordinates": [350, 1024], "flag": "", "name": "Punch Merchant" },
{ "coordinates": [1700, 1024], "flag": "", "name": "Nuke Thrower" },
{ "coordinates": [400, 1650], "flag": "", "name": "Fullsender" },
{ "coordinates": [1024, 1750], "flag": "", "name": "Factory Builder" },
{ "coordinates": [1650, 1650], "flag": "", "name": "Front Manager" },
{ "coordinates": [700, 700], "flag": "", "name": "Box Fighter" },
{ "coordinates": [1350, 700], "flag": "", "name": "Cage Liberator" },
{ "coordinates": [700, 1350], "flag": "", "name": "Train Trader" },
{ "coordinates": [1350, 1350], "flag": "", "name": "Non-peaceful Bot" }
]
}
+1
View File
@@ -63,6 +63,7 @@ var maps = []struct {
{Name: "world"},
{Name: "lemnos"},
{Name: "twolakes"},
{Name: "thebox"},
{Name: "didier"},
{Name: "didierfrance"},
{Name: "amazonriver"},
-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path fill="#fff" d="M90.9 17.1c-3.9-3.9-10.2-3.9-14.1 0L37.5 56.4 23.2 42.1c-3.9-3.9-10.2-3.9-14.1 0s-3.9 10.2 0 14.1l21.4 21.4c1.9 1.9 4.4 2.9 7.1 2.9s5.1-1 7.1-2.9l46.4-46.4c3.8-3.9 3.8-10.2-.2-14.1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 278 B

+28 -10
View File
@@ -26,12 +26,15 @@
"title": "OpenFront (АЛФА)",
"join_discord": "Discord",
"login_discord": "Влез с Discord",
"sign_in": "Вход",
"discord_avatar_alt": "Discord профилна снимка",
"user_avatar_alt": "Профилната снимка на {username}",
"checking_login": "Проверяване на входа...",
"logged_in": "Влезли сте!",
"log_out": "Излез от профила си",
"create": "Създай частна игра",
"join": "Присъедини се към частна игра",
"solo": "Самостоятелна игра",
"solo": "Самостоятелно",
"instructions": "Инструкции",
"game_info": "Информация за играта",
"wiki": "Wiki",
@@ -42,7 +45,7 @@
"play": "Играй",
"news": "Новини",
"store": "Магазин",
"options": "Опции",
"settings": "Настройки",
"keys": "Клавиши",
"stats": "Статистики",
"account": "Акаунт",
@@ -179,13 +182,8 @@
"title": "Акаунт",
"connected_as": "Вписан като",
"stats_overview": "Преглед на статистики",
"save_progress_title": "Запази си напредъка",
"save_progress_desc": "Свържи си акаунта, за да запазиш статистиките, ранка и козметиките си в безопасност.",
"link_discord": "Свържи Discord акаунт",
"link_via_email_placeholder": "Свържи чрез имейл",
"link_button": "Свържи",
"log_out": "Изход от профила",
"welcome_back": "Добре дошъл отново!",
"sign_in_desc": "Впиши се, за да запазиш статистиките и напредъка си",
"or": "ИЛИ",
"email_placeholder": "Въведи имейл адреса си",
@@ -237,7 +235,7 @@
"pirate": "Пират",
"conquered": "Завладяно",
"loading_game_info": "Зареждат се статистиките на играта",
"no_winner": "Играта е свършила без победител"
"no_winner": "Играта завърши без победител (или победа на нация)"
},
"map": {
"map": "Карта",
@@ -429,7 +427,7 @@
"factory": "Фабрика"
},
"user_setting": {
"title": "Потребителски настройки",
"title": "Настройки",
"tab_basic": "Базови настройки",
"tab_keybinds": "Бързи клавиши",
"dark_mode_label": "Тъмен режим",
@@ -487,6 +485,11 @@
"build_hydrogen_bomb_desc": "Пускане на водородна бомба под курсора Ви.",
"build_mirv": "Пускане на МИРВ",
"build_mirv_desc": "Пускане на МИРВ под курсора Ви.",
"menu_shortcuts": "Преки пътища за меню",
"build_menu_modifier": "Модификатор на менюто за изграждане",
"build_menu_modifier_desc": "Задръж този клавиш, докато кликаш, за да отвориш менюто за изграждане.",
"emoji_menu_modifier": "Модификатор на менюто с емоджита",
"emoji_menu_modifier_desc": "Задръж този клавиш, докато кликаш, за да отвориш менюто за емоджита.",
"attack_ratio_controls": "Контроли за съотношение на атака",
"attack_ratio_up": "Увеличаване на съотношение на атака",
"attack_ratio_up_desc": "Увеличаване на съотношение на атака с 10%",
@@ -497,6 +500,8 @@
"boat_attack_desc": "Изпраща атака с лодка към плочката под курсора ви.",
"ground_attack": "Земна атака",
"ground_attack_desc": "Изпраща земна атака към плочката под курсора ви.",
"swap_direction": "Размени посоката на ракетата",
"swap_direction_desc": "Превключване на посоката на изстрелване на ракетата (нагоре/надолу).",
"zoom_controls": "Контроли за позиция на камерата",
"zoom_out": "Отдалечаване на камерата",
"zoom_out_desc": "Отдалечаване на камерата от картата",
@@ -711,7 +716,20 @@
"wants_to_renew_alliance": "{name} иска да поднови съюза си с теб",
"ignore": "Игнориране",
"unit_voluntarily_deleted": "Елементът бе изтрит доброволно",
"betrayal_debuff_ends": "Остават {time} секунди до края на предателското отслабване"
"betrayal_debuff_ends": "Остават {time} секунди до края на предателското отслабване",
"attack_cancelled_retreat": "Атаката бе отменена, {troops} войници бяха убити по време на отстъплението",
"received_gold_from_captured_ship": "Получи {gold} злато от лодка на {name}, превзета от теб",
"received_gold_from_trade": "Получи {gold} злато от търговия с {name}",
"missile_intercepted": "Противоракетната установка прихвана {unit}",
"mirv_warheads_intercepted": "{count, plural, one {{count} МИРВ бе прихванат} other {{count} МИРВ-а бяха прихванати}}",
"sent_troops_to_player": "Изпрати {troops} войници на {name}",
"received_troops_from_player": "Получи {troops} войници от {name}",
"sent_gold_to_player": "Изпрати {gold} злато на {name}",
"received_gold_from_player": "Получи {gold} злато от {name}",
"unit_captured_by_enemy": "Твоят/а {unit} бе превзет от {name}",
"captured_enemy_unit": "Превзе {unit} от {name}",
"unit_destroyed": "Твоят/а {unit} бе унищожен",
"no_boats_available": "Няма свободни кораби, максимум {max}"
},
"unit_info_modal": {
"structure_info": "Информация за постройката",
+6 -1
View File
@@ -46,6 +46,7 @@
"play": "Play",
"news": "News",
"store": "Store",
"store_new_badge": "NEW",
"settings": "Settings",
"keys": "Keys",
"stats": "Stats",
@@ -90,6 +91,8 @@
"canvas_2d_no_gpu": "Canvas 2D (no GPU)"
},
"help_modal": {
"video_tutorial": "Video Tutorial",
"video_tutorial_title": "OpenFront.io Tutorial",
"hotkeys": "Hotkeys",
"table_key": "Key",
"table_action": "Action",
@@ -267,7 +270,8 @@
"naval_trade": "Tradeship",
"conquest_gold": "Conquered player gold",
"stolen_gold": "Stolen with warships",
"num_of_conquests": "Number of conquered players",
"num_of_conquests_humans": "Player kills",
"num_of_conquests_bots": "Bot kills",
"duration": "Duration",
"survival_time": "Survival time",
"war": "War",
@@ -324,6 +328,7 @@
"manicouagan": "Manicouagan",
"lemnos": "Lemnos",
"sierpinski": "Sierpinski",
"thebox": "The Box",
"twolakes": "Two Lakes",
"straitofhormuz": "Strait of Hormuz",
"surrounded": "Surrounded",
+193 -48
View File
@@ -7,6 +7,7 @@
},
"common": {
"close": "Fermer",
"back": "Retour",
"available": "Disponible",
"preset_max": "Max",
"summary_send": "Envoyer",
@@ -17,26 +18,42 @@
"cap_tooltip": "Capacité restante du destinataire",
"target_dead": "Cible éliminée",
"target_dead_note": "Vous ne pouvez pas envoyer de ressources à un joueur éliminé.",
"none": "Aucun"
"none": "Aucun",
"copied": "Copié !",
"click_to_copy": "Cliquer pour copier"
},
"main": {
"title": "OpenFront (ALPHA)",
"join_discord": "Discord",
"login_discord": "Se connecter avec Discord",
"sign_in": "Se connecter",
"discord_avatar_alt": "Avatar du profil Discord",
"user_avatar_alt": "Avatar de {username}",
"checking_login": "Vérification de la connexion...",
"logged_in": "Connecté !",
"log_out": "Se déconnecter",
"create_lobby": "Créer un salon",
"join_lobby": "Rejoindre un salon",
"single_player": "Mode solo",
"create": "Créer un salon",
"join": "Rejoindre un salon",
"solo": "Solo",
"instructions": "Instructions",
"game_info": "Infos sur la partie",
"wiki": "Wiki",
"privacy_policy": "Politique de confidentialité",
"terms_of_service": "Conditions d'utilisation",
"reddit": "Reddit"
"copyright": "©️ OpenFront™ et Contributeurs",
"reddit": "Reddit",
"play": "Jouer",
"news": "Actus",
"store": "Boutique",
"settings": "Paramètres",
"keys": "Touches",
"stats": "Stats",
"account": "Compte",
"help": "Aide",
"menu": "Menu",
"pick_pattern": "Choisis un motif !"
},
"news": {
"see_all_releases": "Voir toutes les versions",
"github_link": "sur Github",
"title": "Notes de version"
},
@@ -67,7 +84,7 @@
"ui_events_desc": "Le panneau des événements affiche les derniers événements, demandes et messages de chat rapide. Quelques exemples sont :",
"ui_events_alliance": "Alliance - Les demandes d'Alliance peuvent être acceptées ou rejetées. Les alliés peuvent partager des ressources et des troupes, mais ne peuvent pas s'attaquer. Cliquer sur Focus déplace la vue vers le joueur qui a envoyé la requête.",
"ui_events_attack": "Attaques - Les attaques entrantes et sortantes sont affichées. Cliquez sur le message pour centrer la vue sur l'attaque, la bombe ou le bateau (navire de transport). Vous pouvez envoyer les troupes en retraite en cliquant sur le bouton rouge X. Cela coûtera la vie à 25% de vos troupes attaquantes. Si vous annulez une attaque de bateau, le bateau revient à son point de départ et y attaquera si la terre a été capturée depuis. Les bombes atomiques ne peuvent pas être retirées une fois lancées.",
"ui_events_quickchat": "Chat rapide - Vous pouvez voir les messages envoyés et reçus ici. Envoyez un message à un joueur en cliquant sur l'icône Chat rapide dans son menu Infos.",
"ui_events_quickchat": "Chat Rapide - Vous pouvez voir les messages envoyés et reçus ici. Envoyez un message à un joueur en cliquant sur l'icône Chat Rapide dans son menu Infos.",
"ui_options": "Options",
"ui_options_desc": "Les éléments suivants peuvent être trouvés à l'intérieur :",
"ui_playeroverlay": "Informations sur le joueur",
@@ -83,6 +100,8 @@
"radial_attack": "Ouvrez le menu d'attaque.",
"radial_info": "Ouvrir le menu d'informations.",
"radial_boat": "Envoyer un bateau (navire de transport) pour attaquer l'endroit sélectionné. Disponible uniquement si vous avez accès à l'eau.",
"radial_donate_troops": "Donner des troupes équivalent à votre ratio d'attaque à l'allié sur lequel vous avez ouvert le menu radial.",
"radial_donate_gold": "Ouvre le menu du curseur de don d'or pour que vous puissiez envoyer rapidement de l'or aux alliés.",
"radial_close": "Fermer le menu.",
"info_title": "Menu d'informations",
"info_enemy_desc": "Contient des informations telles que le nom du joueur sélectionné, son or, ses troupes, s'il a cessé de commercer avec vous, les bombes qu'il vous a envoyées, et si le joueur est un traître. L'arrêt du commerce signifie que vous ne recevrez pas d'or de sa part et qu'il ne vous enverra pas d'or via des navires commerciaux. Manuellement (si le joueur a cliqué sur « Arrêter le commerce », qui dure jusqu'à ce que vous cliquiez sur « Commencer le commerce ») ou automatiquement (si vous avez trahi votre alliance, ce qui dure jusqu'à ce que vous deveniez alliés de nouveau ou après 5 minutes). Le traître affiche Oui pendant 30 secondes lorsque le joueur a trahi et attaqué un joueur qui était dans une alliance avec lui. Les icônes ci-dessous représentent les interactions suivantes :",
@@ -114,7 +133,7 @@
"build_silo": "Silo à missiles",
"build_silo_desc": "Permet de lancer des missiles.",
"build_sam": "Lanceur SAM",
"build_sam_desc": "Vous pouvez intercepter les missiles ennemis à portée de 100 pixels. Avec une probabilité de 100% pour la Bombe Atomique, 80% pour la Bombe Hydrogène et 50% pour les Ogives MIRV individuelles. Le SAM a un temps de recharge de 7,5 secondes.",
"build_sam_desc": "Peut intercepter les missiles ennemis dans un rayon de 100 pixels. Le SAM a un temps de recharge de 7,5 secondes.",
"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",
@@ -129,12 +148,15 @@
"icon_embargo": "Signe dollar barré - Embargo. Ce joueur a cessé de commercer avec vous automatiquement ou manuellement.",
"icon_request": "Enveloppe - Demande d'alliance. Ce joueur vous a envoyé une demande d'alliance.",
"info_enemy_panel": "Panneau d'information de l'ennemi",
"exit_confirmation": "Êtes-vous sûr de vouloir quitter le jeu ?"
"exit_confirmation": "Êtes-vous sûr de vouloir quitter le jeu ?",
"bomb_direction": "Direction de l'arc de bombe Atomique / Hydrogène"
},
"single_modal": {
"title": "Joueur seul",
"title": "Solo",
"random_spawn": "Spawn aléatoire",
"allow_alliances": "Autoriser les alliances",
"toggle_achievements": "Activer / Désactiver les succès",
"sign_in_for_achievements": "Connectez-vous pour obtenir des succès",
"options_title": "Options",
"bots": "Bots : ",
"bots_disabled": "Désactivé",
@@ -145,6 +167,8 @@
"infinite_troops": "Troupes infinies",
"compact_map": "Carte compacte",
"max_timer": "Durée de jeu (minutes)",
"max_timer_placeholder": "Mins",
"max_timer_invalid": "Veuillez entrer une valeur max valide pour le minuteur (1-120 minutes)",
"disable_nukes": "Désactiver les armes nucléaires",
"enables_title": "Activer les paramètres",
"start": "Commencer la partie"
@@ -156,10 +180,21 @@
},
"account_modal": {
"title": "Compte",
"logged_in_as": "Connecté en tant que {email}",
"connected_as": "Connecté en tant que",
"stats_overview": "Aperçu des Statistiques",
"link_discord": "Lier un Compte Discord",
"log_out": "Se Déconnecter",
"sign_in_desc": "Connectez-vous pour enregistrer vos statistiques et progrès",
"or": "OU",
"email_placeholder": "Entrez votre adresse email",
"get_magic_link": "Obtenir un Lien Magique",
"linked_account": "Connecté en tant que {account_name}",
"fetching_account": "Récupération des informations du compte...",
"logged_in_with_discord": "Connecté avec Discord",
"recovery_email_sent": "Courriel de récupération envoyé à {email}"
"recovery_email_sent": "Courriel de récupération envoyé à {email}",
"not_found": "Introuvable",
"clear_session": "Effacer la session",
"failed_to_send_recovery_email": "Échec de l'envoi de l'e-mail de récupération",
"enter_email_address": "Veuillez saisir une adresse e-mail"
},
"stats_modal": {
"title": "Statistiques",
@@ -167,11 +202,40 @@
"loading": "Chargement...",
"error": "Erreur lors du chargement des statistiques du clan",
"no_stats": "Pas de statistique de clan disponible",
"no_data_yet": "Aucune donnée pour le moment",
"clan": "Clan",
"games": "Parties",
"win_score": "Score de Victoire",
"win_score_tooltip": "Victoires pondérées en fonction de la participation du clan et de la difficulté du match",
"loss_score": "Score de Défaite",
"win_loss_ratio": "Victoires/Défaites"
"loss_score_tooltip": "Défaites pondérées en fonction de la participation du clan et de la difficulté du match",
"win_loss_ratio": "Victoires/Défaites",
"ratio": "Coefficient",
"rank": "Rang",
"try_again": "Réessayer"
},
"game_info_modal": {
"title": "Infos sur la partie",
"players": "Joueurs",
"atoms": "Atomes",
"hydros": "Hydros",
"mirv": "MIRV",
"bombs": "Bombes",
"total_gold": "Total",
"all_gold": "Tout l'or",
"trade": "Commercer",
"conquest_gold": "Or de joueur conquis",
"stolen_gold": "Volé avec des navires de guerre",
"num_of_conquests": "Nombre de joueurs conquis",
"duration": "Durée",
"survival_time": "Temps de survie",
"war": "Guerre",
"economy": "Économie",
"conquests": "Conquêtes",
"pirate": "Pirate",
"conquered": "Conquis",
"loading_game_info": "Chargement des stats du jeu",
"no_winner": "Cette partie s'est terminée sans aucun gagnant (ou une Nation a gagné)"
},
"map": {
"map": "Carte",
@@ -186,6 +250,7 @@
"asia": "Asie",
"mars": "Mars",
"southamerica": "Amérique du Sud",
"britanniaclassic": "Grande-Bretagne (Classique)",
"britannia": "Grande-Bretagne",
"gatewaytotheatlantic": "Porte de l'Atlantique",
"australia": "Australie",
@@ -196,7 +261,7 @@
"betweentwoseas": "Entre deux mers",
"faroeislands": "Îles Féroé",
"deglaciatedantarctica": "Antarctique Déglacée",
"europeclassic": "Europe (classique)",
"europeclassic": "Europe (Classique)",
"falklandislands": "Îles Malouines",
"baikal": "Lac Baïkal",
"halkidiki": "Chalcidique",
@@ -206,19 +271,33 @@
"yenisei": "Ienisseï",
"pluto": "Pluton",
"montreal": "Montréal",
"newyorkcity": "New York City",
"achiran": "Achiran",
"baikalnukewars": "Baïkal (Guerres Nucléaires)",
"fourislands": "Quatre Îles",
"gulfofstlawrence": "Golfe du Saint-Laurent",
"lisbon": "Lisbonne"
"lisbon": "Lisbonne",
"svalmel": "Svalmel",
"manicouagan": "Manicouagan",
"lemnos": "Lemnos",
"sierpinski": "Sierpinski",
"twolakes": "Deux Lacs",
"straitofhormuz": "Détroit d'Ormuz",
"surrounded": "Encerclé",
"didier": "Didier",
"didierfrance": "Didier (France)",
"amazonriver": "Fleuve Amazone"
},
"map_categories": {
"continental": "Continental",
"regional": "Régional",
"fantasy": "Autre"
"fantasy": "Autre",
"special": "Spéciales",
"arcade": "Arcade"
},
"map_component": {
"loading": "Chargement..."
"loading": "Chargement...",
"error": "Erreur"
},
"private_lobby": {
"title": "Rejoindre un salon privé",
@@ -229,42 +308,55 @@
"checking": "Vérification du salon...",
"not_found": "Salon introuvable. Veuillez vérifier l'ID et réessayer.",
"error": "Une erreur s'est produite. Veuillez réessayer ou contacter le support.",
"joined_waiting": "Rejoint avec succès ! En attente du début de la partie...",
"version_mismatch": "Cette partie a été créée avec une version différente. Impossible de rejoindre."
"joined_waiting": "Salon rejoint ! En attente du démarrage de l'hôte...",
"version_mismatch": "Cette partie a été créée avec une version différente. Impossible de rejoindre.",
"disabled_units": "Unités désactivées"
},
"public_lobby": {
"join": "Rejoindre la prochaine partie",
"waiting": "joueurs en attente",
"teams_Duos": "de 2 (Duos)",
"teams_Trios": "de 3 (Trios)",
"teams_Quads": "de 4 (Quatuors)",
"teams_hvn": "Humains Vs Nations",
"teams_Duos": "{team_count} équipes de 2 (Duos)",
"teams_Trios": "{team_count} équipes de 3 (Trios)",
"teams_Quads": "{team_count} équipes de 4 (Quatuors)",
"waiting_for_players": "En attente de joueurs",
"starting_game": "Démarrage en cours...",
"teams_hvn": "Humains vs Nations",
"teams_hvn_detailed": "{num} Humains vs {num} Nations",
"teams": "{num} équipes",
"players_per_team": "de {num}"
"players_per_team": "de {num}",
"started": "Lancé"
},
"matchmaking_modal": {
"title": "Matchmaking",
"title": "Matchmaking 1v1 Classé (ALPHA)",
"connecting": "Connexion au serveur de matchmaking...",
"searching": "Recherche d'une partie...",
"waiting_for_game": "En attente du début de la partie..."
"waiting_for_game": "En attente du début de la partie...",
"elo": "Votre ELO : {elo}"
},
"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]."
"invalid_chars": "Le pseudonyme peut seulement contenir des lettres, chiffres, espaces et underscores.",
"tag": "TAG",
"tag_too_short": "Le tag de clan doit faire 2 à 5 caractères alphanumériques.",
"tag_invalid_chars": "Le tag de clan peut seulement contenir des lettres et des chiffres."
},
"host_modal": {
"title": "Salon privé",
"title": "Créer un Salon Privé",
"label": "Privé",
"mode": "Mode",
"team_count": "Nombre d'équipes",
"team_type": "Type d'équipe",
"options_title": "Paramètres",
"bots": "Bots : ",
"bots_disabled": "Désactivé",
"player_immunity_duration": "Immunité au JcJ (minutes)",
"nations": "Nations : ",
"disable_nations": "Désactiver les nations",
"max_timer": "Durée de jeu (minutes)",
"mins_placeholder": "Mins",
"instant_build": "Construction instantanée",
"infinite_gold": "Or infini",
"donate_gold": "Donner de l'or",
@@ -283,7 +375,11 @@
"assigned_teams": "Équipes Attribuées",
"empty_teams": "Équipes vides",
"empty_team": "Vide",
"remove_player": "Retirer {username}"
"remove_player": "Retirer {username}",
"teams_Duos": "Duos (équipes de 2)",
"teams_Trios": "Trios (équipes de 3)",
"teams_Quads": "Quatuors (équipes de 4)",
"teams_Humans Vs Nations": "Humains vs Nations"
},
"team_colors": {
"red": "Rouge",
@@ -301,16 +397,20 @@
"code_license": "Code sous licence AGPL-3.0 (sans garantie)"
},
"difficulty": {
"difficulty": "Difficulté",
"Easy": "Détendu",
"Medium": "Équilibré",
"Hard": "Intense",
"Impossible": "Impossible"
"difficulty": "Difficulté des nations",
"easy": "Facile",
"medium": "Moyen",
"hard": "Difficile",
"impossible": "Impossible"
},
"game_mode": {
"ffa": "Chacun pour soi",
"teams": "Équipes"
},
"public_game_modifier": {
"random_spawn": "Spawn aléatoire",
"compact_map": "Carte compacte"
},
"select_lang": {
"title": "Sélectionner une langue"
},
@@ -327,7 +427,7 @@
"factory": "Usine"
},
"user_setting": {
"title": "Paramètres utilisateur",
"title": "Paramètres",
"tab_basic": "Réglages de base",
"tab_keybinds": "Raccourcis clavier",
"dark_mode_label": "Mode sombre",
@@ -340,16 +440,18 @@
"special_effects_desc": "Activer/désactiver les effets spéciaux. Désactiver pour améliorer les performances",
"structure_sprites_label": "Sprites de structure",
"structure_sprites_desc": "Activer/désactiver les sprites de structure",
"cursor_cost_label_label": "Coûts de construction",
"cursor_cost_label_desc": "Afficher une pastille indiquant le coût sous l'icône du curseur de construction",
"anonymous_names_label": "Noms masqués",
"anonymous_names_desc": "Cacher le vrai nom des joueurs avec des noms aléatoires sur votre écran.",
"lobby_id_visibility_label": "ID du salon masqué",
"lobby_id_visibility_desc": "Cacher l'ID du salon lors de la création du salon privé",
"toggle_visibility": "Changer la visibilité",
"left_click_label": "Clic gauche pour ouvrir le menu",
"left_click_desc": "Activé, un clic gauche ouvre le menu et le bouton épée d'attaque. Désactivé, un clic gauche attaque directement.",
"left_click_menu": "Menu Clic gauche",
"attack_ratio_label": "⚔️ Ratio d'attaque",
"attack_ratio_desc": "Quel pourcentage de vos troupes envoyer dans une attaque (1100%)",
"troop_ratio_desc": "Ajuster l'équilibre entre les troupes (pour le combat) et les ouvriers (pour la production d'or) (1100%)",
"territory_patterns_label": "🏳️ Skins de territoire",
"territory_patterns_desc": "Choisissez d'afficher ou non les designs des skins de territoire dans le jeu",
"performance_overlay_label": "Surcouche de performances",
@@ -358,6 +460,7 @@
"easter_writing_speed_desc": "Ajuster la vitesse à laquelle vous prétendez coder (x1x100)",
"easter_bug_count_label": "Nombre de bugs",
"easter_bug_count_desc": "Combien de bugs vous acceptez (0-1000, émotionnellement)",
"press_a_key": "Appuyez sur une touche",
"view_options": "Options d'affichage",
"toggle_view": "Activer/désactiver l'affichage",
"toggle_view_desc": "Vue alternative (terrain/pays)",
@@ -382,6 +485,11 @@
"build_hydrogen_bomb_desc": "Envoyer une bombe à hydrogène sous votre curseur.",
"build_mirv": "Construire un MIRV",
"build_mirv_desc": "Construire un MIRV sous votre curseur.",
"menu_shortcuts": "Menu des raccourcis ",
"build_menu_modifier": "Menu de modification des constructions",
"build_menu_modifier_desc": "Maintenez cette touche enfoncée en cliquant pour ouvrir le menu de construction.",
"emoji_menu_modifier": "Menu de modifications des émojis",
"emoji_menu_modifier_desc": "Maintenez cette touche enfoncée en cliquant pour ouvrir le menu des emojis.",
"attack_ratio_controls": "Contrôles du ratio d'attaque",
"attack_ratio_up": "Augmenter le ratio d'attaque",
"attack_ratio_up_desc": "Augmenter le ratio d'attaque de 10%",
@@ -392,6 +500,8 @@
"boat_attack_desc": "Envoyer une attaque navale à la tuile sous votre curseur.",
"ground_attack": "Attaque au sol",
"ground_attack_desc": "Envoyez une attaque au sol sur la tuile sous votre curseur.",
"swap_direction": "Inverser la trajectoire balistique",
"swap_direction_desc": "Inverser la trajectoire balistique de lancement",
"zoom_controls": "Contrôles de zoom",
"zoom_out": "Zoom arrière",
"zoom_out_desc": "Dézoom de la carte",
@@ -416,7 +526,8 @@
"exit_game_label": "Quitter la partie",
"exit_game_info": "Retour au menu principal",
"background_music_volume": "Volume de la musique de fond",
"sound_effects_volume": "Volume des effets sonores"
"sound_effects_volume": "Volume des effets sonores",
"keybind_conflict_error": "La clé {key} est déjà liée à une autre action."
},
"chat": {
"title": "Discussion",
@@ -529,6 +640,7 @@
"other_team": "L'équipe {team} a gagné !",
"you_won": "Vous avez gagné !",
"other_won": "{player} a gagné !",
"nation_won": "La nation {nation} a gagné !",
"exit": "Quitter la partie",
"keep": "Continuer à jouer",
"spectate": "Regarder",
@@ -537,7 +649,7 @@
"ofm_winter_description": "Rejoignez le tournoi et affrontez les meilleurs joueurs",
"join_tournament": "Rejoindre le tournoi",
"join_discord": "Rejoignez notre communauté Discord !",
"discord_description": "Connectez-vous avec d'autres joueurs, recevez les nouvelles et partagez des stratégies",
"discord_description": "Parlez avec des joueurs, découvrez de nouvelles fonctionnalités et gagnez des prix !",
"join_server": "Rejoindre le Serveur",
"youtube_tutorial": "Besoin d'aide ?"
},
@@ -549,7 +661,7 @@
"team": "Équipe",
"owned": "Possédé",
"gold": "Or",
"troops": "Troupes",
"maxtroops": "Troupes max",
"launchers": "Lanceurs",
"sams": "SAMs",
"warships": "Vaisseaux de guerre",
@@ -565,6 +677,7 @@
"team": "Équipe",
"alliance_timeout": "L'alliance se termine dans",
"troops": "Troupes",
"maxtroops": "Troupes max",
"a_troops": "Troupes en attaque",
"gold": "Or",
"ports": "Ports",
@@ -575,7 +688,9 @@
"warships": "Navires de guerre",
"health": "Santé",
"attitude": "Attitude",
"levels": "Niveaux"
"levels": "Niveaux",
"wilderness_title": "Étendues sauvages",
"irradiated_wilderness_title": "Terre irradiée"
},
"events_display": {
"retreating": "en retraite",
@@ -601,7 +716,20 @@
"wants_to_renew_alliance": "{name} souhaite renouveler votre alliance",
"ignore": "Ignorer",
"unit_voluntarily_deleted": "Unité volontairement supprimée",
"betrayal_debuff_ends": "{time} secondes restantes jusqu'à la fin du malus de trahison"
"betrayal_debuff_ends": "{time} secondes restantes jusqu'à la fin du malus de trahison",
"attack_cancelled_retreat": "Attaque annulée, {troops} soldats ont été tués pendant la retraite",
"received_gold_from_captured_ship": "{gold} ors reçu pour la capture d'un navire de {name}",
"received_gold_from_trade": "{gold} ors reçu pour le commerce avec {name}",
"missile_intercepted": "Le missile a intercepté {unit}",
"mirv_warheads_intercepted": "{count, plural, one {{count} ogive nucléaire MIRV a été interceptée} other {{count} ogives nucléaire MIRV ont été interceptées}}",
"sent_troops_to_player": "Vous avez envoyé {troops} troupes à {name}",
"received_troops_from_player": "Vous avez reçu {troops} troupes de {name}",
"sent_gold_to_player": "Vous avez envoyé {gold} ors à {name}",
"received_gold_from_player": "Vous avez reçu {gold} ors de {name}",
"unit_captured_by_enemy": "Votre {unit} a été capturé par {name}",
"captured_enemy_unit": "{unit} de {name} capturé",
"unit_destroyed": "Votre {unit} a été détruit",
"no_boats_available": "Aucun bateau disponible, max {max}"
},
"unit_info_modal": {
"structure_info": "Infos sur la structure",
@@ -653,7 +781,10 @@
"send_alliance": "Envoyer une alliance",
"send_troops": "Envoyer des troupes",
"send_gold": "Envoyer de l'or",
"emotes": "Émojis"
"emotes": "Émojis",
"arc_up": "Arc vers le haut",
"arc_down": "Arc vers le bas",
"flip_rocket_trajectory": "Inverser la trajectoire de la fusée"
},
"send_troops_modal": {
"title_with_name": "Envoyer des troupes à {name}",
@@ -702,20 +833,26 @@
},
"heads_up_message": {
"choose_spawn": "Choisissez un emplacement de départ",
"random_spawn": "Le spawn aléatoire est activé. Sélection de l'emplacement de départ pour vous..."
"random_spawn": "Le spawn aléatoire est activé. Sélection de l'emplacement de départ pour vous...",
"singleplayer_game_paused": "Jeu en pause",
"multiplayer_game_paused": "Jeu mis en pause par le créateur du salon"
},
"territory_patterns": {
"title": "Skins",
"colors": "Couleurs",
"purchase": "Acheter",
"show_only_owned": "Mes skins",
"all_owned": "Vous possédez déjà tous les motifs ! Revenez plus tard pour de nouveau.",
"not_logged_in": "Non connecté",
"blocked": {
"login": "Vous devez être connecté pour accéder à ce skin.",
"purchase": "Achetez ce skin pour le débloquer."
},
"pattern": {
"default": "Par défaut"
}
},
"select_skin": "Sélectionnez le motif",
"selected": "sélectionné"
},
"flag_input": {
"title": "Sélectionner un drapeau",
@@ -786,8 +923,9 @@
"mode": "Mode",
"mode_ffa": "Chacun pour soi",
"mode_team": "Équipe",
"view": "Vue",
"replay": "Revoir",
"details": "Détails",
"ranking": "Classé",
"started": "Débuté",
"map": "Carte",
"difficulty": "Difficulté",
@@ -796,13 +934,20 @@
"player_stats_tree": {
"public": "Public",
"private": "Privé",
"singleplayer": "Mode solo",
"singleplayer": "Solo",
"mode": "Mode",
"stats_wins": "Victoires",
"stats_losses": "Défaites",
"stats_wlr": "Ratio Victoires:Défaites",
"stats_games_played": "Parties jouées",
"mode_ffa": "Chacun pour soi",
"mode_team": "En équipe"
"mode_team": "En équipe",
"no_stats": "Aucune statistique enregistrée pour cette sélection."
},
"matchmaking_button": {
"play_ranked": "Matchmaking 1v1 classé",
"description": "(ALPHA)",
"login_required": "Connectez-vous pour jouer en mode classé",
"must_login": "Vous devez être connecté pour jouer en mode classé."
}
}
+953
View File
@@ -0,0 +1,953 @@
{
"lang": {
"en": "Indonesian",
"native": "Bahasa Indonesia",
"svg": "id",
"lang_code": "id"
},
"common": {
"close": "Keluar",
"back": "Kembali",
"available": "Tersedia",
"preset_max": "Maks",
"summary_send": "Kirim",
"summary_keep": "Simpan",
"cancel": "Batalkan",
"send": "Kirim",
"cap_label": "Batas maksimal",
"cap_tooltip": "Kapasitas penerima yang tersisa",
"target_dead": "Target dieliminasi",
"target_dead_note": "Anda tidak dapat mengirim sumber daya ke pemain yang telah tereliminasi.",
"none": "Tidak Satupun",
"copied": "Tersalin",
"click_to_copy": "Klik untuk salin"
},
"main": {
"title": "OpenFront (ALPHA)",
"join_discord": "Discord",
"login_discord": "Masuk dengan Discord",
"sign_in": "Masuk",
"discord_avatar_alt": "Avatar profil Discord",
"user_avatar_alt": "Avatar {username}",
"checking_login": "Memeriksa login...",
"logged_in": "Berhasil masuk!",
"log_out": "Keluar",
"create": "Buat Lobi",
"join": "Bergabung ke Lobi",
"solo": "Sendiri",
"instructions": "Petunjuk",
"game_info": "Informasi Permainan",
"wiki": "Wiki",
"privacy_policy": "Kebijakan Privasi",
"terms_of_service": "Ketentuan Layanan",
"copyright": "© OpenFront™ dan para kontributor",
"reddit": "Reddit",
"play": "Main",
"news": "Berita",
"store": "Toko",
"settings": "Pengaturan",
"keys": "Tombol",
"stats": "Statistik",
"account": "Akun",
"help": "Bantuan",
"menu": "Menu",
"pick_pattern": "Pilih pola!"
},
"news": {
"github_link": "di GitHub",
"title": "Catatan Rilis"
},
"help_modal": {
"hotkeys": "Tombol pintas",
"table_key": "Kunci",
"table_action": "Tindakan",
"action_alt_view": "Ganti Tampilan (Medan / Negara)",
"action_attack_altclick": "Serang (saat klik kiri diatur untuk membuka menu)",
"action_build": "Buka menu Pembangunan",
"action_emote": "Buka menu Ekspresi",
"action_center": "Pusatkan kamera pada pemain",
"action_zoom": "Perkecil / Perbesar tampilan",
"action_move_camera": "Pindahkan kamera",
"action_ratio_change": "Kurangi / Tingkatkan rasio serangan",
"action_reset_gfx": "Atur ulang grafis",
"action_auto_upgrade": "Tingkatkan bangunan terdekat secara otomatis",
"ui_section": "UI Permainan",
"ui_leaderboard": "Papan Peringkat",
"ui_your_team": "Tim anda:",
"ui_leaderboard_desc": "Menampilkan pemain teratas dalam permainan beserta nama mereka, persentase wilayah yang dikuasai, jumlah emas, dan pasukan. Opsi Tampilkan Semua akan menampilkan seluruh pemain dalam permainan. Jika tidak ingin melihat papan peringkat, klik Sembunyikan.",
"ui_control": "Panel kendali",
"ui_control_desc": "Panel kontrol berisi elemen berikut:",
"ui_pop": "Populasi - Jumlah unit yang kamu miliki, batas populasi maksimum, serta laju pertambahannya.",
"ui_gold": "Emas - Jumlah emas yang kamu miliki dan laju perolehannya.",
"ui_attack_ratio": "Rasio Serangan - Jumlah pasukan yang akan digunakan saat kamu menyerang. Kamu dapat menyesuaikan rasio serangan menggunakan penggeser. Memiliki pasukan penyerang lebih banyak daripada pasukan bertahan akan mengurangi jumlah pasukan yang hilang saat menyerang, sedangkan jumlah pasukan yang lebih sedikit akan meningkatkan kerusakan yang diterima pasukan penyerang. Efek ini tidak berlaku di atas rasio 2:1.",
"ui_events": "Panel Event",
"ui_events_desc": "Panel Event menampilkan peristiwa, permintaan, dan pesan Obrolan Cepat terbaru. Beberapa contohnya adalah:",
"ui_events_alliance": "Aliansi - Permintaan aliansi dapat diterima atau ditolak. Sekutu dapat berbagi sumber daya dan pasukan, tetapi tidak dapat saling menyerang. Menekan Fokus akan memusatkan tampilan ke pemain yang mengirim permintaan.",
"ui_events_attack": "Serangan - Menampilkan serangan yang masuk dan serangan yang kamu lakukan. Klik pesan untuk memusatkan tampilan ke lokasi serangan, nuklir, atau Kapal (kapal pengangkut). Kamu dapat menarik mundur pasukan dengan menekan tombol X merah. Tindakan ini akan mengorbankan 25% dari pasukan penyerang.\nJika serangan Kapal ditarik kembali, kapal akan kembali ke titik awal dan akan menyerang kembali di sana jika wilayah tersebut telah dikuasai sejak saat itu. Serangan nuklir tidak dapat ditarik kembali setelah diluncurkan.",
"ui_events_quickchat": "Obrolan Cepat - Di sini kamu dapat melihat pesan obrolan yang dikirim dan diterima. Untuk mengirim pesan ke pemain, klik ikon Obrolan Cepat di menu Info pemain tersebut.",
"ui_options": "Pilihan",
"ui_options_desc": "Elemen-elemen berikut dapat ditemukan di dalamnya:",
"ui_playeroverlay": "Overlay Info Pemain",
"ui_playeroverlay_desc": "Saat kamu mengarahkan kursor ke suatu negara, overlay Info Pemain akan ditampilkan di bawah menu Opsi. Overlay ini menampilkan jenis pemain: Manusia, Negara (bot pintar), atau Bot; sikap suatu Negara terhadapmu, mulai dari Bermusuhan hingga Ramah; serta jumlah pasukan bertahan, emas, jumlah Kapal Perang, dan berbagai bangunan yang dimiliki pemain tersebut.",
"ui_wilderness": "Alam Liar",
"option_pause": "Jeda / Lanjutkan permainan Hanya tersedia dalam mode single-player.",
"option_timer": "Timer Waktu yang telah berlalu sejak permainan dimulai.",
"option_exit": "Tombol keluar.",
"option_settings": "Pengaturan Membuka menu pengaturan. Di dalamnya kamu dapat mengaktifkan atau menonaktifkan Tampilan Alternatif, Emoji, Mode Gelap, Ninja (mode anonim / nama acak), serta aksi pada klik kiri.",
"radial_title": "Menu Radial",
"radial_desc": "Klik kanan (atau sentuhan di perangkat seluler) akan membuka Menu Radial. Klik kanan di luar menu untuk menutupnya. Dari menu ini kamu dapat:",
"radial_build": "Buka menu Pembangunan.",
"radial_attack": "Buka menu Serangan.",
"radial_info": "Buka menu informasi.",
"radial_boat": "Kirim Kapal (kapal pengangkut) untuk menyerang lokasi yang dipilih. Hanya tersedia jika kamu memiliki akses ke perairan.",
"radial_donate_troops": "Donasikan pasukan kepada sekutu sesuai dengan persentase pada penggeser rasio serangan yang sedang kamu gunakan pada menu radial tersebut.",
"radial_donate_gold": "Membuka menu penggeser donasi emas sehingga kamu dapat dengan cepat mengirim emas kepada sekutu.",
"radial_close": "Tutup menu.",
"info_title": "Menu Informasi",
"info_enemy_desc": "Berisi informasi seperti nama pemain yang dipilih, jumlah emas, pasukan, status berhenti berdagang dengan kamu, nuklir yang dikirim ke arahmu, serta apakah pemain tersebut adalah pengkhianat.\nStatus Berhenti Berdagang berarti kamu tidak akan menerima emas dari pemain tersebut dan mereka juga tidak akan mengirimkan emas kepadamu melalui kapal dagang. Status ini dapat terjadi secara manual (jika pemain menekan tombol “Hentikan Perdagangan”, yang akan berlangsung sampai kalian berdua menekan “Mulai Perdagangan”) atau secara otomatis (jika kamu mengkhianati aliansi, yang akan berlangsung sampai kalian kembali menjadi sekutu atau setelah 5 menit).\nStatus Pengkhianat akan menampilkan “Ya” selama 30 detik ketika pemain tersebut mengkhianati dan menyerang pemain yang sebelumnya berada dalam aliansi dengannya.\nIkon-ikon di bawah ini mewakili interaksi berikut:",
"info_chat": "Kirim pesan Obrolan Cepat ke pemain. Pilih Kategori, Frasa, dan jika frasa berisi [P1], pilih nama pemain untuk menggantikannya. Lalu tekan Kirim.",
"info_target": "Pasang tanda target pada pemain, sehingga terlihat oleh semua sekutu. Digunakan untuk mengoordinasikan serangan.",
"info_alliance": "Kirim permintaan aliansi ke pemain. Sekutu dapat berbagi sumber daya dan pasukan, tetapi tidak dapat saling menyerang.",
"info_emoji": "Kirim emoji ke pemainnya.",
"info_trade": "Gunakan “Hentikan Perdagangan” untuk berhenti memberikan emas kepada pemain tersebut dan berhenti menerima emas dari mereka melalui kapal dagang. Jika kalian berdua menekan “Mulai\".",
"info_ally_panel": "Panel Info Sekutu",
"info_ally_desc": "Saat kamu beraliansi dengan seorang pemain, ikon-ikon baru berikut akan tersedia:",
"ally_betray": "Mengkhianati sekutumu akan mengakhiri aliansi, menghentikan perdagangan, dan melemahkan pertahananmu. Perdagangan di antara kalian akan dijeda selama 5 menit (atau sampai kalian kembali menjadi sekutu), dan pemain lain juga dapat menghentikan perdagangan. Kecuali jika pemain lain tersebut memang sudah berstatus pengkhianat, kamu akan ditandai sebagai Pengkhianat selama 30 detik.\nSelama waktu ini, sebuah ikon akan muncul di atas namamu dan kamu akan menerima debuff pertahanan sebesar 50%. Bot akan lebih enggan beraliansi denganmu, dan pemain lain akan berpikir dua kali sebelum melakukannya.",
"ally_donate": "Donasikan sebagian pasukanmu kepada sekutu. Digunakan ketika mereka kekurangan pasukan, sedang diserang, atau membutuhkan kekuatan tambahan untuk menghancurkan musuh.",
"ally_donate_gold": "Donasikan sebagian emasmu kepada sekutu. Digunakan saat mereka kekurangan emas dan membutuhkannya untuk membangun, atau ketika anggota tim sedang menabung untuk MIRV.",
"build_menu_title": "Menu Pembangunan",
"build_menu_desc": "Bangun item berikut atau lihat jumlah yang sudah kamu bangun:",
"build_name": "Judul",
"build_icon": "Ikon",
"build_desc": "Deskripsi",
"build_city": "Kota",
"build_city_desc": "Meningkatkan batas populasi maksimum. Berguna saat kamu tidak dapat memperluas wilayah atau hampir mencapai batas populasi.",
"build_factory": "Pabrik",
"build_factory_desc": "Secara otomatis membangun jalur kereta api ke kota, pelabuhan, dan pabrik lain di sekitarnya, serta dapat terhubung dengan negara tetangga yang bersahabat. Kereta akan muncul secara berkala dan memberimu sejumlah emas tetap untuk setiap bangunan yang dikunjungi sepanjang rute, dengan bonus emas tambahan saat mengunjungi bangunan milik tetanggamu.",
"build_defense": "Pos Pertahanan",
"build_defense_desc": "Meningkatkan pertahanan di sekitar perbatasan terdekat, yang ditandai dengan pola kotak-kotak. Serangan musuh menjadi lebih lambat dan menyebabkan lebih banyak korban.",
"build_port": "Pelabuhan",
"build_port_desc": "Hanya dapat dibangun di dekat air. Memungkinkan pembangunan Kapal Perang. Secara otomatis mengirim kapal dagang antara pelabuhan di negaramu dan negara lain (kecuali saat perdagangan dihentikan), yang memberikan emas bagi kedua pihak.\nPerdagangan dengan seorang pemain akan berhenti secara otomatis ketika kamu menyerang atau diserang oleh pemain tersebut. Perdagangan akan dilanjutkan kembali setelah 5 menit atau jika kalian menjadi sekutu. Kamu juga dapat mengatur perdagangan secara manual dengan memilih “Hentikan Perdagangan” atau “Mulai Perdagangan”.",
"build_warship": "Kapal Perang",
"build_warship_desc": "Berpatroli di suatu area, menangkap kapal dagang musuh serta menghancurkan Kapal (kapal pengangkut) dan Kapal Perang mereka. Unit ini muncul dari Pelabuhan terdekat dan akan berpatroli di area yang pertama kali kamu klik saat membangunnya.\nKamu dapat mengendalikan Kapal Perang dengan klik-serang pada unit tersebut (lihat aksi Serang pada menu Hotkeys), lalu klik-serang area baru yang ingin dituju.",
"build_silo": "Silo Peluncur Rudal",
"build_silo_desc": "Memungkinkan peluncuran rudal.",
"build_sam": "Peluncur Rudal SAM",
"build_sam_desc": "Dapat mencegat rudal musuh dalam jangkauan 100 piksel. Peluncur SAM memiliki waktu jeda cooldown 7,5 detik.",
"build_atom": "Bom Atom",
"build_atom_desc": "Bom kecil berdaya ledak tinggi yang menghancurkan wilayah, bangunan, kapal, dan perahu. Muncul dari Silo Rudal terdekat dan mendarat di area yang pertama kali kamu klik saat membangunnya.",
"build_hydrogen": "Bom Hidrogen",
"build_hydrogen_desc": "Bom berdaya ledak besar. Muncul dari Silo Rudal terdekat dan mendarat di area yang pertama kali kamu klik saat membangunnya.",
"build_mirv": "MIRV",
"build_mirv_desc": "Bom paling kuat di dalam permainan. Akan terpecah menjadi bom-bom yang lebih kecil dan mencakup area wilayah yang sangat luas. Hanya memberikan kerusakan kepada pemain yang pertama kali kamu klik saat membangunnya.\nSenjata ini muncul dari Silo Rudal terdekat dan akan mendarat di area yang pertama kali kamu klik saat membangunnya.",
"player_icons": "Ikon Pemain",
"icon_desc": "Berikut beberapa ikon yang akan kamu temui di dalam permainan beserta artinya:",
"icon_crown": "Mahkota Peringkat 1. Pemain teratas di papan peringkat.",
"icon_traitor": "Perisai Retak Pengkhianat. Pemain ini menyerang sekutu.",
"icon_ally": "Jabat Tangan Sekutu. Pemain ini adalah sekutumu.",
"icon_embargo": "Tanda Dolar Dicoret Embargo. Pemain ini menghentikan perdagangan denganmu, baik secara otomatis maupun manual.",
"icon_request": "Amplop Permintaan Aliansi. Pemain ini mengirim permintaan aliansi kepadamu.",
"info_enemy_panel": "Panel Info Musuh",
"exit_confirmation": "Apakah yakin keluar dari game?",
"bomb_direction": "Arah busur bom atom/hidrogen"
},
"single_modal": {
"title": "Sendiri",
"random_spawn": "Kemunculan acak",
"allow_alliances": "Perbolehkan Aliansi",
"toggle_achievements": "Tampilkan / Sembunyikan pencapaian",
"sign_in_for_achievements": "Masuk untuk melihat pencapaian",
"options_title": "Opsi",
"bots": "Bot: ",
"bots_disabled": "Dinonaktifkan",
"nations": "Bangsa-bangsa: ",
"disable_nations": "Nonaktifkan negara",
"instant_build": "Bangun instan",
"infinite_gold": "Emas tak terbatas",
"infinite_troops": "Pasukan tak terbatas",
"compact_map": "Peta Kecil",
"max_timer": "Lama permainan (menit)",
"max_timer_placeholder": "Menit",
"max_timer_invalid": "Silakan masukkan nilai pengatur waktu maksimum yang valid (1-120 menit)",
"disable_nukes": "Nonaktifkan Senjata Nuklir",
"enables_title": "Aktifkan Pengaturan",
"start": "Mulai Permainan"
},
"token_login_modal": {
"title": "Sedang masuk...",
"logging_in": "Sedang masuk...",
"success": "Berhasil masuk sebagai {email}!"
},
"account_modal": {
"title": "Akun",
"connected_as": "Terhubung sebagai",
"stats_overview": "Gambaran Umum Statistik",
"link_discord": "Tautkan Akun Discord",
"log_out": "Keluar",
"sign_in_desc": "Masuk untuk menyimpan statistik dan kemajuan Anda",
"or": "ATAU",
"email_placeholder": "Masukkan alamat email Anda",
"get_magic_link": "Dapatkan Tautan Ajaib",
"linked_account": "Masuk sebagai {account_name}",
"fetching_account": "Mengambil informasi akun...",
"recovery_email_sent": "Pemulihan email dikirim ke {email}",
"not_found": "Tidak Ditemukan",
"clear_session": "Hapus Sesi",
"failed_to_send_recovery_email": "Gagal mengirim pemulihan email",
"enter_email_address": "Silahkan masukan alamat email"
},
"stats_modal": {
"title": "Statistik",
"clan_stats": "Statistik Klan",
"loading": "Loading...",
"error": "Error saat memuat statistik klan",
"no_stats": "Tidak ada klan yang tersedia",
"no_data_yet": "Data belum tersedia",
"clan": "Klan",
"games": "Permainan",
"win_score": "Skor Kemenangan",
"win_score_tooltip": "Kemenangan dihitung berdasarkan bobot partisipasi klan dan tingkat kesulitan pertandingan",
"loss_score": "Skor Kekalahan",
"loss_score_tooltip": "Kerugian dihitung berdasarkan partisipasi klan dan kesulitan pertandingan",
"win_loss_ratio": "Menang/Kalah",
"ratio": "Rasio",
"rank": "Peringkat",
"try_again": "Coba Lagi"
},
"game_info_modal": {
"title": "Informasi Permainan",
"players": "Pemain",
"atoms": "Atom",
"hydros": "Hidro",
"mirv": "MIRV",
"bombs": "Bom",
"total_gold": "Total",
"all_gold": "Semua emas",
"trade": "Perdagangan",
"conquest_gold": "Emas pemain yang ditaklukan",
"stolen_gold": "Dicuri oleh Kapal Perang",
"num_of_conquests": "Jumlah pemain yang ditaklukan",
"duration": "Durasi",
"survival_time": "Menit Bertahan",
"war": "Perang",
"economy": "Ekonomi",
"conquests": "Penaklukan",
"pirate": "Bajak Laut",
"conquered": "Ditaklukan",
"loading_game_info": "Memuat Statistik Permainan",
"no_winner": "Permainan ini berakhir tanpa pemenang (atau Negara menang)"
},
"map": {
"map": "Peta",
"world": "Dunia",
"giantworldmap": "Map Dunia Besar",
"europe": "Eropa",
"mena": "MENA",
"northamerica": "Amerika Utara",
"oceania": "Oseania",
"blacksea": "Laut Hitam",
"africa": "Afrika",
"asia": "Asia",
"mars": "Mars",
"southamerica": "Amerika Selatan",
"britanniaclassic": "Britania (klasik)",
"britannia": "Britania",
"gatewaytotheatlantic": "Pintu masuk menuju Atlantik",
"australia": "Australia",
"random": "Random",
"iceland": "Islandia",
"pangaea": "Pangea",
"eastasia": "Asia Timur",
"betweentwoseas": "Diantara Dua Laut",
"faroeislands": "Kepulauan Faroe",
"deglaciatedantarctica": "Antartika yang telah bebas dari gletser",
"europeclassic": "Eropa (klasik)",
"falklandislands": "Kepulauan Falkland",
"baikal": "Baikal",
"halkidiki": "Kalkidiki",
"straitofgibraltar": "Selat Gibraltar",
"italia": "Italia",
"japan": "Jepang",
"yenisei": "Sungai Yenisei",
"pluto": "Pluto",
"montreal": "Montreal",
"newyorkcity": "Kota New York",
"achiran": "Sungai Akheron",
"baikalnukewars": "Baikal (Perang Nuklir)",
"fourislands": "Empat Pulau",
"gulfofstlawrence": "Teluk St. Lawrence",
"lisbon": "Lisboa",
"svalmel": "Svalmel",
"manicouagan": "Manicouagan",
"lemnos": "Lemnos",
"sierpinski": "Sierpinski",
"twolakes": "Dua Danau",
"straitofhormuz": "Selat Hormuz",
"surrounded": "Surrourded",
"didier": "Didier",
"didierfrance": "Didier (Prancis)",
"amazonriver": "Sungai Amazon"
},
"map_categories": {
"continental": "Kontinental",
"regional": "Regional",
"fantasy": "Lain",
"special": "Spesial",
"arcade": "Arkade"
},
"map_component": {
"loading": "Loading...",
"error": "Kesalahan"
},
"private_lobby": {
"title": "Gabung Lobi Privat",
"enter_id": "Masukan ID Lobi",
"player": "Pemain",
"players": "Pemain",
"join_lobby": "Bergabung ke Lobi",
"checking": "Memeriksa Lobi...",
"not_found": "Lobi tidak ditemukan. Mohon periksa ID dan coba lagi.",
"error": "Beberapa kesalahan terjadi. Silakan coba lagi atau hubungi dukungan.",
"joined_waiting": "Berhasil gabung ke lobi! Menunggu untuk penyelenggara untuk memulai...",
"version_mismatch": "Permainan ini dibuat dengan versi yang berbeda. Tidak dapat gabung.",
"disabled_units": "Nonaktfikan Units"
},
"public_lobby": {
"join": "Gabung ke permainan selanjutnya",
"waiting": "Pemain menunggu",
"teams_Duos": "{team_count} tim berisi 2 pemain (Berdua)",
"teams_Trios": "{team_count} tim berisi 3 pemain (Bertiga)",
"teams_Quads": "{team_count} tim berisi 4 pemain (Berempat)",
"waiting_for_players": "Menunggu pemain",
"starting_game": "Memulai permainan…",
"teams_hvn": "Pemain vs Negara",
"teams_hvn_detailed": "{num} Pemain vs {num} Negara",
"teams": "{num} tim",
"players_per_team": "dari {num}",
"started": "Dimulai"
},
"matchmaking_modal": {
"title": "Pertandingan 1v1 Ranked (ALPHA)",
"connecting": "Menghubungkan ke server pencarian lawan...",
"searching": "Mencari permainan...",
"waiting_for_game": "Menunggu permainan untuk dimulai...",
"elo": "ELO anda: {elo}"
},
"username": {
"enter_username": "Masukkan nama pengguna",
"not_string": "Nama pengguna harus berupa string.",
"too_short": "Nama pengguna harus memiliki panjang minimal {min} karakter.",
"too_long": "Nama pengguna tidak boleh melebihi {max} karakter.",
"invalid_chars": "Nama pengguna hanya boleh berupa huruf, angka, spasi dan garis bawah.",
"tag": "Tag",
"tag_too_short": "Nama klan harus terdiri dari 2-5 karakter alfanumerik.",
"tag_invalid_chars": "Tag klan hanya boleh berisi huruf dan angka"
},
"host_modal": {
"title": "Buat Lobi Tertutup",
"label": "Tertutup",
"mode": "Mode",
"team_count": "Jumlah Tim",
"team_type": "Tipe Tim",
"options_title": "Pilihan",
"bots": "Bot: ",
"bots_disabled": "Nonaktif",
"player_immunity_duration": "Durasi imunitas PVP (menit)",
"nations": "Bangsa-bangsa: ",
"disable_nations": "Nonaktifkan Negara",
"max_timer": "Lama permainan (menit)",
"mins_placeholder": "Menit",
"instant_build": "Bangun instan",
"infinite_gold": "Emas tak terbatas",
"donate_gold": "Donasikan emas",
"infinite_troops": "Pasukan tak terbatas",
"donate_troops": "Donasikan pasukan",
"compact_map": "Peta Kecil",
"enables_title": "Aktifkan Pengaturan",
"player": "Pemain",
"players": "Pemain",
"nation_players": "Bangsa-bangsa",
"nation_player": "Bangsa",
"waiting": "Menunggu pemain...",
"random_spawn": "Kemunculan Acak",
"start": "Mulai Permainan",
"host_badge": "Host",
"assigned_teams": "Tim yang Ditugaskan",
"empty_teams": "Tim Kosong",
"empty_team": "Kosong",
"remove_player": "Hapus {username}",
"teams_Duos": "Berdua (tim yang terdiri dari 2 orang)",
"teams_Trios": "Bertiga (tim yang terdiri dari 3 orang)",
"teams_Quads": "Berempat (tim yang teridri dari 4 orang)",
"teams_Humans Vs Nations": "Pemain vs Negara"
},
"team_colors": {
"red": "Merah",
"blue": "Biru",
"teal": "Hijau Laut",
"purple": "Ungu",
"yellow": "Kuning",
"orange": "Oranye",
"green": "Hijau",
"bot": "Bot"
},
"game_starting_modal": {
"title": "Memulai Permainan...",
"credits": "Kredit",
"code_license": "Kode berlisensi AGPL-3.0 (tanpa garansi)"
},
"difficulty": {
"difficulty": "Kesulitan Negara",
"easy": "Mudah",
"medium": "Sedang",
"hard": "Sulit",
"impossible": "Mustahil"
},
"game_mode": {
"ffa": "Siapapun bisa bergabung",
"teams": "Tim-tim"
},
"public_game_modifier": {
"random_spawn": "Kemunculan Acak",
"compact_map": "Peta Kecil"
},
"select_lang": {
"title": "Pilih Bahasa"
},
"unit_type": {
"city": "Kota",
"defense_post": "Pos Pertahanan",
"port": "Pelabuhan",
"warship": "Kapal Perang",
"missile_silo": "Silo Peluncur Rudal",
"sam_launcher": "Peluncur Rudal SAM",
"atom_bomb": "Bom Atom",
"hydrogen_bomb": "Bom Hidrogen",
"mirv": "MIRV",
"factory": "Pabrik"
},
"user_setting": {
"title": "Pengaturan",
"tab_basic": "Pengaturan Dasasr",
"tab_keybinds": "Tombol pintasan",
"dark_mode_label": "Mode Gelap",
"dark_mode_desc": "Beralih tampilan situs antara tema terang dan gelap",
"emojis_label": "Emoji",
"emojis_desc": "Alihkan tampilan emoji di dalam game",
"alert_frame_label": "Bingkai Peringatan",
"alert_frame_desc": "Aktifkan / Nonaktifkan bingkai peringatan. Saat diaktifkan, bingkai akan ditampilkan ketika kamu dikhianati atau diserang melalui darat.",
"special_effects_label": "Efek Spesial",
"special_effects_desc": "Alihkan efek khusus. Nonaktifkan untuk meningkatkan performa",
"structure_sprites_label": "Sprite Bangunan",
"structure_sprites_desc": "Alihkan tampilan sprite bangunan",
"cursor_cost_label_label": "Biaya Pembangunan Kursor",
"cursor_cost_label_desc": "Tampilkan label biaya di bawah ikon kursor pembangunan",
"anonymous_names_label": "Sembunyikan Nama",
"anonymous_names_desc": "Sembunyikan nama asli pemain dengan nama acak di layar Anda.",
"lobby_id_visibility_label": "Sembunyikan ID Lobby",
"lobby_id_visibility_desc": "Sembunyikan ID Lobby saat membuat lobby pribadi",
"toggle_visibility": "Alihkan Visibilitas",
"left_click_label": "Klik Kiri untuk Membuka Menu",
"left_click_desc": "Saat AKTIF, klik kiri membuka menu dan tombol pedang digunakan untuk menyerang. Saat NONAKTIF, klik kiri langsung melakukan serangan.",
"left_click_menu": "Klik Kiri untuk Menu",
"attack_ratio_label": "⚔️ Rasio Serangan",
"attack_ratio_desc": "Persentase pasukan yang dikirim saat menyerang (1100%)",
"territory_patterns_label": "🏳️ Skin Wilayah",
"territory_patterns_desc": "Pilih apakah ingin menampilkan desain skin wilayah di dalam game",
"performance_overlay_label": "Tampilan Performa",
"performance_overlay_desc": "Aktifkan / Nonaktifkan overlay performa.\nSaat diaktifkan, overlay performa akan ditampilkan. Tekan Shift + D saat permainan berlangsung untuk mengaktifkan atau menonaktifkannya.",
"easter_writing_speed_label": "Multiplier Kecepatan Menulis",
"easter_writing_speed_desc": "Atur seberapa cepat kamu berpura-pura coding (x1x100)",
"easter_bug_count_label": "Jumlah Bug",
"easter_bug_count_desc": "Seberapa banyak bug yang masih bisa Anda toleransi (01000, secara emosional)",
"press_a_key": "Tekan tombol",
"view_options": "Opsi Tampilan",
"toggle_view": "Alihkan Tampilan",
"toggle_view_desc": "Ganti Tampilan (Medan / Negara)",
"build_controls": "Kontrol Pembangunan",
"build_city": "Membangun Kota",
"build_city_desc": "Bangun Kota di bawah kursor Anda.",
"build_factory": "Bangun Pabrik",
"build_factory_desc": "Bangun Pabrik di bawah kursor Anda.",
"build_defense_post": "Bangun Pos Pertahanan",
"build_defense_post_desc": "Bangun Pos Pertahanan di bawah kursor Anda.",
"build_port": "Membangun Pelabuhan",
"build_port_desc": "Bangun Pelabuhan di bawah kursor Anda.",
"build_warship": "Bangun Kapal Perang",
"build_warship_desc": "Bangun Kapal Perang di bawah kursor Anda.",
"build_missile_silo": "Bangun Silo Peluncur Rudal",
"build_missile_silo_desc": "Bangun Silo Peluncur Rudal di bawah kursor Anda.",
"build_sam_launcher": "Bangun Peluncur Rudal SAM",
"build_sam_launcher_desc": "Bangun Peluncur Rudal SAM di bawah kursor Anda.",
"build_atom_bomb": "Bangun Bom Atom",
"build_atom_bomb_desc": "Bangun Bom Atom di bawah kursor Anda.",
"build_hydrogen_bomb": "Bangun Bom Hidrogen",
"build_hydrogen_bomb_desc": "Membangun Bom Hidrogen di bawah kursor Anda.",
"build_mirv": "Membangun MIRV",
"build_mirv_desc": "Bangun MIRV di bawah kursor Anda.",
"menu_shortcuts": "Menu Pintasan",
"build_menu_modifier": "Pengubah Menu Pembangunan",
"build_menu_modifier_desc": "Tahan tombol ini sambil mengklik untuk membuka menu pembuatan.",
"emoji_menu_modifier": "Pengubah Menu Emoji",
"emoji_menu_modifier_desc": "Tahan tombol ini sambil mengklik untuk membuka menu emoji.",
"attack_ratio_controls": "Kontrol Rasio Serangan",
"attack_ratio_up": "Tingkatkan Rasio Serangan",
"attack_ratio_up_desc": "Tingkatkan Rasio Serangan sebesar 10%",
"attack_ratio_down": "Kurangi Rasio Serangan",
"attack_ratio_down_desc": "Kurangi Rasio Serangan sebesar 10%",
"attack_keybinds": "Tombol pintas untuk Serangan",
"boat_attack": "Serangan Kapal Pengangkut",
"boat_attack_desc": "Kirim serangan kapal ke petak di bawah kursor Anda.",
"ground_attack": "Serangan Darat",
"ground_attack_desc": "Kirim serangan darat ke petak di bawah kursor Anda.",
"swap_direction": "Tukar Arah Roket",
"swap_direction_desc": "Ubah arah peluncuran roket (atas/bawah).",
"zoom_controls": "Kontrol Zoom",
"zoom_out": "Perkecil tampilan",
"zoom_out_desc": "Perkecil tampilan peta",
"zoom_in": "Perbesar",
"zoom_in_desc": "Perbesar tampilan peta",
"camera_movement": "Pergerakan Kamera",
"center_camera": "Sorot kamera ke tengah",
"center_camera_desc": "Pusatkan kamera pada pemain",
"move_up": "Pindahkan kamera ke atas",
"move_up_desc": "Memindahkan kamera ke atas",
"move_left": "Pindahkan kamera ke kiri",
"move_left_desc": "Memindahkan kamera ke kiri",
"move_down": "Pindahkan kamera ke bawah",
"move_down_desc": "Memindahkan kamera ke bawah",
"move_right": "Pindahkan kemara ke kanan",
"move_right_desc": "Memindahkan kamera ke kanan",
"reset": "Reset",
"unbind": "Batalkan pengikatan tombol",
"on": "Hidup",
"off": "Mati",
"toggle_terrain": "Tampilkan / Sembunyikan Medan",
"exit_game_label": "Keluar Game",
"exit_game_info": "Kembali ke menu utama",
"background_music_volume": "Volume latar belakang musik",
"sound_effects_volume": "Volume Efek Suara",
"keybind_conflict_error": "Tombol {key} sudah terikat ke aksi lain."
},
"chat": {
"title": "Obrolan Cepat",
"to": "Dari {user}: {msg}",
"from": "Dari {user}: {msg}",
"category": "Kategori",
"phrase": "Frase",
"player": "Pemain",
"send": "Kirim",
"search": "Cari pemain...",
"build": "Ketik pesanmu...",
"cat": {
"help": "Bantuan",
"attack": "Serang",
"defend": "Bertahan",
"greet": "Salam",
"misc": "Lain-lain",
"warnings": "Peringatan"
},
"help": {
"troops": "Tolong berikan saya tentara!",
"troops_frontlines": "Kirim pasukan ke garis depan!",
"gold": "Tolong berikan saya emas!",
"no_attack": "Tolong jangan serang saya!",
"sorry_attack": "Maaf, Saya tidak bermaksud untuk menyerang Anda.",
"alliance": "Aliansi?",
"help_defend": "Bantu saya bertahan dari [P1]!",
"trade_partners": "Mari menjadi mitra dagang!"
},
"attack": {
"attack": "Serang [P1]!",
"mirv": "Luncurkan MIRV ke [P1]!",
"focus": "Fokus serangan pada [P1]!",
"finish": "Mari selesaikan [P1]!",
"build_warships": "Bangun Kapal-Kapal Perang!"
},
"defend": {
"defend": "Pertahankan [P1]!",
"defend_from": "Bertahan dari [P1]!",
"dont_attack": "Jangan serang [P1]!",
"ally": "[P1] adalah aliansi saya!",
"build_posts": "Bangun Pos Pertahanan!"
},
"greet": {
"hello": "Halo!",
"good_job": "Kerja bagus!",
"good_luck": "Semoga sukses!",
"have_fun": "Selamat bersenang-senang!",
"gg": "GG!",
"nice_to_meet": "Senang bertemu denganmu!",
"well_played": "Bagus Sekali!",
"hi_again": "Halo lagi!",
"bye": "Da!",
"thanks": "Terima kasih!",
"oops": "Ups, salah tombol!",
"trust_me": "Anda bisa percaya saya. Janji!",
"trust_broken": "Aku percaya padamu...",
"ruining_games": "Kamu bikin permainan kita berdua jadi kacau.",
"dont_do_that": "Jangan!",
"same_team": "Saya di pihak Anda!"
},
"misc": {
"go": "Ayo!",
"strategy": "Strategi yang mantap!",
"fun": "Permainan ini seru!",
"team_up": "Mari menyerang [P1] bersama-sama!",
"pr": "Kapan PR-ku akhirnya akan digabungkan...?",
"build_closer": "Bangun lebih dekat agar membuat jalur kereta!",
"coastline": "Tolong izinkan saya mendapatkan garis pantai."
},
"warnings": {
"strong": "[P1] kuat.",
"weak": "[P1] lemah.",
"mirv_soon": "[P1] akan meluncurkan MIRV segera!",
"number1_warning": "Pemain nomor 1 akan segera menang kecuali kita bekerja sama!",
"stalemate": "Mari berdamai. Ini jalan buntu, kita berdua akan kalah.",
"has_allies": "[P1] punya banyak sekutu.",
"no_allies": "[P1] tidak punya sekutu.",
"betrayed": "[P1] menkhianati sekutu dia!",
"betrayed_me": "[P1] menkhianati saya!",
"getting_big": "[P1] berkembang sangat cepat!",
"danger_base": "[P1] tidak terproteksi!",
"saving_for_mirv": "[P1] sedang menabung untuk meluncurkan MIRV.",
"mirv_ready": "[P1] punya cukup emas untuk meluncurkan MIRV!",
"snowballing": "[P1] berkembang terlalu cepat!",
"cheating": "[P1] curang!",
"stop_trading": "Stop berdangan dengan [P1]!"
}
},
"build_menu": {
"desc": {
"atom_bomb": "Ledakan kecil",
"hydrogen_bomb": "Ledakan dahsyat",
"mirv": "Ledakan Dahsyat, hanya menargetkan pemain yang dipilih",
"missile_silo": "Digunakan untuk meluncurkan nuklir",
"sam_launcher": "Penangkalan nuklir yang mendekat",
"warship": "Menangkap kapal dagang, menghancurkan kapal dan perahu.",
"port": "Mengirim kapal untuk mendapatkan emas",
"defense_post": "Meningkatkan pertahanan perbatasan",
"city": "Meningkatkan jumlah maksimal populasi",
"factory": "Membuat rel dan memunculkan kereta"
},
"not_enough_money": "Uang tidak cukup"
},
"win_modal": {
"support_openfront": "Dukung OpenFront!",
"territory_pattern": "Beli skin wilayah untuk bebas iklan!",
"died": "Anda meninggal",
"your_team": "Tim Anda menang!",
"other_team": "tim {team} menang!",
"you_won": "Anda Menang!",
"other_won": "{player} menang!",
"nation_won": "Negara {nation} menang!",
"exit": "Keluar Game",
"keep": "Terus Main",
"spectate": "Menonton",
"wishlist": "Wishlist di Steam!",
"ofm_winter": "Turnamen Musim Dingin OpenFront Masters!",
"ofm_winter_description": "Ikuti turnamen kompetitif dan bersaing melawan pemain terbaik",
"join_tournament": "Ikut Turnamen",
"join_discord": "Gabung Komunitas Discord Kami!",
"discord_description": "Terhubung dengan pemain lain, temukan fitur baru, dan menangkan hadiah!",
"join_server": "Bergabung dengan Server",
"youtube_tutorial": "Butuh bantuan?"
},
"leaderboard": {
"title": "Papan Peringkat",
"hide": "Sembunyikan",
"rank": "Peringkat",
"player": "Pemain",
"team": "Tim",
"owned": "Dimiliki",
"gold": "Emas",
"maxtroops": "Maksimal pasukan",
"launchers": "Peluncur",
"sams": "SAM-SAM",
"warships": "Kapal Perang",
"cities": "Kota-Kota",
"show_control": "Tampilkan Kontrol",
"show_units": "Tampilkan Unit"
},
"player_info_overlay": {
"type": "Jenis",
"bot": "Bot",
"nation": "Bangsa",
"player": "Pemain",
"team": "Tim",
"alliance_timeout": "Aliansi berakhir dalam",
"troops": "Pasukan",
"maxtroops": "Maksimal pasukan",
"a_troops": "Pasukan menyerang",
"gold": "Emas",
"ports": "Pelabuhan-Pelabuhan",
"cities": "Kota-kota",
"factories": "Pabrik-pabrik",
"missile_launchers": "Peluncur rudal",
"sams": "SAM",
"warships": "Kapal Perang",
"health": "Kesehatan",
"attitude": "Sikap",
"levels": "Tingkat",
"wilderness_title": "Alam Liar",
"irradiated_wilderness_title": "Hutan Belantara yang Terkena Radiasi"
},
"events_display": {
"retreating": "mundur",
"retaliate": "Membalas",
"boat": "Perahu",
"alliance_request_status": "{name} {status} permintaan aliansi Anda",
"alliance_accepted": "diterima",
"alliance_rejected": "ditolak",
"duration_second": "1 detik",
"betrayal_description": "Kamu memutus aliansi dengan {name}, menjadikanmu PENGKHIANAT ({malusPercent}% pengurangan pertahanan selama {durationText})",
"duration_seconds_plural": "{seconds} detik",
"betrayed_you": "{name} memutus aliansi dengan Anda",
"about_to_expire": "Aliansi Anda dengan {name} hampir berakhir!",
"alliance_expired": "Aliansi Anda dengan {name} berakhir",
"attack_request": "{name} meminta Anda untuk menyerang {target}",
"sent_emoji": "Dari {name}: {emoji}",
"renew_alliance": "Minta untuk memperpanjang",
"request_alliance": "{name} meminta aliansi!",
"focus": "Fokus",
"accept_alliance": "Setuju",
"reject_alliance": "Tolak",
"alliance_renewed": "Aliansi anda dengan {name} sudah di perpanjang",
"wants_to_renew_alliance": "{name} ingin memperpanjang aliansi",
"ignore": "Abaikan",
"unit_voluntarily_deleted": "Unit dihapus secara sukarela",
"betrayal_debuff_ends": "{time} detik tersisa hingga efek negatif pengkhianatan berakhir",
"attack_cancelled_retreat": "Penyerangan dibatalkan, {troops} pasukan terbunuh saat mundur",
"received_gold_from_captured_ship": "Menerima {gold} emas dari kapal yang di tawan dari {name}",
"received_gold_from_trade": "Menerima {gold} emas dari perdagangan dengan {name}",
"missile_intercepted": "Rudal dicegat {unit}",
"mirv_warheads_intercepted": "{count, plural, one {{count} hulu ledak MIRV berhasil dicegat} other {{count} hulu ledak MIRV berhasil dicegat}}",
"sent_troops_to_player": "Mengirim {troops} pasukan ke {name}",
"received_troops_from_player": "Menerima {troops} pasukan dari {name}",
"sent_gold_to_player": "Mengirim {gold} emas ke {name}",
"received_gold_from_player": "Menerima {gold} emas dari {name}",
"unit_captured_by_enemy": "{unit} Anda ditangkap oleh {name}",
"captured_enemy_unit": "Menangkap {unit} dari {name}",
"unit_destroyed": "{unit} Anda dihancurkan",
"no_boats_available": "Tidak ada kapal yang tersedia, maksmial {max}"
},
"unit_info_modal": {
"structure_info": "Informasi Struktur",
"unit_type_unknown": "Tidak Diketahui",
"close": "Keluar",
"cooldown": "Cooldown",
"type": "Jenis",
"upgrade": "Tingkatkan",
"level": "Tingkat"
},
"player_type": {
"player": "Pemain",
"nation": "Bangsa",
"bot": "Bot"
},
"relation": {
"hostile": "Berseteru",
"distrustful": "Tak dapat dipercaya",
"neutral": "Netral",
"friendly": "Ramah",
"default": "Default"
},
"control_panel": {
"gold": "Emas",
"troops": "Pasukan",
"attack_ratio": "Rasio Serangan"
},
"player_panel": {
"gold": "Emas",
"troops": "Pasukan",
"betrayals": "Pengkhianatan",
"traitor": "Pengkhianat",
"trading": "Perdagangan",
"active": "Aktif",
"stopped": "Berhenti",
"alliance_time_remaining": "Aliansi Berakhir Dalam",
"embargo": "Berhenti berdangan dengan Anda",
"nuke": "Nuklir dikirim oleh mereka kepada Anda",
"start_trade": "Mulai Berdagang",
"stop_trade": "Stop Berdagang",
"stop_trade_all": "Stop Berdagang degnan Semuanya",
"start_trade_all": "Mulai Berdagang dengan Semuanya",
"alliances": "Aliansi",
"flag": "Bendera",
"chat": "Chat",
"target": "Sasaran",
"break_alliance": "Rusak Aliansi",
"alliance": "Aliansi",
"send_alliance": "Kirim Proposal Aliansi",
"send_troops": "Kirim Pasukan",
"send_gold": "Kirim Emas",
"emotes": "Emoji",
"arc_up": "Lengkungan ke Atas",
"arc_down": "Lengkungan ke Bawah",
"flip_rocket_trajectory": "Balikkan lintasan roket"
},
"send_troops_modal": {
"title_with_name": "Kirim Pasukan ke {name}",
"available_tooltip": "Pasukan Anda yang tersedia saat ini",
"min_keep": "Minimal yang ditinggalkan",
"slider_tooltip": "{{percent}}% • {{amount}}",
"aria_slider": "Penggeser pasukan",
"capacity_note": "Penerima hanya dapat menerima {{amount}} saat ini."
},
"send_gold_modal": {
"title_with_name": "Kirim Emas ke {name}",
"available_tooltip": "Emas yang Anda miliki saat ini",
"aria_slider": "Penggeser jumlah",
"slider_tooltip": "{{percent}}% • {{amount}}"
},
"replay_panel": {
"replay_speed": "Kecepatan tanyangan ulang",
"game_speed": "Kecepatan Permainan",
"fastest_game_speed": "Maks"
},
"error_modal": {
"crashed": "Game berhenti / rusak!",
"connection_error": "Kesalahan koneksi!",
"paste_discord": "Silakan tempelkan teks berikut di laporan bug Anda di Discord:",
"copy_clipboard": "Salin ke papan klip",
"copied": "Tersalin!",
"failed_copy": "Gagal menyalin",
"spawn_failed": {
"title": "Kemunculan gagal",
"description": "Pemilihan titik awal otomatis gagal. Anda tidak dapat memainkan game ini."
},
"desync_notice": "Anda tidak tersinkronisasi dengan pemain lain. Apa yang Anda lihat mungkin berbeda dari pemain lain."
},
"performance_overlay": {
"reset": "Set ulang",
"copy_json_title": "Salin metrik kinerja saat ini sebagai JSON.",
"copy_clipboard": "Menyalin JSON",
"copied": "Tersalin!",
"failed_copy": "Gagal menyalin",
"fps": "FPS:",
"avg_60s": "Rata-rata (60d):",
"frame": "Bingkai:",
"tick_exec": "Eksekutif Tick:",
"tick_delay": "Penundaan Detik:",
"layers_header": "Lapisan (rata-rata / maksimum, diurutkan berdasarkan total waktu):"
},
"heads_up_message": {
"choose_spawn": "Pilih lokasi awal",
"random_spawn": "Kemunculan acak diaktifkan. Memilih lokasi awal untuk Anda...",
"singleplayer_game_paused": "Permainan dijeda",
"multiplayer_game_paused": "Permainan di tunda oleh Pembuat Lobi"
},
"territory_patterns": {
"title": "Tampilan",
"colors": "Warna-Warna",
"purchase": "Beli",
"show_only_owned": "Skin Saya",
"all_owned": "Semua skin sudah dimiliki! Silakan periksa kembali nanti untuk item baru.",
"not_logged_in": "Belum masuk",
"blocked": {
"login": "Anda harus login untuk mengakses skin ini.",
"purchase": "Beli skin ini untuk membukanya."
},
"pattern": {
"default": "Default"
},
"select_skin": "Pilih Skin",
"selected": "dipilih"
},
"flag_input": {
"title": "Pilih Bendera",
"button_title": "Pilih bendera!",
"search_flag": "Cari..."
},
"spawn_ad": {
"loading": "Memuat iklan..."
},
"auth": {
"login_required": "Masuk dibutuhkan untuk mengakses website ini.",
"redirecting": "Anda sedang diarahkan...",
"not_authorized": "Anda tidak punya izin untuk mengakses website ini.",
"contact_admin": "Jika Anda yakin melihat pesan ini karena kesalahan, silakan hubungi administrator situs web."
},
"radial_menu": {
"delete_unit_title": "Hapus Unit",
"delete_unit_description": "Klik untuk menghapus unit terdekat"
},
"discord_user_header": {
"avatar_alt": "Avatar"
},
"player_stats_table": {
"building_stats": "Statistik Bangunan",
"ship_arrivals": "Kedatangan Kapal",
"nuke_stats": "Statistik Nuklir",
"player_metrics": "Metrik Pemain",
"building": "Gedung",
"ship_type": "Jenis Kapal",
"weapon": "Senjata",
"built": "Bangun",
"destroyed": "Telah Hancur",
"captured": "Ditangkap",
"lost": "Kalah",
"hits": "Hits",
"launched": "Telah Diluncurkan",
"landed": "Mendarat",
"sent": "Terkirim",
"arrived": "Tiba",
"attack": "Serang",
"received": "Diterima",
"cancelled": "Dibatalkan",
"count": "Hitungan",
"gold": "Emas",
"workers": "Pekerja",
"war": "Perang",
"trade": "Perdagangan",
"steal": "Steal",
"unit": {
"city": "Kota",
"port": "Pelabuhan",
"defp": "Pos Pertahanan",
"saml": "Peluncur Rudal SAM",
"silo": "Silo Peluncur Rudal",
"wshp": "Kapal Perang",
"fact": "Pabrik",
"trade": "Kapal Perdagangan",
"trans": "Kapal Pengangkut",
"abomb": "Bom Atom",
"hbomb": "Bom Hidrogen",
"mirv": "MIRV",
"mirvw": "Hulu ledak MIRV"
}
},
"game_list": {
"recent_games": "Permainan Terbaru",
"game_id": "ID Permainan",
"mode": "Mode",
"mode_ffa": "Siapapun bisa bergabung",
"mode_team": "Tim",
"replay": "Tayangan ulang",
"details": "Detail",
"ranking": "Peringkat",
"started": "Dimulai",
"map": "Peta",
"difficulty": "Tingkat Kesulitan",
"type": "Jenis"
},
"player_stats_tree": {
"public": "Publik",
"private": "Tertutup",
"singleplayer": "Sendiri",
"mode": "Mode",
"stats_wins": "Jumlah Kemenangan",
"stats_losses": "Jumlah Kehilangan",
"stats_wlr": "Menang:Kalah Rasio",
"stats_games_played": "Permainan Dimainkan",
"mode_ffa": "Siapapun bisa bergabung",
"mode_team": "Tim",
"no_stats": "Tidak ada statistik yang tercatat untuk pilihan ini."
},
"matchmaking_button": {
"play_ranked": "Pertandingan 1v1 Ranked",
"description": "(ALPHA)",
"login_required": "Masuk untuk bermain peringkat!",
"must_login": "Anda harus masuk untuk bermain di pertandingan 1v1 Ranked."
}
}
+28 -10
View File
@@ -26,12 +26,15 @@
"title": "OpenFront (ALPHA)",
"join_discord": "Discord",
"login_discord": "Discordでログイン",
"sign_in": "サインイン",
"discord_avatar_alt": "Discordのプロフィールアバター",
"user_avatar_alt": "{username}のアバター",
"checking_login": "ログイン中...",
"logged_in": "ログイン中!",
"log_out": "ログアウト",
"create": "ロビーを作成",
"join": "ロビーに参加",
"solo": "1人のロビー",
"solo": "ソロ",
"instructions": "説明書",
"game_info": "ゲームの情報",
"wiki": "ウィキ",
@@ -42,7 +45,7 @@
"play": "プレイ",
"news": "お知らせ",
"store": "ストア",
"options": "設定",
"settings": "設定",
"keys": "キー設定",
"stats": "統計",
"account": "アカウント",
@@ -179,13 +182,8 @@
"title": "アカウント",
"connected_as": "接続されたアカウント",
"stats_overview": "統計の概要",
"save_progress_title": "進捗状況を保存する",
"save_progress_desc": "アカウントをリンクして、統計、ランク、コスメティックを安全に保ちます。",
"link_discord": "Discordアカウントを連携する",
"link_via_email_placeholder": "メールで連携する",
"link_button": "連携",
"log_out": "ログアウト",
"welcome_back": "おかえりなさい",
"sign_in_desc": "統計と進捗状況を保存するにはサインインしてください",
"or": "または",
"email_placeholder": "メールアドレスを入力してください",
@@ -237,7 +235,7 @@
"pirate": "海賊",
"conquered": "征服された",
"loading_game_info": "ゲームの統計を読み込んでいます",
"no_winner": "この試合の勝者はいなかった"
"no_winner": "このゲームは勝者なしで終了しました(または国家が勝利しました)"
},
"map": {
"map": "地図",
@@ -429,7 +427,7 @@
"factory": "工場"
},
"user_setting": {
"title": "ユーザー設定",
"title": "設定",
"tab_basic": "基本設定",
"tab_keybinds": "キーの割り当て",
"dark_mode_label": "ダークモード",
@@ -487,6 +485,11 @@
"build_hydrogen_bomb_desc": "選択した位置に水素爆弾を発射します。",
"build_mirv": "MIRVを発射",
"build_mirv_desc": "選択した位置にMIRVを発射します。",
"menu_shortcuts": "メニューのショートカット",
"build_menu_modifier": "ビルドメニューを表示",
"build_menu_modifier_desc": "ビルドメニューを開きます。",
"emoji_menu_modifier": "絵文字メニューを表示",
"emoji_menu_modifier_desc": "絵文字メニューを開きます。",
"attack_ratio_controls": "攻撃比率の調整",
"attack_ratio_up": "出撃兵力の割合を上げる",
"attack_ratio_up_desc": "出撃兵力を10%増加させる",
@@ -497,6 +500,8 @@
"boat_attack_desc": "カーソルの位置に合わせた土地にボート攻撃を送ります。",
"ground_attack": "ボート攻撃",
"ground_attack_desc": "カーソルの位置に合わせた土地にボート攻撃を送ります。",
"swap_direction": "核の撃つ向きを逆転",
"swap_direction_desc": "核の発射方向を切り替える(上方向/下方向)。",
"zoom_controls": "ズーム操作",
"zoom_out": "ズームアウト",
"zoom_out_desc": "マップを縮小します",
@@ -711,7 +716,20 @@
"wants_to_renew_alliance": "{name} が同盟の更新を提案しています",
"ignore": "無視",
"unit_voluntarily_deleted": "ユニットは自己破壊しました",
"betrayal_debuff_ends": "裏切りのデバフ終了まであと {time} 秒"
"betrayal_debuff_ends": "裏切りのデバフ終了まであと {time} 秒",
"attack_cancelled_retreat": "攻撃はキャンセルされました、撤退中に{troops} 人の兵士が死亡しました",
"received_gold_from_captured_ship": "{name} から捕獲した船から資金 {gold} を獲得しました",
"received_gold_from_trade": "{name} との貿易で資金 {gold}を獲得しました",
"missile_intercepted": "ミサイルが{unit}を迎撃しました",
"mirv_warheads_intercepted": "{count, plural, other {{count}発の MIRV 弾頭を迎撃}}",
"sent_troops_to_player": "{troops} の兵士を {name} に送信しました",
"received_troops_from_player": "{name}から{troops}の軍隊を受け取りました",
"sent_gold_to_player": "{gold} の資金を {name}に贈りました",
"received_gold_from_player": "{gold} から {name} の資金を受け取りました",
"unit_captured_by_enemy": "あなたの {unit} は {name}に鹵獲されました",
"captured_enemy_unit": "{unit}を{name}から奪い取りました",
"unit_destroyed": "あなたの{unit}は破壊されました",
"no_boats_available": "ボートをこれ以上出せません、最大は{max}隻までです"
},
"unit_info_modal": {
"structure_info": "建造物情報",
+6
View File
@@ -101,6 +101,12 @@
"en": "Hungarian",
"svg": "hu"
},
{
"code": "id",
"native": "Bahasa Indonesia",
"en": "Indonesian",
"svg": "id"
},
{
"code": "it",
"native": "Italiano",
+30 -12
View File
@@ -26,12 +26,15 @@
"title": "OpenFront (ALFA)",
"join_discord": "Discord",
"login_discord": "Login met Discord",
"sign_in": "Aanmelden",
"discord_avatar_alt": "Avatar Discord profiel",
"user_avatar_alt": "Avatar van {username}",
"checking_login": "Inlog controleren...",
"logged_in": "Ingelogd!",
"log_out": "Uitloggen",
"create": "Lobby aanmaken",
"join": "Lobby toetreden",
"solo": "Solo-lobby",
"solo": "Solo",
"instructions": "Instructies",
"game_info": "Spelinformatie",
"wiki": "Wiki",
@@ -42,7 +45,7 @@
"play": "Spelen",
"news": "Nieuws",
"store": "Winkel",
"options": "Opties",
"settings": "Instellingen",
"keys": "Sneltoetsen",
"stats": "Statistieken",
"account": "Account",
@@ -179,14 +182,9 @@
"title": "Account",
"connected_as": "Gekoppeld als",
"stats_overview": "Overzicht van statistieken",
"save_progress_title": "Sla je voortgang op",
"save_progress_desc": "Koppel je account om je statistieken, rang en cosmetica veilig te houden.",
"link_discord": "Discord-account koppelen",
"link_via_email_placeholder": "Koppel via e-mail",
"link_button": "Koppelen",
"log_out": "Uitloggen",
"welcome_back": "Welkom terug",
"sign_in_desc": "Log in om je statistieken en voortgang op te slaan",
"sign_in_desc": "Meld je aan om statistieken en voortgang op te slaan",
"or": "OF",
"email_placeholder": "Voer je e-mailadres in",
"get_magic_link": "Krijg Magische Link",
@@ -237,7 +235,7 @@
"pirate": "Kapen",
"conquered": "Veroverd",
"loading_game_info": "Spelstatistieken worden geladen",
"no_winner": "Dit spel eindigde zonder winnaar"
"no_winner": "Dit spel eindigde zonder winnaar (of een Natie won)"
},
"map": {
"map": "Kaart",
@@ -429,7 +427,7 @@
"factory": "Fabriek"
},
"user_setting": {
"title": "Gebruikersinstellingen",
"title": "Instellingen",
"tab_basic": "Basisinstellingen",
"tab_keybinds": "Sneltoetsen",
"dark_mode_label": "Donkere Modus",
@@ -487,6 +485,11 @@
"build_hydrogen_bomb_desc": "Bouw een Waterstofbom onder je cursor.",
"build_mirv": "Bouw MIRV",
"build_mirv_desc": "Bouw een MIRV onder je cursor.",
"menu_shortcuts": "Menu sneltoetsen",
"build_menu_modifier": "Bouwmenu",
"build_menu_modifier_desc": "Houdt deze toets ingedrukt terwijl je klikt, om het bouwmenu te openen.",
"emoji_menu_modifier": "Emoji-menu",
"emoji_menu_modifier_desc": "Houdt deze toets ingedrukt terwijl je klikt, om het emoji-menu te openen.",
"attack_ratio_controls": "Aanvalsverhouding-bediening",
"attack_ratio_up": "Verhoog Aanvalsverhouding",
"attack_ratio_up_desc": "Verhoog aanvalsverhouding met 10%",
@@ -497,6 +500,8 @@
"boat_attack_desc": "Stuur een bootaanval naar de plek onder je cursor.",
"ground_attack": "Grondaanval",
"ground_attack_desc": "Stuur een grondaanval naar de plek onder je cursor.",
"swap_direction": "Omdraaien boogrichting atoom- / waterstofbom",
"swap_direction_desc": "Draai boogrichting raket om (opwaarts/neerwaarts).",
"zoom_controls": "Zoombediening",
"zoom_out": "Uitzoomen",
"zoom_out_desc": "Kaart uitzoomen",
@@ -711,7 +716,20 @@
"wants_to_renew_alliance": "{name} wil jullie alliantie vernieuwen",
"ignore": "Negeren",
"unit_voluntarily_deleted": "Eenheid vrijwillig verwijderd",
"betrayal_debuff_ends": "Nog {time} seconden tot de verraad-verzwakking afloopt"
"betrayal_debuff_ends": "Nog {time} seconden tot de verraad-verzwakking afloopt",
"attack_cancelled_retreat": "Aanval geannuleerd, {troops} soldaten gedood tijdens terugtrekken",
"received_gold_from_captured_ship": "{gold} Goud ontvangen van veroverd schip van {name}",
"received_gold_from_trade": "{gold} Goud ontvangen van handel met {name}",
"missile_intercepted": "Raket onderschepte {unit}",
"mirv_warheads_intercepted": "{count, plural, one {{count} MIRV-kernkop onderschept} other {{count} MIRV-kernkoppen onderschept}}",
"sent_troops_to_player": "{troops} Troepen naar {name} gestuurd",
"received_troops_from_player": "{troops} Troepen ontvangen van {name}",
"sent_gold_to_player": "{gold} Goud verstuurd aan {name}",
"received_gold_from_player": "{gold} Goud ontvangen van {name}",
"unit_captured_by_enemy": "Jouw {unit} werd veroverd door {name}",
"captured_enemy_unit": "{unit} veroverd van {name}",
"unit_destroyed": "Jouw {unit} werd vernietigd",
"no_boats_available": "Geen boten beschikbaar, max. {max}"
},
"unit_info_modal": {
"structure_info": "Gebouw Info",
@@ -866,7 +884,7 @@
"ship_type": "Scheepstype",
"weapon": "Wapen",
"built": "Gebouwd",
"destroyed": "Verwoest",
"destroyed": "Vernietigd",
"captured": "Veroverd",
"lost": "Verloren",
"hits": "Treffers",
+218 -73
View File
@@ -7,6 +7,7 @@
},
"common": {
"close": "Закрыть",
"back": "Назад",
"available": "Доступно",
"preset_max": "Максимум",
"summary_send": "Перевод",
@@ -17,26 +18,42 @@
"cap_tooltip": "Оставшаяся ёмкость получателя",
"target_dead": "Цель устранена",
"target_dead_note": "Невозможно отправить ресурсы устранённому игроку.",
"none": "Ничего"
"none": "Ничего",
"copied": "Скопировано!",
"click_to_copy": "Нажмите, чтобы скопировать"
},
"main": {
"title": "OpenFront (АЛЬФА)",
"join_discord": "Discord",
"login_discord": "Войти через Discord",
"sign_in": "Войти",
"discord_avatar_alt": "Аватар профиля Discord",
"user_avatar_alt": "Аватар {username}",
"checking_login": "Проверка авторизации...",
"logged_in": "Вход выполнен!",
"log_out": "Выйти",
"create_lobby": "Создать лобби",
"join_lobby": "Присоединиться к лобби",
"single_player": "Одиночная игра",
"create": "Создать лобби",
"join": "Присоединиться к лобби",
"solo": "Соло",
"instructions": "Инструкции",
"game_info": "Информация об игре",
"wiki": "Вики",
"privacy_policy": "Политика конфиденциальности",
"terms_of_service": "Пользовательское соглашение",
"reddit": "Reddit"
"copyright": "© OpenFront™ и участники",
"reddit": "Reddit",
"play": "Играть",
"news": "Новости",
"store": "Магазин",
"settings": "Настройки",
"keys": "Клавиши",
"stats": "Статистика",
"account": "Аккаунт",
"help": "Помощь",
"menu": "Меню",
"pick_pattern": "Выберите узор!"
},
"news": {
"see_all_releases": "Посмотреть все выпуски",
"github_link": "на GitHub",
"title": "Список изменений"
},
@@ -66,7 +83,7 @@
"ui_events": "Панель событий",
"ui_events_desc": "Панель событий отображает последние события, запросы и сообщения быстрого чата. Некоторые примеры:",
"ui_events_alliance": "Союз — Запросы на заключение союзов можно принимать или отклонять. Союзники могут обмениваться ресурсами и войсками, но не могут атаковать друг друга. Нажатие на «Осмотреть» перемещает вид на игрока, который отправил запрос.",
"ui_events_attack": "Атаки — Отображение входящих и исходящих атак. Нажмите на сообщение, чтобы центровать камеру на атаку, ракету или лодку (транспортный корабль). Вы можете отозвать войска, нажав на красную кнопку «X». Это будет стоить жизней 25% войск, которые атакуют. Если вы отозвёте лодку, она вернётся в исходное местоположение и совершит атаку, если территория была захвачена. Ракеты нельзя отозвать после запуска.",
"ui_events_attack": "Атаки — Отображение входящих и исходящих атак. Нажмите на сообщение, чтобы центровать камеру на атаку, ракету или лодку (транспортный корабль). Вы можете отозвать войска, нажав на красную кнопку «X». Это будет стоить жизней 25% войск, которые атакуют. Если вы отозвёте судо, оно вернётся в исходное местоположение и совершит атаку, если территория была захвачена. Ракеты нельзя отозвать после запуска.",
"ui_events_quickchat": "Быстрый чат — Здесь вы можете увидеть отправленные и полученные сообщения. Отправьте сообщение игроку, нажав на значок быстрого чата в его меню информации.",
"ui_options": "Настройки",
"ui_options_desc": "Среди них можно найти следующие элементы:",
@@ -76,13 +93,15 @@
"option_pause": "Приостановить/Продолжить игру — Доступно только в режиме одиночной игры.",
"option_timer": "Таймер — Время, прошедшее с начала игры.",
"option_exit": "Кнопка выхода.",
"option_settings": "Настройки — Открыть меню настроек. В нём вы можете включить/выключить альтернативное представление, эмодзи, тёмный режим, ниндзя (режим скрытых/случайных имён) и взаимодействие левой кнопкой мыши.",
"option_settings": "Настройки — Открыть меню настроек. В нём вы можете переключить альтернативное представление, эмодзи, тёмный режим, ниндзя (режим скрытых/случайных имён) и взаимодействие левой кнопкой мыши.",
"radial_title": "Круговое меню",
"radial_desc": "Щелчок правой кнопкой мыши (или нажатие на мобильном устройстве) открывает круговое меню. Щёлкните правой кнопкой мыши за его пределами, чтобы закрыть его. С этого меню вы можете:",
"radial_build": "Открыть меню строительства.",
"radial_attack": "Открыть меню атаки.",
"radial_info": "Открыть меню информации.",
"radial_boat": "Отправить лодку (транспортный корабль) для атаки указанного места. Доступно только при наличии доступа к воде.",
"radial_boat": "Отправить судно (транспортный корабль) для атаки указанного места. Доступно только при наличии доступа к воде.",
"radial_donate_troops": "Пожертвовать войска, равные соотношению вашего ползунка атаки тому союзнику, на котором вы открыли круговое меню.",
"radial_donate_gold": "Открывает меню ползунка пожертвования золота для быстрой отправки золота союзникам.",
"radial_close": "Закрыть меню.",
"info_title": "Меню информации",
"info_enemy_desc": "Содержит такую информацию о выбранном игроке, как его имя, количество золота, войск, состояние торговли с вами, запущенные на вас ракеты и метку предателя. Прекращённая торговля значит, что вы не будете получать от игрока золото и он не будет отправлять вам золото через торговые корабли. Вручную (если игрок нажал «Прекратить торговлю», что длится до тех пор, пока вы оба не нажмёте «Начать торговлю») или автоматически (если вы предали ваш союз, что длится до тех пор, пока вы не станете союзниками снова или через 5 минут). В поле «Предатель» будет указана метка «Да» в течение 30 секунд после того, как игрок предал и напал на игрока, который был в союзе с ними. Значки ниже обозначают следующие взаимодействия:",
@@ -110,12 +129,12 @@
"build_port": "Порт",
"build_port_desc": "Может быть построен только вблизи воды. Позволяет строить военные корабли. Автоматически посылает торговые суда между портами вашей и других стран (за исключением случаев, когда торговля прекращена), выдавая золото обеим сторонам. Торговля прекращается автоматически если вы атакуете или атакуют вас. Возобновляется через 5 минут или если вы становитесь союзниками. Вы можете вручную управлять торговлей с помощью кнопок «Прекратить торговлю» и «Начать торговлю».",
"build_warship": "Военный корабль",
"build_warship_desc": "Патрулирует территорию, захватывая вражеские торговые корабли и разрушая вражеские лодки (транспортные корабли) и военные корабли. Появляется из ближайшего порта и патрулирует область, выбранную нажатием кнопкой мыши при создании. Вы можете управлять военными кораблями при помощью кнопки атаки (см. действие «Атака» в разделе «Горячие клавиши»): сначала нажмите на корабль, а затем — на новую область, к которой вы хотите переместиться.",
"build_warship_desc": "Патрулирует территорию, захватывая вражеские торговые корабли и разрушая вражеские суда (транспортные корабли) и военные корабли. Появляется из ближайшего порта и патрулирует область, выбранную нажатием кнопкой мыши при создании. Вы можете управлять военными кораблями при помощью кнопки атаки (см. действие «Атака» в разделе «Горячие клавиши»): сначала нажмите на корабль, а затем — на новую область, к которой вы хотите переместиться.",
"build_silo": "Ракетная шахта",
"build_silo_desc": "Позволяет запускать ракеты.",
"build_sam": усковая установка ЗРК",
"build_sam_desc": "Позволяет перехватывать вражеские ракеты в радиусе 100 пикселей. Имеет шанс 100% на попадание в атомную бомбу, 80% — в водородную бомбу и 50% — в отдельные боеголовки РГЧ ИН. Перезарядка ЗРК составляет 7,5 секунды.",
"build_atom": "Атомная бомба",
"build_sam": У ЗРК",
"build_sam_desc": "Может перехватывать вражеские ракеты в радиусе 100 пикселей. ЗРК имеет период перезарядки в 7,5 секунд.",
"build_atom": "Ядерная бомба",
"build_atom_desc": "Небольшая взрывная бомба, которая разрушает территорию, сооружения, корабли и лодки. Запускается из ближайшей ракетной шахты и наносит удар по области, выбранной нажатием кнопкой мыши.",
"build_hydrogen": "Водородная бомба",
"build_hydrogen_desc": "Большая взрывная бомба. Запускается из ближайшей ракетной шахты и наносит удар по области, выбранной нажатием кнопкой мыши.",
@@ -129,12 +148,15 @@
"icon_embargo": "Перечёркнутый знак доллара — Эмбарго. Этот игрок перестал торговать с вами; автоматически или вручную.",
"icon_request": "Конверт — Запрос на союз. Этот игрок отправил вам запрос на заключение союза.",
"info_enemy_panel": "Панель информации о враге",
"exit_confirmation": "Вы уверены, что хотите выйти из игры?"
"exit_confirmation": "Вы уверены, что хотите выйти из игры?",
"bomb_direction": "Траектория полёта ядерной/водородной бомбы"
},
"single_modal": {
"title": "Одиночная игра",
"title": "Соло",
"random_spawn": "Случайное появление",
"allow_alliances": "Разрешить союзы",
"toggle_achievements": "Переключение достижений",
"sign_in_for_achievements": "Войдите, чтобы получать достижения",
"options_title": "Настройки",
"bots": "Боты: ",
"bots_disabled": "Отключены",
@@ -145,6 +167,8 @@
"infinite_troops": "Неограниченные войска",
"compact_map": "Компактная карта",
"max_timer": "Продолжительность игры (минуты)",
"max_timer_placeholder": "Минуты",
"max_timer_invalid": "Пожалуйста, введите допустимое максимальное значение таймера (1–120 минут)",
"disable_nukes": "Отключить бомбы",
"enables_title": "Разрешения",
"start": "Начать игру"
@@ -156,10 +180,21 @@
},
"account_modal": {
"title": "Аккаунт",
"logged_in_as": "Вы вошли как {email}",
"connected_as": "Вы вошли как",
"stats_overview": "Обзор статистики",
"link_discord": "Привязать учётную запись Discord",
"log_out": "Выйти",
"sign_in_desc": "Войдите, чтобы сохранить статистику и прогресс",
"or": "ИЛИ",
"email_placeholder": "Введите свою почту",
"get_magic_link": "Получить волшебную ссылку",
"linked_account": "Вы вошли как {account_name}",
"fetching_account": "Получение информации об аккаунте...",
"logged_in_with_discord": "Вы вошли через Discord",
"recovery_email_sent": "Письмо для восстановления отправлено на {email}"
"recovery_email_sent": "Письмо для восстановления отправлено на {email}",
"not_found": "Не найдено",
"clear_session": "Очистить сессию",
"failed_to_send_recovery_email": "Не удалось отправить письмо для восстановления",
"enter_email_address": "Пожалуйста, введите адрес электронной почты"
},
"stats_modal": {
"title": "Статистика",
@@ -167,11 +202,40 @@
"loading": "Загрузка...",
"error": "Ошибка загрузки статистики кланов",
"no_stats": "Статистика кланов недоступна",
"no_data_yet": "Пока нет данных",
"clan": "Клан",
"games": "Игры",
"win_score": "Счёт побед",
"win_score_tooltip": "Взвешенные победы на основе участия клана и сложности матча",
"loss_score": "Счёт поражений",
"win_loss_ratio": "Победы/Поражения"
"loss_score_tooltip": "Взвешенные поражения на основе участия клана и сложности матча",
"win_loss_ratio": "Победы/Поражения",
"ratio": "Соотношение",
"rank": "Ранг",
"try_again": "Попробуйте ещё раз"
},
"game_info_modal": {
"title": "Информация об игре",
"players": "Игроки",
"atoms": "Ядерные бомбы",
"hydros": "Водородные бомбы",
"mirv": "РГЧ ИН",
"bombs": "Бомбы",
"total_gold": "Всего",
"all_gold": "Всё золото",
"trade": "Торговля",
"conquest_gold": "Захваченное золото игроков",
"stolen_gold": "Украдено с помощью военных кораблей",
"num_of_conquests": "Количество покорённых игроков",
"duration": "Продолжительность",
"survival_time": "Время выживания",
"war": "Война",
"economy": "Экономика",
"conquests": "Завоевания",
"pirate": "Пиратство",
"conquered": "Завоёвано",
"loading_game_info": "Загрузка игровой статистики",
"no_winner": "Эта игра закончилась без победителя (или выиграла нация)"
},
"map": {
"map": "Карта",
@@ -186,6 +250,7 @@
"asia": "Азия",
"mars": "Марс",
"southamerica": "Южная Америка",
"britanniaclassic": "Британия (классическая)",
"britannia": "Британия",
"gatewaytotheatlantic": "Гибралтарский пролив",
"australia": "Австралия",
@@ -206,22 +271,36 @@
"yenisei": "Енисей",
"pluto": "Плутон",
"montreal": "Монреаль",
"newyorkcity": "Нью-Йорк",
"achiran": "Акиран",
"baikalnukewars": "Байкал (ядерные войны)",
"fourislands": "Четыре острова",
"gulfofstlawrence": "Залив Св. Лоуренса",
"lisbon": "Лиссабон"
"lisbon": "Лиссабон",
"svalmel": "Свалмель",
"manicouagan": "Маникуаган",
"lemnos": "Лемнос",
"sierpinski": "Серпинский",
"twolakes": "Два озера",
"straitofhormuz": "Ормузский пролив",
"surrounded": "Окружение",
"didier": "Дидье",
"didierfrance": "Дидье (Франция)",
"amazonriver": "Река Амазонка"
},
"map_categories": {
"continental": "Континентальные",
"regional": "Региональные",
"fantasy": "Прочие"
"fantasy": "Прочие",
"special": "Особые",
"arcade": "Аркадные"
},
"map_component": {
"loading": "Загрузка..."
"loading": "Загрузка...",
"error": "Ошибка"
},
"private_lobby": {
"title": "Присоединиться к приватному лобби",
"title": "Присоединение к приватному лобби",
"enter_id": "Введите ID лобби",
"player": "Игрок",
"players": "Игрока(-ов)",
@@ -229,42 +308,55 @@
"checking": "Проверка лобби...",
"not_found": "Лобби не найдено. Пожалуйста, проверьте правильность ID и попробуйте ещё раз.",
"error": "Произошла ошибка. Пожалуйста, попробуйте ещё раз или обратитесь в службу поддержки.",
"joined_waiting": "Вы успешно присоединились! Ожидание начала игры...",
"version_mismatch": "Эта игра была создана в другой версии. Невозможно присоединиться."
"joined_waiting": "Лобби подключено! Ждём, пока хост начнёт игру...",
"version_mismatch": "Эта игра была создана в другой версии. Невозможно присоединиться.",
"disabled_units": "Отключённые сооружения"
},
"public_lobby": {
"join": "Присоединиться к следующей игре",
"waiting": "игрока(-ов) в ожидании",
"teams_Duos": "по 2 (дуо)",
"teams_Trios": "по 3 (трио)",
"teams_Quads": "по 4 (квады)",
"teams_Duos": "{team_count} команды по 2 (дуо)",
"teams_Trios": "{team_count} команды по 3 (трио)",
"teams_Quads": "{team_count} команды по 4 (квады)",
"waiting_for_players": "Ожидание игроков",
"starting_game": "Запуск игры…",
"teams_hvn": "Люди против наций",
"teams_hvn_detailed": "{num} людей против {num} наций",
"teams": "Команд: {num}",
"players_per_team": "по {num}"
"players_per_team": "по {num}",
"started": "Начато"
},
"matchmaking_modal": {
"title": "Подбор игроков",
"title": "Рейтинговый подбор 1v1 (АЛЬФА)",
"connecting": "Подключение к серверу подбора игроков...",
"searching": "Поиск игры...",
"waiting_for_game": "Ожидание начала игры..."
"waiting_for_game": "Ожидание начала игры...",
"elo": "Ваш ELO: {elo}"
},
"username": {
"enter_username": "Введите своё имя игрока",
"not_string": "Имя игрока должно быть строкой.",
"too_short": "Имя игрока должно содержать не менее {min} символов.",
"too_long": "Имя игрока не должно превышать {max} символов.",
"invalid_chars": "Имя игрока может содержать только латинские буквы, цифры, пробелы, подчёркивания и [квадратные скобки]."
"invalid_chars": "Имя игрока может содержать только латинские буквы, цифры, пробелы и подчёркивания.",
"tag": "ТЕГ",
"tag_too_short": "Тег клана должен состоять из 2–5 буквенно-цифровых символов.",
"tag_invalid_chars": "Тег клана может содержать только латинские буквы и цифры."
},
"host_modal": {
"title": "Приватное лобби",
"title": "Создание приватного лобби",
"label": "Приватный",
"mode": "Режим",
"team_count": "Количество команд",
"team_type": "Тип команды",
"options_title": "Настройки",
"bots": "Боты: ",
"bots_disabled": "Отключены",
"player_immunity_duration": "Продолжительность иммунитета в PVP (минуты)",
"nations": "Нации: ",
"disable_nations": "Отключить нации",
"max_timer": "Продолжительность игры (минуты)",
"mins_placeholder": "Минуты",
"instant_build": "Мгновенная стройка",
"infinite_gold": "Неограниченное золото",
"donate_gold": "Пожертвование золота",
@@ -283,7 +375,11 @@
"assigned_teams": "Распределённые команды",
"empty_teams": "Пустые команды",
"empty_team": "Пусто",
"remove_player": "Удалить {username}"
"remove_player": "Удалить {username}",
"teams_Duos": "Дуо (команды по 2)",
"teams_Trios": "Трио (команды по 3)",
"teams_Quads": "Квады (команды по 4)",
"teams_Humans Vs Nations": "Люди против наций"
},
"team_colors": {
"red": "Красный",
@@ -301,18 +397,22 @@
"code_license": "Код лицензирован согласно AGPL-3.0 (без гарантий)"
},
"difficulty": {
"difficulty": "Сложность",
"Easy": "Расслабленная",
"Medium": "Уравновешенная",
"Hard": "Напряжённая",
"Impossible": "Невозможная"
"difficulty": "Сложность наций",
"easy": "Легко",
"medium": "Средне",
"hard": "Сложно",
"impossible": "Невозможно"
},
"game_mode": {
"ffa": "Каждый против каждого (FFA)",
"teams": "Команды"
},
"public_game_modifier": {
"random_spawn": "Случайное появления",
"compact_map": "Компактная карта"
},
"select_lang": {
"title": "Выберите язык"
"title": "Выбор языка"
},
"unit_type": {
"city": "Город",
@@ -320,44 +420,47 @@
"port": "Порт",
"warship": "Военный корабль",
"missile_silo": "Ракетная шахта",
"sam_launcher": усковая установка ЗРК",
"atom_bomb": "Атомная бомба",
"sam_launcher": У ЗРК",
"atom_bomb": "Ядерная бомба",
"hydrogen_bomb": "Водородная бомба",
"mirv": "РГЧ ИН",
"factory": "Фабрика"
},
"user_setting": {
"title": "Пользовательские настройки",
"title": "Настройки",
"tab_basic": "Основные настройки",
"tab_keybinds": "Привязки клавиш",
"dark_mode_label": "Тёмный режим",
"dark_mode_desc": "Переключение внешнего вида сайта между светлой и тёмной темой",
"emojis_label": "Эмодзи",
"emojis_desc": "Включение/выключение видимости эмодзи в игре",
"emojis_desc": "Переключить видимость эмодзи в игре",
"alert_frame_label": "Рамка тревоги",
"alert_frame_desc": "Включить/выключить рамку тревоги. Когда включено, она будет отображаться, когда вас предают или атакуют по суше.",
"alert_frame_desc": "Переключить рамку тревоги. При включении рамка будет отображаться, когда вас предают или атакуют по суше.",
"special_effects_label": "Спецэффекты",
"special_effects_desc": "Включить/выключить спецэффекты. Отключите для улучшения производительности",
"special_effects_desc": "Переключить спецэффекты. Отключите для улучшения производительности",
"structure_sprites_label": "Спрайты структур",
"structure_sprites_desc": "Включение/выключение спрайтов структур",
"structure_sprites_desc": "Переключить спрайты структур",
"cursor_cost_label_label": "Цена постройки под указателем",
"cursor_cost_label_desc": "Показывать цену постройки под указателем",
"anonymous_names_label": "Скрытые имена",
"anonymous_names_desc": "Скрыть настоящие имена игроков и заменить их случайными.",
"lobby_id_visibility_label": "Скрытые ID лобби",
"lobby_id_visibility_desc": "Скрыть ID при создании приватного лобби",
"toggle_visibility": "Переключение видимости",
"left_click_label": "Открытие меню левой кнопкой мыши",
"left_click_desc": "ВКЛЮЧЕНО: щелчок левой кнопкой мыши открывает меню, атака совершается кнопкой с мечом. ВЫКЛЮЧЕНО: нажатие левой кнопкой мыши совершает атаку напрямую.",
"left_click_menu": "Меню на левую кнопку мыши",
"attack_ratio_label": "⚔️ Соотношение атаки",
"attack_ratio_desc": "Какой процент ваших войск отправлять в бой (1–100%)",
"troop_ratio_desc": "Настройте соотношение между войсками (для боя) и рабочими (для добычи золота) (1–100%)",
"territory_patterns_label": "🏳️ Скины территории",
"territory_patterns_desc": "Выберите, показывать ли скины территорий в игре",
"performance_overlay_label": "Оверлей производительности",
"performance_overlay_desc": "Включить/выключить оверлей производительности. Если включено, будет отображаться оверлей производительности. Нажмите Shift+D во время игры для включения/выключения.",
"performance_overlay_desc": "Переключить оверлей производительности. При включении будет показан оверлей производительности. Нажмите Shift+D во время игры для переключения.",
"easter_writing_speed_label": "Множитель скорости печати",
"easter_writing_speed_desc": "Настройте скорость, с которой вы делаете вид, что программируете (x1–x100)",
"easter_bug_count_label": "Количество багов",
"easter_bug_count_desc": "Количество багов, которое вы считаете приемлемым (0–1000, эмоционально)",
"press_a_key": "Нажмите клавишу",
"view_options": "Настройки просмотра",
"toggle_view": "Переключить представление",
"toggle_view_desc": "Альтернативное представление (рельеф/страны)",
@@ -374,24 +477,31 @@
"build_warship_desc": "Разместить военный корабль под указателем.",
"build_missile_silo": "Разместить ракетную шахту",
"build_missile_silo_desc": "Разместить ракетную шахту под указателем.",
"build_sam_launcher": "Разместить установку ЗРК",
"build_sam_launcher_desc": "Разместить установку ЗРК под указателем.",
"build_sam_launcher": "Разместить ПУ ЗРК",
"build_sam_launcher_desc": "Разместить ПУ ЗРК под указателем.",
"build_atom_bomb": "Разместить ядерную бомбу",
"build_atom_bomb_desc": "Разместить ядерную бомбу под указателем.",
"build_hydrogen_bomb": "Разместить водородную бомбу",
"build_hydrogen_bomb_desc": "Разместить водородную бомбу под указателем.",
"build_mirv": "Разместить РГЧ ИН",
"build_mirv_desc": "Разместить РГЧ ИН под указателем.",
"menu_shortcuts": "Горячие клавиши меню",
"build_menu_modifier": "Модификатор меню строительства",
"build_menu_modifier_desc": "Удерживайте эту клавишу при нажатии, чтобы открыть меню строительства.",
"emoji_menu_modifier": "Модификатор меню эмодзи",
"emoji_menu_modifier_desc": "Удерживайте эту клавишу при нажатии, чтобы открыть меню эмодзи.",
"attack_ratio_controls": "Управление соотношением атаки",
"attack_ratio_up": "Увеличить соотношение атаки",
"attack_ratio_up_desc": "Увеличить соотношение атаки на 10%",
"attack_ratio_down": "Уменьшить соотношение атаки",
"attack_ratio_down_desc": "Уменьшить соотношение атаки на 10%",
"attack_keybinds": "Привязки клавиш атаки",
"boat_attack": "Атака лодкой",
"boat_attack": "Атака судом",
"boat_attack_desc": "Отправить атаку лодкой на ячейку под указателем.",
"ground_attack": "Наземная атака",
"ground_attack_desc": "Отправить наземную атаку на ячейку под указателем.",
"swap_direction": "Поменять направление ракеты",
"swap_direction_desc": "Переключить направление ракеты (вверх/вниз).",
"zoom_controls": "Масштабирование",
"zoom_out": "Отдалить",
"zoom_out_desc": "Отдалить карту",
@@ -412,11 +522,12 @@
"unbind": "Освободить",
"on": "Включено",
"off": "Выключено",
"toggle_terrain": "Включение/выключение рельефа",
"toggle_terrain": "Переключить рельеф",
"exit_game_label": "Выйти из игры",
"exit_game_info": "Вернуться в главное меню",
"background_music_volume": "Громкость фоновой музыки",
"sound_effects_volume": "Громкость звуковых эффектов"
"sound_effects_volume": "Громкость звуковых эффектов",
"keybind_conflict_error": "Клавиша {key} уже привязана к другому действию."
},
"chat": {
"title": "Быстрый чат",
@@ -512,7 +623,7 @@
"hydrogen_bomb": "Большой взрыв",
"mirv": "Огромный взрыв, нацеленный только на выбранного игрока",
"missile_silo": "Используется для запуска ракет",
"sam_launcher": "Защищает от атомных ракет",
"sam_launcher": "Защищает от ядерных ударов",
"warship": "Захватывает торговые суда, уничтожает суда и лодки",
"port": "Отправляет торговые корабли для генерации золота",
"defense_post": "Укрепляет защиту ближайших границ",
@@ -529,6 +640,7 @@
"other_team": "Команда «{team}» победила!",
"you_won": "Вы победили!",
"other_won": "Игрок {player} победил!",
"nation_won": "Нация {nation} победила!",
"exit": "Выйти из игры",
"keep": "Продолжить игру",
"spectate": "Наблюдать",
@@ -537,7 +649,7 @@
"ofm_winter_description": "Присоединяйтесь к турниру и состязайтесь с лучшими игроками",
"join_tournament": "Присоединиться к турниру",
"join_discord": "Присоединяйтесь к нашему сообществу в Discord!",
"discord_description": "Связывайтесь с другими игроками, получайте новости и делитесь стратегиями",
"discord_description": "Связывайтесь с игроками, открывайте новые возможности и выигрывайте призы!",
"join_server": "Присоединиться к серверу",
"youtube_tutorial": "Нужна помощь?"
},
@@ -549,7 +661,7 @@
"team": "Команда",
"owned": "Территории",
"gold": "Золото",
"troops": "Войска",
"maxtroops": "Максимум войск",
"launchers": "Установки",
"sams": "ЗРК",
"warships": "Военные корабли",
@@ -565,6 +677,7 @@
"team": "Команда",
"alliance_timeout": "Конец союза через",
"troops": "Войска",
"maxtroops": "Максимум войск",
"a_troops": "Войска атаки",
"gold": "Золото",
"ports": "Порты",
@@ -575,12 +688,14 @@
"warships": "Военные корабли",
"health": "Здоровье",
"attitude": "Отношение",
"levels": "Уровни"
"levels": "Уровни",
"wilderness_title": "Пустошь",
"irradiated_wilderness_title": "Радиоактивная пустошь"
},
"events_display": {
"retreating": "отступает",
"retaliate": "Напасть в ответ",
"boat": "Лодка",
"boat": "Судно",
"alliance_request_status": "{name} {status} ваш запрос",
"alliance_accepted": "принял",
"alliance_rejected": "отклонил",
@@ -600,8 +715,21 @@
"alliance_renewed": "Ваш союз с {name} был продлён",
"wants_to_renew_alliance": "{name} хочет продлить ваш союз",
"ignore": "Игнорировать",
"unit_voluntarily_deleted": "Объект добровольно удалён",
"betrayal_debuff_ends": "Осталось {time} сек до окончания наказания предателя"
"unit_voluntarily_deleted": "Сооружение добровольно удалено",
"betrayal_debuff_ends": "Осталось {time} сек до окончания наказания предателя",
"attack_cancelled_retreat": "Атака отменена, {troops} солдат погибло во время отступления",
"received_gold_from_captured_ship": "Получено {gold} золота с корабля, захваченного у {name}",
"received_gold_from_trade": "Получено {gold} золота от торговли с {name}",
"missile_intercepted": "{unit} перехватывает ракету",
"mirv_warheads_intercepted": "{count, plural, one {Перехвачено {count} боеголовку РГЧ ИН} few {Перехвачено {count} боеголовки РГЧ ИН} many {Перехвачено {count} боеголовок РГЧ ИН} other {Перехвачено {count} боеголовок РГЧ ИН}}",
"sent_troops_to_player": "Отправлено {troops} войск к {name}",
"received_troops_from_player": "Получено {troops} войск от {name}",
"sent_gold_to_player": "Отправлено {gold} золота для {name}",
"received_gold_from_player": "Получено {gold} золота от {name}",
"unit_captured_by_enemy": "{name} захватывает ваше сооружение «{unit}»",
"captured_enemy_unit": "Захвачено сооружение «{unit}» у {name}",
"unit_destroyed": "Ваше сооружение «{unit}» было уничтожено",
"no_boats_available": "Нет доступных судов, максимум — {max}"
},
"unit_info_modal": {
"structure_info": "Информация о структуре",
@@ -653,7 +781,10 @@
"send_alliance": "Отправить предложение союза",
"send_troops": "Отправить войска",
"send_gold": "Отправить золото",
"emotes": "Эмодзи"
"emotes": "Эмодзи",
"arc_up": "Верхняя дуга",
"arc_down": "Нижняя дуга",
"flip_rocket_trajectory": "Отразить траекторию ракеты"
},
"send_troops_modal": {
"title_with_name": "Отправить войска игроку {name}",
@@ -702,20 +833,26 @@
},
"heads_up_message": {
"choose_spawn": "Выберите стартовое местоположение",
"random_spawn": "Случайное появление включено. Выбираем стартовое местоположение за вас..."
"random_spawn": "Случайное появление включено. Выбираем стартовое местоположение за вас...",
"singleplayer_game_paused": "Игра приостановлена",
"multiplayer_game_paused": "Игра приостановлена владельцем лобби"
},
"territory_patterns": {
"title": "Скины",
"colors": "Цвета",
"purchase": "Купить",
"show_only_owned": "Мои скины",
"all_owned": "Все узоры куплены! Возвращайтесь позже за новыми товарами.",
"not_logged_in": "Вы не авторизованы",
"blocked": {
"login": "Вы должны войти, чтобы получить доступ к этому скину.",
"purchase": "Купите этот скин, чтобы разблокировать его."
},
"pattern": {
"default": "По умолчанию"
}
},
"select_skin": "Выберете узор",
"selected": "выбрано"
},
"flag_input": {
"title": "Выберите флаг",
@@ -732,8 +869,8 @@
"contact_admin": "Если вы считаете, что видите это сообщение по ошибке, пожалуйста, свяжитесь с администратором сайта."
},
"radial_menu": {
"delete_unit_title": "Удалить объект",
"delete_unit_description": "Нажмите, чтобы удалить ближайший объект"
"delete_unit_title": "Удалить сооружение",
"delete_unit_description": "Нажмите, чтобы удалить ближайшее сооружение"
},
"discord_user_header": {
"avatar_alt": "Аватар"
@@ -743,7 +880,7 @@
"ship_arrivals": "Прибытия кораблей",
"nuke_stats": "Статистика бомбардирования",
"player_metrics": "Статистика игрока",
"building": "Строительство",
"building": "Сооружение",
"ship_type": "Тип корабля",
"weapon": "Оружие",
"built": "Построено",
@@ -762,19 +899,19 @@
"gold": "Золото",
"workers": "Рабочие",
"war": "Войны",
"trade": "Обмен",
"trade": "Торговля",
"steal": "Украдено",
"unit": {
"city": "Город",
"port": "Порт",
"defp": "Укрепление",
"saml": усковая установка ЗРК",
"saml": У ЗРК",
"silo": "Ракетная шахта",
"wshp": "Военный корабль",
"fact": "Фабрика",
"trade": "Торговый корабль",
"trans": "Транспортный корабль",
"abomb": "Атомная бомба",
"abomb": "Ядерная бомба",
"hbomb": "Водородная бомба",
"mirv": "РГЧ ИН",
"mirvw": "Боеголовка РГЧ ИН"
@@ -786,8 +923,9 @@
"mode": "Режим",
"mode_ffa": "Каждый против каждого",
"mode_team": "Команда",
"view": "Осмотреть",
"replay": "Повтор",
"details": "Подробности",
"ranking": "Рейтинг",
"started": "Начато",
"map": "Карта",
"difficulty": "Сложность",
@@ -796,13 +934,20 @@
"player_stats_tree": {
"public": "Публичный",
"private": "Приватный",
"singleplayer": "Одиночная игра",
"singleplayer": "Соло",
"mode": "Режим",
"stats_wins": "Победы",
"stats_losses": "Поражения",
"stats_wlr": "Соотношение побед:поражений",
"stats_games_played": "Игр сыграно",
"mode_ffa": "Все против всех",
"mode_team": "Команда"
"mode_team": "Команда",
"no_stats": "Нет данных для этой выборки."
},
"matchmaking_button": {
"play_ranked": "Рейтинговый подбор 1v1",
"description": "(АЛЬФА)",
"login_required": "Войдите, чтобы играть в рейтинговом режиме!",
"must_login": "Вы должны войти в систему, чтобы играть в рейтинговом режиме."
}
}
+413 -108
View File
@@ -6,27 +6,54 @@
"lang_code": "tr"
},
"common": {
"close": "Kapat"
"close": "Kapat",
"back": "Geri",
"available": "Mevcut",
"preset_max": "Maksimum",
"summary_send": "Gönder",
"summary_keep": "Sakla",
"cancel": "İptal Et",
"send": "Gönder",
"cap_label": "Limit",
"cap_tooltip": "Alıcının kalan kapasitesi",
"target_dead": "Hedef saf dışı kaldı",
"target_dead_note": "Saf dışı kalmış bir oyuncuya kaynak gönderemezsin.",
"none": "Hiçbiri",
"copied": "Kopyalandı!",
"click_to_copy": "Kopyalamak için tıkla"
},
"main": {
"title": "OpenFront (ALFA)",
"join_discord": "Discord'a katılın!",
"join_discord": "Discord",
"login_discord": "Discord'la giriş yap",
"sign_in": "Oturum Aç",
"discord_avatar_alt": "Discord profil avatarı",
"user_avatar_alt": "{username}'in avatarı",
"checking_login": "Giriş kontrol ediliyor...",
"logged_in": "Giriş yapıldı!",
"log_out": "Çıkış yap",
"create_lobby": "Lobi Oluştur",
"join_lobby": "Lobiye Katıl",
"single_player": "Tek Oyunculu",
"create": "Lobi Oluştur",
"join": "Lobiye Katıl",
"solo": "Tekli",
"instructions": "Rehber",
"how_to_play": "Nasıl Oynanır",
"advertise": "Reklam Ver",
"game_info": "Oyun bilgisi",
"wiki": "Wiki",
"privacy_policy": "Gizlilik Politikası",
"terms_of_service": "Hizmet Şartları"
"terms_of_service": "Hizmet Şartları",
"copyright": "© OpenFront™ ve Katkıda Bulunanlar",
"reddit": "Reddit",
"play": "Oyna",
"news": "Haberler",
"store": "Mağaza",
"settings": "Seçenekler",
"keys": "Tuşlar",
"stats": "İstatistikler",
"account": "Hesap",
"help": "Yardım",
"menu": "Menü",
"pick_pattern": "Desen seç!"
},
"news": {
"see_all_releases": "Tüm sürümleri gör",
"github_link": "GitHub'da",
"title": "Sürüm Notları"
},
@@ -57,7 +84,7 @@
"ui_events_desc": "Olay paneli en son olayları, istekleri ve Hızlı Sohbet mesajlarını görüntüler. Bazı örnekler şunlardır:",
"ui_events_alliance": "İttifak - İttifak istekleri kabul edilebilir veya reddedilebilir. Müttefikler kaynakları ve askerleri paylaşabilir, ancak birbirlerine saldıramazlar. Odaklan'a tıklamak görünümü isteği gönderen oyuncuya taşır.",
"ui_events_attack": "Saldırılar - Gelen saldırılar ve giden saldırılarınız gösterilir. Saldırı, nükleer veya Tekne (nakliye gemisi) üzerine görünümü ortalamak için mesaja tıklayın. Kırmızı X düğmesine tıklayarak askerleri geri çekebilirsiniz. Bu, saldıran askerlerinizin %25'inin hayatına mal olur. Bir Tekne saldırısını geri alırsanız, tekne başlangıç noktasına döner ve o zamandan beri toprak ele geçirildiyse orada saldırır. Nükleerler fırlatıldıktan sonra geri alınamaz.",
"ui_events_quickchat": "Hızlı Sohbet - Gönderilen ve alınan sohbet mesajlarını burada görebilirsiniz. Bilgi menüsündeki Hızlı Sohbet simgesine tıklayarak bir oyuncuya mesaj gönderin.",
"ui_events_quickchat": "Hızlı Sohbet - Burada gönderilen ve alınan sohbet mesajlarını görebilirsin. Oyuncunun Bilgi menüsündeki Hızlı Sohbet simgesine tıklayarak oyuncuya mesaj gönderebilirsin.",
"ui_options": "Seçenekler",
"ui_options_desc": "İçerisinde aşağıdaki öğeler bulunabilir:",
"ui_playeroverlay": "Oyuncu bilgi katmanı",
@@ -73,6 +100,8 @@
"radial_attack": "Saldırı menüsünü aç.",
"radial_info": "Bilgi menüsünü aç.",
"radial_boat": "Seçilen konuma saldırması için bir Tekne (nakliye gemisi) gönder. Sadece suya erişiminiz varsa kullanılabilir.",
"radial_donate_troops": "Saldırı oranı kaydırma çubuğundaki yüzdeye eşdeğer sayıda askerleri, radyal menüyü açtığınız müttefikinize bağışlayın.",
"radial_donate_gold": "Müttefiklerinize hızlıca altın yollayabilmeniz için altın bağışı kaydırma menüsünü açar.",
"radial_close": "Menüyü kapat.",
"info_title": "Bilgi menüsü",
"info_enemy_desc": "Seçilen oyuncunun adı, altını, askerleri, sizinle ticareti durdurmuş olup olmadığı, size gönderdiği nükleerler ve oyuncunun hain olup olmadığı gibi bilgileri içerir. Ticareti durdurmuş olmak, onlardan altın almayacağınız ve onlara ticaret gemileri aracılığıyla altın göndermeyeceğiniz anlamına gelir. Manuel olarak (oyuncu \"Ticareti durdur\"a tıklarsa, her ikiniz de \"Ticareti başlat\"a tıklayana kadar sürer) veya otomatik olarak (ittifakınıza ihanet ederseniz, tekrar müttefik olana kadar veya 5 dakika sonra kadar sürer). Hain, oyuncunun kendisiyle ittifak halinde olan bir oyuncuya ihanet edip saldırdığında 30 saniye boyunca Evet gösterir. Aşağıdaki simgeler şu etkileşimleri temsil eder:",
@@ -104,7 +133,7 @@
"build_silo": "Füze Silosu",
"build_silo_desc": "Füze fırlatmaya izin verir.",
"build_sam": "SAM Fırlatıcı",
"build_sam_desc": "100 piksel menzili içindeki düşman füzelerini engelleyebilir. Atom Bombası için %100, Hidrojen Bombası için %80 ve bireysel MIRV Savaş Başlıkları için %50 isabet şansı ile. SAM'ın 7.5 saniye bekleme süresi vardır.",
"build_sam_desc": "100 piksele kadar menzildeki düşman füzelerini önler. SAM 7,5 saniye bekleme süresine sahiptir.",
"build_atom": "Atom Bombası",
"build_atom_desc": "Bölgeyi, binaları, gemileri ve tekneleri yok eden küçük patlayıcı bomba. En yakın Füze Silosundan doğar ve ilk inşa etmek için tıkladığınız alana düşer.",
"build_hydrogen": "Hidrojen Bombası",
@@ -119,20 +148,27 @@
"icon_embargo": "Üstü çizili Dolar - Ambargo. Bu oyuncu sizinle ticareti otomatik veya manuel olarak durdurmuş.",
"icon_request": "Zarf - İttifak isteği. Bu oyuncu size ittifak isteği göndermiş.",
"info_enemy_panel": "Düşman bilgi paneli",
"exit_confirmation": "Oyundan çıkmak istediğine emin misin?"
"exit_confirmation": "Oyundan çıkmak istediğine emin misin?",
"bomb_direction": "Atom / Hidrojen bombası yay yönü"
},
"single_modal": {
"title": "Tek Oyunculu",
"title": "Tekli",
"random_spawn": "Rastgele doğma",
"allow_alliances": "İttifaklara izin ver",
"toggle_achievements": "Başarımları aç/kapat",
"sign_in_for_achievements": "Başarımlar için oturum aç",
"options_title": "Seçenekler",
"bots": "Botlar",
"bots_disabled": "Devre Dışı",
"nations": "Ülkeler: ",
"disable_nations": "Ulusları Devre Dışı Bırak",
"instant_build": "Anında İnşa",
"infinite_gold": "Sınırsız Altın",
"donate_gold": "Altın bağışla",
"infinite_troops": "Sınırsız Asker",
"donate_troops": "Asker bağışla",
"compact_map": "Sıkıştırılmış Harita",
"max_timer": "Oyun süresi (dakika)",
"max_timer_placeholder": "Dakika",
"max_timer_invalid": "Lütfen geçerli bir maksimum zamanlayıcı değeri girin (1-120 dakika)",
"disable_nukes": "Nükleerleri Devre Dışı Bırak",
"enables_title": "Ayarları Etkinleştir",
"start": "Oyunu Başlat"
@@ -144,9 +180,62 @@
},
"account_modal": {
"title": "Hesap",
"logged_in_as": "{email} olarak oturum açıldı",
"logged_in_with_discord": "Discord'la giriş yapıldı",
"recovery_email_sent": "Kurtarma e-postası {email}'a gönderildi"
"connected_as": "Şu olarak bağlandı",
"stats_overview": "İstatistiklere Genel Bakış",
"link_discord": "Discord Hesabı Bağla",
"log_out": "Çıkış Yap",
"sign_in_desc": "İstatistiklerini ve ilerlemeni kaydetmek için oturum aç",
"or": "YA DA",
"email_placeholder": "E-posta adresini gir",
"get_magic_link": "Sihirli Linkini Al",
"linked_account": "{account_name} olarak giriş yapıldı",
"fetching_account": "Hesap bilgisi alınıyor...",
"recovery_email_sent": "Kurtarma e-postası {email}'a gönderildi",
"not_found": "Bulunamadı",
"clear_session": "Oturumu Temizle",
"failed_to_send_recovery_email": "Kurtarma e-postası gönderimi başarısız",
"enter_email_address": "Lütfen bir e-posta adresi giriniz"
},
"stats_modal": {
"title": "İstatistikler",
"clan_stats": "Klan İstatistikleri",
"loading": "Yükleniyor...",
"error": "Klan istatistikleri yüklenirken hata",
"no_stats": "Klan istatistikleri mevcut değil",
"no_data_yet": "Henüz Veri Yok",
"clan": "Klan",
"games": "Oyunlar",
"win_score": "Zafer Skoru",
"win_score_tooltip": "Klan katılımı ve maç zorluğuna göre ağırlıklı kazançlar",
"loss_score": "Yenilgi Skoru",
"loss_score_tooltip": "Klan katılımı ve maç zorluğuna göre ağırlıklı kayıplar",
"win_loss_ratio": "Zafer/Yenilgi",
"ratio": "Oran",
"rank": "Sıra",
"try_again": "Tekrar Dene"
},
"game_info_modal": {
"title": "Oyun bilgisi",
"players": "Oyuncular",
"atoms": "Atomlar",
"hydros": "Hidrojenler",
"mirv": "MIRV",
"bombs": "Bombalar",
"total_gold": "Toplam",
"all_gold": "Tüm altın",
"trade": "Ticaret",
"conquest_gold": "Fethedilen oyuncu altını",
"stolen_gold": "Savaş gemileriyle çalınan",
"num_of_conquests": "Fethedilen oyuncu sayısı",
"duration": "Süre",
"survival_time": "Hayatta kalma süresi",
"war": "Savaş",
"economy": "Ekonomi",
"conquests": "Fetihler",
"pirate": "Korsan",
"conquered": "Fethedildi",
"loading_game_info": "Oyun verileri yükleniyor",
"no_winner": "Bu oyun kazanan olmadan bitti (ya da bir Ülke kazanmadan)"
},
"map": {
"map": "Harita",
@@ -161,6 +250,7 @@
"asia": "Asya",
"mars": "Mars",
"southamerica": "Güney Amerika",
"britanniaclassic": "Britanya (Klasik)",
"britannia": "Britanya",
"gatewaytotheatlantic": "Atlantik'e Açılan Kapı",
"australia": "Avustralya",
@@ -177,16 +267,37 @@
"halkidiki": "Halkidiki",
"straitofgibraltar": "Cebelitarık Boğazı",
"italia": "İtalya",
"japan": "Japonya",
"yenisei": "Yenisey",
"pluto": "Plüto"
"pluto": "Plüto",
"montreal": "Montreal",
"newyorkcity": "New York Şehri",
"achiran": "Achiran",
"baikalnukewars": "Baykal (Nükleer Savaşlar)",
"fourislands": "Dört Adalar",
"gulfofstlawrence": "St. Lawrence Körfezi",
"lisbon": "Lizbon",
"svalmel": "Svalmel",
"manicouagan": "Manicouagan",
"lemnos": "Lemnos",
"sierpinski": "Sierpinski",
"twolakes": "İki Nehir",
"straitofhormuz": "Hürmüz Boğazı",
"surrounded": "Etrafı Çevrili",
"didier": "Didier",
"didierfrance": "Didier (Fransa)",
"amazonriver": "Amazon Nehri"
},
"map_categories": {
"continental": "Kıtasal",
"regional": "Bölgesel",
"fantasy": "Diğer"
"fantasy": "Diğer",
"special": "Özel",
"arcade": "Arcade"
},
"map_component": {
"loading": "Yükleniyor..."
"loading": "Yükleniyor...",
"error": "Hata"
},
"private_lobby": {
"title": "Özel Lobiye Katıl",
@@ -196,43 +307,79 @@
"join_lobby": "Lobiye katıl",
"checking": "Lobi kontrol ediliyor...",
"not_found": "Lobi bulunamadı. Lütfen ID'yi kontrol edip tekrar deneyin.",
"error": "Bir hata oluştu. Lütfen tekrar deneyin.",
"joined_waiting": "Başarıyla katıldınız! Oyunun başlaması bekleniyor..."
"error": "Bir hata oluştu. Lütfen tekrar deneyin ya da destek ile iletişime geçin.",
"joined_waiting": "Lobiye katıldınız! Oda sahibinin başlatması bekleniyor...",
"version_mismatch": "Bu lobi oyunun başka bir sürümü ile oluşturuldu. Katılamazsın.",
"disabled_units": "Devre Dışı Bırakılmış Birimler"
},
"public_lobby": {
"join": "Sıradaki Oyuna Katıl",
"waiting": "oyuncu bekliyor",
"teams_Duos": "İkili (2'li takımlar)",
"teams_Trios": "Üçlü (3'lü takımlar)",
"teams_Quads": "Dörtlü (4'lü takımlar)",
"teams": "{num} takımlar"
"teams_Duos": "{team_count} adet 2 kişilik takımlar (İkili)",
"teams_Trios": "{team_count} adet 3 kişilik takımlar (Üçlü)",
"teams_Quads": "{team_count} adet 4 kişilik takımlar (Dörtlü)",
"waiting_for_players": "Oyuncular bekleniyor",
"starting_game": "Oyun başlatılıyor…",
"teams_hvn": "İnsanlar vs Ülkeler",
"teams_hvn_detailed": "{num} İnsan vs {num} Ülke",
"teams": "{num} takımlar",
"players_per_team": "{num}",
"started": "Başladı"
},
"matchmaking_modal": {
"title": "1v1 Aşamalı Eşleştirme (ALFA)",
"connecting": "Maç bulma sunucusuna bağlanılıyor...",
"searching": "Oyun aranıyor...",
"waiting_for_game": "Oyunun başlaması bekleniyor...",
"elo": "Senin ELO'n: {elo}"
},
"username": {
"enter_username": "Kullanıcı adınızı girin",
"not_string": "Kullanıcı adı bir metin olmalıdır.",
"too_short": "Kullanıcı adı en az {min} karakter uzunluğunda olmalıdır.",
"too_long": "Kullanıcı adı {max} karakteri geçmemelidir.",
"invalid_chars": "Kullanıcı adı yalnızca harf, rakam, boşluk, alt çizgi ve [köşeli parantez] içerebilir."
"invalid_chars": "Kullanıcı adı sadece harfler, sayılar, boşluklar ve alt çizgi içerebilir.",
"tag": "ETİKET",
"tag_too_short": "Klan etiketi 2-5 harf arasında olmalı.",
"tag_invalid_chars": "Klan etiketi sadece harf ve sayı içerebilir."
},
"host_modal": {
"title": "Özel Lobi",
"title": "Özel Lobi Oluştur",
"label": "Özel",
"mode": "Mod",
"team_count": "Takım Sayısı",
"team_type": "Takım Türü",
"options_title": "Seçenekler",
"bots": "Botları:",
"bots_disabled": "Devre Dışı",
"player_immunity_duration": "PVP bağışıklık süresi (dakika)",
"nations": "Ülkeler: ",
"disable_nations": "Ulusları Devre Dışı Bırak",
"max_timer": "Oyun süresi (dakika)",
"mins_placeholder": "Dakika",
"instant_build": "Anında İnşa",
"infinite_gold": "Sınırsız Altın",
"donate_gold": "Altın bağışla",
"infinite_troops": "Sınırsız Asker",
"donate_troops": "Asker bağışla",
"compact_map": "Sıkıştırılmış Harita",
"enables_title": "Ayarları Etkinleştir",
"player": "Oyuncu",
"players": "Oyuncular",
"nation_players": "Ülkeler",
"nation_player": "Millet",
"waiting": "Oyuncular bekleniyor...",
"random_spawn": "Rastgele doğma",
"start": "Oyunu Başlat",
"host_badge": "Host"
"host_badge": "Host",
"assigned_teams": "Atanmış Takımlar",
"empty_teams": "Boş Takımlar",
"empty_team": "Boş",
"remove_player": "{username}'i Kaldır",
"teams_Duos": "İkili (2 kişilik takımlar)",
"teams_Trios": "Üçlü (3 kişilik takımlar)",
"teams_Quads": "Dörtlü (4 kişilik takımlar)",
"teams_Humans Vs Nations": "İnsanlar vs Ülkeler"
},
"team_colors": {
"red": "Kırmızı",
@@ -246,19 +393,24 @@
},
"game_starting_modal": {
"title": "Oyun Başlıyor...",
"desc": "Oyun başlamak üzere hazırlanıyor. Lütfen bekleyin."
"credits": "Atıflar",
"code_license": "Kod AGPL-3.0 altında lisanslanmıştır (garanti yok)"
},
"difficulty": {
"difficulty": "Zorluk",
"Easy": "Rahat",
"Medium": "Dengeli",
"Hard": "Yoğun",
"Impossible": "İmkansız"
"difficulty": "Ülke Zorluğu",
"easy": "Kolay",
"medium": "Orta",
"hard": "Zor",
"impossible": "İmkansız"
},
"game_mode": {
"ffa": "Herkes Tek",
"teams": "Takımlar"
},
"public_game_modifier": {
"random_spawn": "Rastgele Doğma",
"compact_map": "Sıkıştırılmış Harita"
},
"select_lang": {
"title": "Dil seç"
},
@@ -275,52 +427,69 @@
"factory": "Fabrika"
},
"user_setting": {
"title": "Kullanıcı Ayarları",
"title": "Seçenekler",
"tab_basic": "Temel Ayarlar",
"tab_keybinds": "Kısayollar",
"dark_mode_label": "Karanlık Tema",
"dark_mode_desc": "Sitenin görünümünü açık ve koyu tema arasında değiştir",
"dark_mode_enabled": "Karanlık tema etkinleştirildi",
"light_mode_enabled": "Açık tema etkinleştirildi",
"emojis_label": "Emojiler",
"emojis_visible": "Emojiler görünüyor",
"emojis_hidden": "Emojiler gizleniyor",
"emojis_desc": "Emojilerin oyunda gösterilip gösterilmeyeceğini değiştir",
"alert_frame_label": "Uyarı Çerçevesi",
"alert_frame_desc": "Uyarı çerçevesini açın/kapatın. Etkinleştirildiğinde, ihanete uğradığınızda çerçeve gözükür.",
"alert_frame_desc": "Uyarı çerçevesini etkinleştir. Etkinleştirildiğinde, ihanete uğradığınızda veya kara saldırısına uğradığınızda çerçeve gözükür.",
"special_effects_label": "Özel efektler",
"special_effects_desc": "Özel efektleri aç/kapat. Performansı artırmak için devre dışı bırakın",
"special_effects_enabled": "Özel efektler açık",
"special_effects_disabled": "Özel efektler kapalı",
"structure_sprites_label": "Yapı Simgeleri",
"structure_sprites_desc": "Yapı simgelerini aç/kapat",
"structure_sprites_enabled": "Yapı Simgeleri etkinleştirildi",
"structure_sprites_disabled": "Yapı Simgeleri devre dışı bırakıldı",
"cursor_cost_label_label": "İmleç Yapım Maliyeti",
"cursor_cost_label_desc": "Oluşturma imleci simgesinin altında bir maliyet hapı göster",
"anonymous_names_label": "Adları Gizle",
"anonymous_names_desc": "Gerçek oyuncu isimlerini ekranında rastgele isimlerle gizle.",
"anonymous_names_enabled": "Adları gizleme açık",
"lobby_id_visibility_label": "Gizli Lobi Kimlikleri",
"lobby_id_visibility_desc": "Özel lobi oluştururken lobi kimliğini gizle",
"real_names_shown": "Gerçek adlar görünüyor",
"toggle_visibility": "Görünürlüğü Aç/Kapat",
"left_click_label": "Menüyü Açmak için Sol Tık",
"left_click_desc": "AÇIK olduğunda, sol tıklama menüyü açar ve kılıç düğmesi saldırır. KAPALI olduğunda, sol tıklama doğrudan saldırır.",
"left_click_menu": "Sol Tık Menüsü",
"left_click_opens_menu": "Sol tık menüyü açar",
"right_click_opens_menu": "Sağ tık menüyü açar",
"attack_ratio_label": "⚔️ Saldırı Oranı",
"attack_ratio_desc": "Bir saldırıda birliklerinin yüzde kaçını göndereceksin (%1-100)",
"troop_ratio_desc": "Askerler (savaş için) ve işçiler (altın üretimi için) arasındaki dengeyi ayarlayın (%1100)",
"territory_patterns_label": "🏳️ Bölge Desenleri",
"territory_patterns_desc": "Oyunda bölge desenlerinin gösterilip gösterilmeyeceğini seç",
"territory_patterns_label": "🏳️Bölge Desenleri",
"territory_patterns_desc": "Bölge desenlerinin oyunda gösterip gösterilmeyeceğini seç",
"performance_overlay_label": "Performans Katmanı",
"performance_overlay_desc": "Performans katmanını açın veya kapatın. Etkinleştirildiğinde, performans katmanı görüntülenir. Oyun sırasında Shift-D tuşlarına basarak açıp kapatabilirsiniz.",
"easter_writing_speed_label": "Yazma Hızı Çarpanı",
"easter_writing_speed_desc": "Kod yazıyormuş gibi yapma hızınızı ayarlayın (x1x100)",
"easter_bug_count_label": "Hata Sayısı",
"easter_bug_count_desc": "Kabul edebileceğiniz hata sayısı (01000, duygusal olarak)",
"press_a_key": "Bir tuşa bas",
"view_options": "Görüntü Seçenekleri",
"toggle_view": "Görüntüyü Değiştir",
"toggle_view_desc": "Alternatif görünüm (arazi/ülkeler)",
"build_controls": "İnşaat Kontrolleri",
"build_city": "Şehir İnşa Et",
"build_city_desc": "İmlecinin altına bir Şehir inşa et.",
"build_factory": "Fabrika İnşa Et",
"build_factory_desc": "İmlecinin altına bir Fabrika inşa et.",
"build_defense_post": "Bir Savunma Karakolu İnşa Et",
"build_defense_post_desc": "İmlecinin altına bir Savunma Karakolu kur.",
"build_port": "Liman İnşa Et",
"build_port_desc": "İmlecinin altına bir Liman inşa et.",
"build_warship": "Savaş Gemisi İnşa Et",
"build_warship_desc": "İmlecinin altına bir Savaş Gemisi inşa et.",
"build_missile_silo": "Füze Silosu İnşa Et",
"build_missile_silo_desc": "İmlecinin altına bir Füze silosu inşa et.",
"build_sam_launcher": "SAM Fırlatıcı İnşa Et",
"build_sam_launcher_desc": "İmlecinin altına bir SAM Fırlatıcı inşa et.",
"build_atom_bomb": "Atom Bombası İnşa Et",
"build_atom_bomb_desc": "İmlecinin altına bir Atom Bombası inşa et.",
"build_hydrogen_bomb": "Hidrojen Bombası İnşa Et",
"build_hydrogen_bomb_desc": "İmlecinin altına bir Hidrojen Bombası inşa et.",
"build_mirv": "MIRV İnşa Et",
"build_mirv_desc": "İmlecinin altına MIRV inşa et.",
"menu_shortcuts": "Menü Kısayolları",
"build_menu_modifier": "Yapı Menüsü Değiştiricisi",
"build_menu_modifier_desc": "Yapı menüsünü açmak için tıklarken bu tuşa basılı tut.",
"emoji_menu_modifier": "Emoji Menüsü Değiştiricisi",
"emoji_menu_modifier_desc": "Emoji menüsünü açmak için tıklarken bu tuşa basılı tut.",
"attack_ratio_controls": "Saldırı Oranı Kontrolleri",
"attack_ratio_up": "Saldırı Oranını Artır",
"attack_ratio_up_desc": "Saldırı oranını %10 artır",
@@ -331,6 +500,8 @@
"boat_attack_desc": "İmlecinizin altındaki kareye tekne saldırısı gönder.",
"ground_attack": "Kara Saldırısı",
"ground_attack_desc": "İmlecinin altındaki kareye kara saldırısı gönderir.",
"swap_direction": "Roket Yönünü Değiştir",
"swap_direction_desc": "Roket fırlatma yönünü aç/kapat (yukarı/aşağı).",
"zoom_controls": "Yakınlaştırma Kontrolleri",
"zoom_out": "Uzaklaştır",
"zoom_out_desc": "Haritayı uzaklaştır",
@@ -352,10 +523,11 @@
"on": "Açık",
"off": "Kapalı",
"toggle_terrain": "Araziyi Göster",
"terrain_enabled": "Arazi görünümü etkinleştirildi",
"terrain_disabled": "Arazi görünümü kapatıldı",
"exit_game_label": "Oyundan Çık",
"exit_game_info": "Ana menüye dön"
"exit_game_info": "Ana menüye dön",
"background_music_volume": "Arkaplan Müziği Sesi",
"sound_effects_volume": "Ses Efektleri Sesi",
"keybind_conflict_error": "{key} tuşu zaten başka bir eyleme atanmış."
},
"chat": {
"title": "Hızlı Sohbet",
@@ -462,15 +634,24 @@
},
"win_modal": {
"support_openfront": "OpenFront'u Destekle!",
"territory_pattern": "OpenFront'u desteklemek için bölge deseni satın al!",
"territory_pattern": "Bir toprak kaplaması alarak reklamlardan kurtul!",
"died": "Öldün",
"your_team": "Takımınız kazandı!",
"other_team": "{team} takımı kazandı!",
"you_won": "Kazandın!",
"other_won": "{player} kazandı!",
"nation_won": "{nation} ülkesi kazandı!",
"exit": "Oyundan Çık",
"keep": "Oynamaya Devam Et",
"wishlist": "Steam'de İstek Listesine Ekle!"
"spectate": "İzle",
"wishlist": "Steam'de İstek Listesine Ekle!",
"ofm_winter": "OpenFront Masters Kış Turnuvası!",
"ofm_winter_description": "Rekabetçi turnuvaya katıl ve en iyi oyunculara karşı mücadele et",
"join_tournament": "Turnuvaya Katıl",
"join_discord": "Discord Topluluğumuza Katıl!",
"discord_description": "Oyuncularla bağlantı kurun, yeni özellikleri keşfedin ve ödüller kazanın!",
"join_server": "Sunucuya Katıl",
"youtube_tutorial": "Yardım lazım mı?"
},
"leaderboard": {
"title": "Lider Tablosu",
@@ -480,7 +661,7 @@
"team": "Takım",
"owned": "Sahip Olunan",
"gold": "Altın",
"troops": "Askerler",
"maxtroops": "Maks birlikler",
"launchers": "Fırlatıcılar",
"sams": "SAM'ler",
"warships": "Savaş Gemileri",
@@ -494,7 +675,9 @@
"nation": "Ulus",
"player": "Oyuncu",
"team": "Takım",
"d_troops": "Savunma birliği",
"alliance_timeout": "İttifak şu sürede bitiyor",
"troops": "Birlikler",
"maxtroops": "Maks birlikler",
"a_troops": "Saldırı birliği",
"gold": "Altın",
"ports": "Limanlar",
@@ -505,10 +688,13 @@
"warships": "Savaş gemileri",
"health": "Sağlık",
"attitude": "Tutum",
"levels": "Seviyeler"
"levels": "Seviyeler",
"wilderness_title": "Vahşi Doğa",
"irradiated_wilderness_title": "Radyoaktif Vahşi Doğa"
},
"events_display": {
"retreating": "geri çekiliyor",
"retaliate": "Karşılık Ver",
"boat": "Tekne",
"alliance_request_status": "{name} {status} ittifak isteğiniz",
"alliance_accepted": "kabul edildi",
@@ -527,8 +713,23 @@
"accept_alliance": "Kabul et",
"reject_alliance": "Reddet",
"alliance_renewed": "{name} ile ittifakın yenilendi",
"wants_to_renew_alliance": "{name} ittifakı yenilemek istiyor",
"ignore": "Yoksay",
"unit_voluntarily_deleted": "Birim gönüllü olarak silindi"
"unit_voluntarily_deleted": "Birim gönüllü olarak silindi",
"betrayal_debuff_ends": "İhanet zayıflatmasının bitmesine {time} saniye kaldı",
"attack_cancelled_retreat": "Saldırı iptal edildi, {troops} asker geri çekilme sırasında öldürüldü",
"received_gold_from_captured_ship": "{name}'den ele geçirilen gemiden {gold} altın alındı",
"received_gold_from_trade": "{name} ile yapılan ticaretten {gold} altın alındı",
"missile_intercepted": "{unit} füzesi önlendi",
"mirv_warheads_intercepted": "{count, plural,one {{count} MIRV başlığı önlendi} other {{count} MIRV başlığı önlendi}}",
"sent_troops_to_player": "{name}'e {troops} birlik gönderildi",
"received_troops_from_player": "{name}'den {troops} birlik alındı",
"sent_gold_to_player": "{name}'e {gold} altın gönderildi",
"received_gold_from_player": "{name}'den {gold} altın alındı",
"unit_captured_by_enemy": "{unit} {name} tarafından ele geçirildi",
"captured_enemy_unit": "{name}'den {unit} ele geçirildi",
"unit_destroyed": "{unit} yok edildi",
"no_boats_available": "Bot mevcut değil, maks {max}"
},
"unit_info_modal": {
"structure_info": "Bina Bilgisi",
@@ -539,6 +740,11 @@
"upgrade": "Yükselt",
"level": "Seviye"
},
"player_type": {
"player": "Oyuncu",
"nation": "Ulus",
"bot": "Bot"
},
"relation": {
"hostile": "Düşman",
"distrustful": "Güvensiz",
@@ -554,23 +760,50 @@
"player_panel": {
"gold": "Altın",
"troops": "Birlik",
"betrayals": "İhanet sayısı",
"betrayals": "İhanetler",
"traitor": "Hain",
"trading": "Ticaret",
"active": "Aktif",
"stopped": "Durduruldu",
"alliance_time_remaining": "İttifak Şu Süre Sonra Bitecek",
"embargo": "Sizinle ticareti durdurdu",
"nuke": "Onlar tarafından size gönderilen nükleerler",
"start_trade": "Ticareti başlat",
"stop_trade": "Ticareti durdur",
"yes": "Evet",
"no": "Hayır",
"none": "Hiçbiri",
"start_trade": "Ticarete Başla",
"stop_trade": "Ticareti Durdur",
"stop_trade_all": "Herkesle Ticareti Durdur",
"start_trade_all": "Herkesle Ticareti Başlat",
"alliances": "İttifaklar",
"flag": "Bayrak"
"flag": "Bayrak",
"chat": "Sohbet",
"target": "Hedef",
"break_alliance": "İttifakı Boz",
"alliance": "İttifak",
"send_alliance": "İttifaklık İsteği Gönder",
"send_troops": "Birlik Gönder",
"send_gold": "Altın Gönder",
"emotes": "Emojiler",
"arc_up": "Yukarı yay",
"arc_down": "Aşağı yay",
"flip_rocket_trajectory": "Roket yörüngesini çevir"
},
"send_troops_modal": {
"title_with_name": "{name}'e Birlik Gönder",
"available_tooltip": "Şu anda mevcut olan birliklerin",
"min_keep": "Minimum Elinde Tutma",
"slider_tooltip": "%{{percent}} •{{amount}}",
"aria_slider": "Birlik çubuğu",
"capacity_note": "Alıcı ancak {{amount}} kadar alabilir."
},
"send_gold_modal": {
"title_with_name": "{name}'e Altın Gönder",
"available_tooltip": "Mevcut altının",
"aria_slider": "Miktar çubuğu",
"slider_tooltip": "%{{percent}} • {{amount}}"
},
"replay_panel": {
"replay_speed": "Tekrar oynatma hızı",
"game_speed": "Oyun hızı",
"fastest_game_speed": "maks"
"fastest_game_speed": "Maks"
},
"error_modal": {
"crashed": "Oyun çöktü!",
@@ -579,53 +812,47 @@
"copy_clipboard": "Panoya kopyala",
"copied": "Kopyalandı!",
"failed_copy": "Kopyalama başarısız",
"spawn_failed": {
"title": "Doğma başarısız",
"description": "Otomatik doğma seçimi başarısız. Bu oyunu oynayamazsın."
},
"desync_notice": "Diğer oyuncularla senkronizasyonunuz bozuldu. Gördükleriniz diğer oyunculardan farklı olabilir."
},
"performance_overlay": {
"reset": "Sıfırla",
"copy_json_title": "Mevcut performans metriklerini JSON olarak kopyala",
"copy_clipboard": "JSON'ı kopyala",
"copied": "Kopyalandı!",
"failed_copy": "Kopyalama başarısız",
"fps": "FPS:",
"avg_60s": "Ortalama (60s):",
"frame": "Kare:",
"tick_exec": "Tik Yürütme:",
"tick_delay": "Tik Gecikmesi:",
"layers_header": "Katmanlar (ort / maks, toplam vakte göre sıralanmış):"
},
"heads_up_message": {
"choose_spawn": "Başlangıç konumu seçin"
"choose_spawn": "Başlangıç konumu seçin",
"random_spawn": "Rastgele doğma aktif. Başlangıç noktası senin için seçiliyor...",
"singleplayer_game_paused": "Oyun durduruldu",
"multiplayer_game_paused": "Oyun Lobi Sahibi tarafından durduruldu"
},
"territory_patterns": {
"title": "Bölge Deseni Seç",
"title": "Kaplamalar",
"colors": "Renkler",
"purchase": "Satın al",
"show_only_owned": "Kaplamalarım",
"all_owned": "Bütün kaplamalara sahipsin! Yeni eşyalar için sonra tekrar kontrol et.",
"not_logged_in": "Giriş yapılmadı",
"blocked": {
"login": "Bu desene erişmek için oturum açmanız gerekir.",
"purchase": "Bu deseni satın alarak kilidini açın."
"login": "Bu kaplamaya erişmek için giriş yapmış olman lazım.",
"purchase": "Bu kaplamayı açmak için satın al."
},
"pattern": {
"default": "Varsayılan",
"custom": "Özel",
"stripes_v": "Dikey",
"stripes_h": "Yatay",
"horizontal_stripes": "Yatay (Alt)",
"vertical_bars": "Dikey (Alt)",
"checkerboard": "Dama tahtası",
"choco": "Çiko",
"diagonal": "Çapraz",
"cross": "Çarpı",
"mini_cross": "Mini Çarpı",
"sword": "Kılıç",
"sparse_dots": "Seyrek Noktalar",
"evan": "Evan",
"diagonal_stripe": "Çapraz Çizgi",
"mountain_ridge": "Dağ Sırtı",
"scattered_dots": "Dağınık Noktalar",
"circuit_board": "Devre Kartı",
"shells": "Kabuklar",
"-w-": ".w.",
"white_rabbit": "Beyaz Tavşan",
"goat": "Keçi",
"cats": "Kediler",
"cursor": "İmleç",
"hand": "El",
"radiation": "Radyasyon",
"openfront_qr": "OpenFront.io QR Kodu",
"openfront": "OpenFront",
"t_rex": "T-Rex",
"embelem": "Amblem",
"contributor": "Katkıda Bulunan",
"grogu_head": "Grogu Başı",
"grogu": "Grogu"
}
"default": "Varsayılan"
},
"select_skin": "Kaplama Seç",
"selected": "seçildi"
},
"flag_input": {
"title": "Bayrak Seç",
@@ -644,5 +871,83 @@
"radial_menu": {
"delete_unit_title": "Birimi Sil",
"delete_unit_description": "En yakın birimi silmek için tıklayın"
},
"discord_user_header": {
"avatar_alt": "Profil Resmi"
},
"player_stats_table": {
"building_stats": "Yapı İstatistikleri",
"ship_arrivals": "Gelen Gemiler",
"nuke_stats": "Nükleer İstatistikleri",
"player_metrics": "Oyuncu İstatistikleri",
"building": "Yapı",
"ship_type": "Gemi Türü",
"weapon": "Silah",
"built": "İnşa Edildi",
"destroyed": "Yok Edildi",
"captured": "Ele Geçirildi",
"lost": "Kaybedildi",
"hits": "Vuruşlar",
"launched": "Fırlatıldı",
"landed": "İniş Yaptı",
"sent": "Gönderildi",
"arrived": "Ulaştı",
"attack": "Saldır",
"received": "Alındı",
"cancelled": "İptal Edildi",
"count": "Miktar",
"gold": "Altın",
"workers": "İşçiler",
"war": "Savaş",
"trade": "Ticaret",
"steal": "Çal",
"unit": {
"city": "Şehir",
"port": "Liman",
"defp": "Savunma Karakolu",
"saml": "SAM Fırlatıcı",
"silo": "Füze Silosu",
"wshp": "Savaş Gemisi",
"fact": "Fabrika",
"trade": "Ticaret Gemisi",
"trans": "Nakliye Gemisi",
"abomb": "Atom Bombası",
"hbomb": "Hidrojen Bombası",
"mirv": "MIRV",
"mirvw": "MIRV Savaş Başlığı"
}
},
"game_list": {
"recent_games": "Son Oyunlar",
"game_id": "Oyun ID'si",
"mode": "Mod",
"mode_ffa": "Herkes Tek",
"mode_team": "Takım",
"replay": "Tekrar",
"details": "Detaylar",
"ranking": "Sıralama",
"started": "Başladı",
"map": "Harita",
"difficulty": "Zorluk",
"type": "Tür"
},
"player_stats_tree": {
"public": "Herkese Açık",
"private": "Özel",
"singleplayer": "Tekli",
"mode": "Mod",
"stats_wins": "Galibiyetler",
"stats_losses": "Yenilgiler",
"stats_wlr": "Kazanma:Kaybetme Oranı",
"stats_games_played": "Oynanan Oyunlar",
"mode_ffa": "Herkes Tek",
"mode_team": "Takım",
"no_stats": "Bu seçenek için veri kaydedilmedi."
},
"matchmaking_button": {
"play_ranked": "1v1 Aşamalı Eşleştirme",
"description": "(ALFA)",
"login_required": "Aşamalı oynamak için giriş yap!",
"must_login": "Aşamalı eşleştirme oynamak için giriş yapmanız gerek."
}
}
+219 -74
View File
@@ -7,6 +7,7 @@
},
"common": {
"close": "Закрити",
"back": "Назад",
"available": "Доступно",
"preset_max": "Максимум",
"summary_send": "Переказ",
@@ -17,32 +18,48 @@
"cap_tooltip": "Залишкова місткість отримувача",
"target_dead": "Ціль знищено",
"target_dead_note": "Неможливо надіслати ресурси полеглому гравцю.",
"none": "Немає"
"none": "Немає",
"copied": "Скопійовано!",
"click_to_copy": "Натисніть, щоб скопіювати"
},
"main": {
"title": "OpenFront (АЛЬФА)",
"join_discord": "Discord",
"login_discord": "Увійти з Discord",
"sign_in": "Увійти",
"discord_avatar_alt": "Аватар профілю Discord",
"user_avatar_alt": "Аватар {username}",
"checking_login": "Перевірка входу...",
"logged_in": "Вхід виконано!",
"logged_in": "Вхід здійснено!",
"log_out": "Вийти",
"create_lobby": "Створити лобі",
"join_lobby": "Приєднатися до лобі",
"single_player": "Гра наодинці",
"create": "Створити лобі",
"join": "Приєднатися до лобі",
"solo": "Соло",
"instructions": "Інструкції",
"game_info": "Інформація про гру",
"wiki": "Вікі",
"privacy_policy": "Політика конфіденційності",
"terms_of_service": "Умови користування",
"reddit": "Reddit"
"copyright": "© OpenFront™ і співавтори",
"reddit": "Reddit",
"play": "Грати",
"news": "Новини",
"store": "Крамниця",
"settings": "Налаштування",
"keys": "Клавіші",
"stats": "Статистика",
"account": "Акаунт",
"help": "Допомога",
"menu": "Меню",
"pick_pattern": "Оберіть візерунок!"
},
"news": {
"see_all_releases": "Переглянути всі випуски",
"github_link": "на GitHub",
"title": "Список змін"
},
"help_modal": {
"hotkeys": "Гарячі клавіші",
"table_key": "Клавіш",
"table_key": "Клавіша",
"table_action": "Дія",
"action_alt_view": "Альтернативний вигляд (рельєф/країни)",
"action_attack_altclick": "Атака (коли лівий клац призначено на відкриття меню)",
@@ -60,7 +77,7 @@
"ui_leaderboard_desc": "Показує найкращих гравців гри та їхні імена, % підконтрольних територій, кількість золота та військ. За допомогою кнопки «Показати все» ви можете переглянути всіх гравців у грі. Якщо ви не бажаєте бачити таблицю лідерів, натисніть «Приховати».",
"ui_control": "Панель керування",
"ui_control_desc": "Панель керування містить наступні елементи:",
"ui_pop": "Населення — Кількість ваших підрозділів, максимальне населення та темп його приросту.",
"ui_pop": "Населення — Кількість ваших підрозділів, ліміт населення та темп його приросту.",
"ui_gold": "Золото — Обсяг вашого золота та швидкість, з якою ви отримуєте його.",
"ui_attack_ratio": "Коефіцієнт атаки — Кількість військ, що беруть участь в атаці. Ви можете налаштувати коефіцієнт атаки за допомогою повзунка. Якщо наступальних військ більше ніж оборонних, то буде зменшено втрати під час атаки, а якщо менше — буде збільшено шкоду, що буде завдано вашим наступальним військам. Ефективність не збільшується після коефіцієнту 2:1.",
"ui_events": "Панель подій",
@@ -76,13 +93,15 @@
"option_pause": "Призупинити/Продовжити гру — Доступно лише в режимі гри наодинці.",
"option_timer": "Таймер — Час, що минув із початку гри.",
"option_exit": "Кнопка виходу.",
"option_settings": "Налаштування — Відкрити меню налаштувань. У ньому можна увімкнути/вимкнути режим альтернативного вигляду, емоджі, темний режим, нінджя (режим прихованих/випадкових імен) та виконання дії при клацанні лівою кнопкою миші.",
"option_settings": "Налаштування — Відкрити меню налаштувань. У ньому можна перемкнути режим альтернативного вигляду, емоджі, темний режим, нінджя (режим прихованих/випадкових імен) та виконання дії при клацанні лівою кнопкою миші.",
"radial_title": "Кругове меню",
"radial_desc": "Правий клац (або дотик на мобільних пристроях) відкриває кругове меню. Клацніть правою кнопкою миші поза ним, щоб закрити його. У меню ви можете:",
"radial_build": "Відкрити меню будівництва.",
"radial_attack": "Відкрити меню атаки.",
"radial_info": "Відкрити меню інформації.",
"radial_boat": "Відправити човен (транспортний корабель) атакувати вибране розташування. Доступно лише якщо ви маєте доступ до води.",
"radial_donate_troops": "Пожертвувати кількість військ, що дорівнює коефіцієнту повзунку атаки тому союзнику, на якому ви відкрили кругове меню.",
"radial_donate_gold": "Відкриває меню повзунка пожертвування золота для швидкого надсилання золота союзникам.",
"radial_close": "Закрити меню.",
"info_title": "Меню інформації",
"info_enemy_desc": "Містить таку інформацію про вибраного гравця, як його імʼя, кількість золота, військ, стан торгувілі з вами, кількість запущених на вас ракет і мітку зрадника. Припинення торгівля означає, що ви не отримуватиме золото від гравця, а він не надсилатиме вам золото торговельними кораблями. Свідомо (якщо гравець натиснув «Припинити торгівлю», що триває, поки ви обидва не натиснете «Розпочати торгівлю») або автоматично (якщо ви зрадили союз, що триває, поки ви знову не станете союзниками або через 5 хвилин). Поле «Зрадник» показує стан «Так» протягом 30 секунд після того, як гравець зрадив й атакував гравця, який перебував у союзні з ним. Значки нижче позначають такі взаємодії:",
@@ -102,7 +121,7 @@
"build_icon": "Значок",
"build_desc": "Опис",
"build_city": "Місто",
"build_city_desc": "Збільшує вашу максимальну кількість населення. Корисно, коли ви не можете розширити територію або населення сягає ліміту.",
"build_city_desc": "Збільшує ваш ліміт населення. Корисно, коли ви не можете розширити територію або населення незабаром досягне ліміту.",
"build_factory": "Фабрика",
"build_factory_desc": "Автоматично прокладає залізничні колії до найближчих міст, портів та інших фабрик. Також може обʼєднуватися з дружніми сусідніми країнами. Поїзди зʼявляються регулярно і дають сталу кількість золота за кожну будівлю, яку проїжджають на шляху, із бонусом за відвідування будівель сусідніх країн.",
"build_defense": "Пункт оборони",
@@ -114,7 +133,7 @@
"build_silo": "Ракетна шахта",
"build_silo_desc": "Дає можливість запускати ракети.",
"build_sam": "ПУ ЗРК",
"build_sam_desc": "Дозволяє перехоплювати ворожі ракети в радіусі 100 пікселів. Має 100% шанс на збиття атомної бомби, 80% — водневої бомби та 50% — окремих боєголовок РГЧ ІН. ЗРК має період перезаряджання в 7,5 секунд.",
"build_sam_desc": "Може перехоплювати ворожі ракети в радіусі 100 пікселів. ЗРК має період перезаряджання в 7,5 секунд.",
"build_atom": "Атомна бомба",
"build_atom_desc": "Невелика вибухова бомба, яка руйнує територію, будівлі, кораблі та човни. Запускається з найближчої ракетної шахти та вражає область, вибрану клацанням кнопкою миші.",
"build_hydrogen": "Воднева бомба",
@@ -129,12 +148,15 @@
"icon_embargo": "Закреслений знак долара — Ембарго. Цей гравець припинив торгівлю з вами; автоматично чи свідомо.",
"icon_request": "Конверт — Запрошення до союзу. Гравець надіслав вам запит на укладення союзу.",
"info_enemy_panel": "Панель інформації про ворога",
"exit_confirmation": "Ви впевнені, що хочете вийти з гри?"
"exit_confirmation": "Ви впевнені, що хочете вийти з гри?",
"bomb_direction": "Траєкторія польоту атомної/водородної бомби"
},
"single_modal": {
"title": "Гра наодинці",
"title": "Соло",
"random_spawn": "Випадкова поява",
"allow_alliances": "Дозволити союзи",
"toggle_achievements": "Перемикання досягнень",
"sign_in_for_achievements": "Увійдіть, щоб отримувати досягнення",
"options_title": "Налаштування",
"bots": "Боти: ",
"bots_disabled": "Відключені",
@@ -145,6 +167,8 @@
"infinite_troops": "Необмежені війська",
"compact_map": "Компактна мапа",
"max_timer": "Тривалість гри (хвилини)",
"max_timer_placeholder": "Хвилини",
"max_timer_invalid": "Будь ласка, введіть дійсне максимальне значення таймера (1–120 хвилин)",
"disable_nukes": "Вимкнути бомби",
"enables_title": "Дозволи",
"start": "Розпочати гру"
@@ -156,10 +180,21 @@
},
"account_modal": {
"title": "Акаунт",
"logged_in_as": "Ви увійшли як {email}",
"connected_as": "Підʼєднано як",
"stats_overview": "Огляд статистики",
"link_discord": "Повʼязати акаунт Discord",
"log_out": "Вийти",
"sign_in_desc": "Увійдіть, щоб зберегти статистику та прогрес",
"or": "АБО",
"email_placeholder": "Введіть свою електронну пошту",
"get_magic_link": "Отримати чарівне посилання",
"linked_account": "Ви увійшли як {account_name}",
"fetching_account": "Отримання інформації про акаунт...",
"logged_in_with_discord": "Ви увійшли через Discord",
"recovery_email_sent": "Лист для відновлення надіслано на {email}"
"recovery_email_sent": "Лист для відновлення надіслано на {email}",
"not_found": "Не знайдено",
"clear_session": "Очистити сесію",
"failed_to_send_recovery_email": "Не вдалося надіслати електронний лист для відновлення",
"enter_email_address": "Будь ласка, введіть адресу електронної пошти"
},
"stats_modal": {
"title": "Статистика",
@@ -167,11 +202,40 @@
"loading": "Завантаження...",
"error": "Помилка завантаження статистики кланів",
"no_stats": "Статистика кланів недоступна",
"no_data_yet": "Дані поки що відсутні",
"clan": "Клан",
"games": "Ігри",
"win_score": "Рахунок перемог",
"win_score_tooltip": "Зважені перемоги на основі участі клану та складності матчу",
"loss_score": "Рахунок поразок",
"win_loss_ratio": "Перемоги/Поразки"
"loss_score_tooltip": "Зважені поразки на основі участі клану та складності матчу",
"win_loss_ratio": "Перемоги/Поразки",
"ratio": "Коефіцієнт",
"rank": "Ранг",
"try_again": "Спробуйте ще раз"
},
"game_info_modal": {
"title": "Інформація про гру",
"players": "Гравці",
"atoms": "Атомні бомби",
"hydros": "Водневі бомби",
"mirv": "РГЧ ІН",
"bombs": "Бомби",
"total_gold": "Загалом",
"all_gold": "Усе золото",
"trade": "Торгівля",
"conquest_gold": "Загарбане золото гравців",
"stolen_gold": "Викрадено воєнними кораблями",
"num_of_conquests": "Кількість підкорених гравців",
"duration": "Тривалість",
"survival_time": "Час виживання",
"war": "Війна",
"economy": "Економіка",
"conquests": "Завоювання",
"pirate": "Піратство",
"conquered": "Завойовано",
"loading_game_info": "Завантаження статистики ігор",
"no_winner": "Ця гра закінчилася без переможця (або перемогла нація)"
},
"map": {
"map": "Мапа",
@@ -186,6 +250,7 @@
"asia": "Азія",
"mars": "Марс",
"southamerica": "Південна Америка",
"britanniaclassic": "Британія (класична)",
"britannia": "Британія",
"gatewaytotheatlantic": "Гібралтарська протока",
"australia": "Австралія",
@@ -206,22 +271,36 @@
"yenisei": "Єнісей",
"pluto": "Плутон",
"montreal": "Монреаль",
"newyorkcity": "Нью-Йорк",
"achiran": "Акіран",
"baikalnukewars": "Байкал (ядерні війни)",
"fourislands": "Чотири острови",
"gulfofstlawrence": "Затока Св. Лаврентія",
"lisbon": "Лісабон"
"lisbon": "Лісабон",
"svalmel": "Свалмел",
"manicouagan": "Манікуаган",
"lemnos": "Лемнос",
"sierpinski": "Серпінський",
"twolakes": "Два озера",
"straitofhormuz": "Ормузька протока",
"surrounded": "Оточення",
"didier": "Дідьє",
"didierfrance": "Дідьє (Франція)",
"amazonriver": "Річка Амазонка"
},
"map_categories": {
"continental": "Континентальні",
"regional": "Регіональні",
"fantasy": "Інші"
"fantasy": "Інші",
"special": "Особливі",
"arcade": "Аркадні"
},
"map_component": {
"loading": "Завантаження..."
"loading": "Завантаження...",
"error": "Помилка"
},
"private_lobby": {
"title": "Приєднатися до приватного лобі",
"title": "Приєднання до приватного лобі",
"enter_id": "Введіть ID лобі",
"player": "Гравець",
"players": "Гравці(в)",
@@ -229,42 +308,55 @@
"checking": "Перевірка лобі...",
"not_found": "Лобі не знайдено. Будь ласка, перевірте дійсність ID і спробуйте знову.",
"error": "Сталася помилка. Спробуйте ще раз або зверніться до служби підтримки.",
"joined_waiting": "Ви успішно приєдналися! Очікування початку гри...",
"version_mismatch": "Цю гру створено в іншій версії. Неможливо приєднатися."
"joined_waiting": "Лобі приєднано! Очікуємо, доки хост почне гру...",
"version_mismatch": "Цю гру створено в іншій версії. Неможливо приєднатися.",
"disabled_units": "Вимкнені споруди"
},
"public_lobby": {
"join": "Приєднатися до наступної гри",
"waiting": "гравці(в) очікують",
"teams_Duos": "по 2 (дуо)",
"teams_Trios": "по 3 (тріо)",
"teams_Quads": "по 4 (квади)",
"teams_Duos": "{team_count} команд по 2 (дуо)",
"teams_Trios": "{team_count} команд по 3 (тріо)",
"teams_Quads": "{team_count} команд по 4 (квади)",
"waiting_for_players": "Очікування гравців",
"starting_game": "Початок гри…",
"teams_hvn": "Люди проти націй",
"teams": "Команд: {num}",
"players_per_team": "по {num}"
"teams_hvn_detailed": "{num} людей проти {num} націй",
"teams": "Команди: {num}",
"players_per_team": "по {num}",
"started": "Почато"
},
"matchmaking_modal": {
"title": "Підбір гравців",
"title": "Рейтинговий підбір 1v1 (АЛЬФА)",
"connecting": "Приєднання до сервера підбору гравців...",
"searching": "Пошук гри...",
"waiting_for_game": "Очікування початку гри..."
"waiting_for_game": "Очікування початку гри...",
"elo": "Ваш ELO: {elo}"
},
"username": {
"enter_username": "Введіть своє імʼя гравця",
"not_string": "Імʼя гравця має бути рядком.",
"too_short": "Імʼя гравця повинно містити щонайменше {min} символів.",
"too_long": "Довжина імʼя гравця не повинна перевищувати {max} символів.",
"invalid_chars": "Імʼя гравця може містити лише латинські літери, цифри, пробіли, знаки підкреслення та [квадратні дужки]."
"invalid_chars": "Імʼя гравця може містити лише латинські літери, цифри, пробіли та підкреслення.",
"tag": "ТЕГ",
"tag_too_short": "Тег клану має складатися з 2–5 абетко-цифрових символів.",
"tag_invalid_chars": "Тег клану може містити лише латинські літери та цифри."
},
"host_modal": {
"title": "Приватне лобі",
"title": "Створення приватного лобі",
"label": "Приватний",
"mode": "Режим",
"team_count": "Кількість команд",
"team_type": "Тип команди",
"options_title": "Налаштування",
"bots": "Боти: ",
"bots_disabled": "Відключені",
"player_immunity_duration": "Тривалість імунітету в PVP (хвилини)",
"nations": "Нації: ",
"disable_nations": "Вимкнути нації",
"max_timer": "Тривалість гри (хвилини)",
"mins_placeholder": "Хвилини",
"instant_build": "Миттєве будівництво",
"infinite_gold": "Безмежне золото",
"donate_gold": "Пожертвування золота",
@@ -283,7 +375,11 @@
"assigned_teams": "Розподілені команди",
"empty_teams": "Порожні команди",
"empty_team": "Немає",
"remove_player": "Вилучити {username}"
"remove_player": "Вилучити {username}",
"teams_Duos": "Дуо (команди по 2)",
"teams_Trios": "Тріо (команди по 3)",
"teams_Quads": "Квади (команди по 4)",
"teams_Humans Vs Nations": "Люди проти націй"
},
"team_colors": {
"red": "Червоний",
@@ -301,18 +397,22 @@
"code_license": "Код ліцензовано під AGPL-3.0 (без гарантій)"
},
"difficulty": {
"difficulty": "Складність",
"Easy": "Розслаблена",
"Medium": "Збалансована",
"Hard": "Напружена",
"Impossible": "Неможлива"
"difficulty": "Складність націй",
"easy": "Легко",
"medium": "Середньо",
"hard": "Важко",
"impossible": "Неможливо"
},
"game_mode": {
"ffa": "Усі проти всіх",
"teams": "Команди"
},
"public_game_modifier": {
"random_spawn": "Випадкова поява",
"compact_map": "Компактна мапа"
},
"select_lang": {
"title": "Виберіть мову"
"title": "Вибір мови"
},
"unit_type": {
"city": "Місто",
@@ -327,51 +427,54 @@
"factory": "Фабрика"
},
"user_setting": {
"title": "Користувацькі налаштування",
"title": "Налаштування",
"tab_basic": "Основні налаштування",
"tab_keybinds": "Призначення клавіш",
"dark_mode_label": "Темний режим",
"dark_mode_desc": "Перемикання зовнішнього вигляду сайту між світлою та темною темою",
"emojis_label": "Емоджі",
"emojis_desc": "Увімкнення/вимкнення видимости емоджі під час гри",
"emojis_desc": "Перемкнути видимість емоджі під час гри",
"alert_frame_label": "Рамка тривоги",
"alert_frame_desc": "Увімкнути/вимкнути рамку тривоги. Якщо увімкнено, вона показуватиметься, коли вас зраджують або атакують по суші.",
"alert_frame_desc": "Перемкнути рамку тривоги. Якщо увімкнено, рамка показуватиметься, коли вас зраджують або атакують по суші.",
"special_effects_label": "Спецефекти",
"special_effects_desc": "Увімкнути/вимкнути спецефекти. Вимкніть для поліпшення продуктивности",
"special_effects_desc": "Перемкнути спецефекти. Вимкніть для поліпшення продуктивности",
"structure_sprites_label": "Спрайти споруд",
"structure_sprites_desc": "Увімкнення/вимкнення спрайтів споруд",
"structure_sprites_desc": "Перемкнути спрайти споруд",
"cursor_cost_label_label": "Вартість будування під указівником",
"cursor_cost_label_desc": "Показувати вартість будівництва під указівником",
"anonymous_names_label": "Приховані імена",
"anonymous_names_desc": "Приховати справжні імена гравців і замінити їх випадковими.",
"lobby_id_visibility_label": "Приховані ID лобі",
"lobby_id_visibility_desc": "Приховати ID при створенні приватного лобі",
"toggle_visibility": "Перемикання видимости",
"left_click_label": "Відкриття меню лівою кнопкою миші",
"left_click_desc": "УВІМКНЕНО — лівий клац відкриває меню, кнопкою з мечем здійснює атаку. ВИМКНЕНО — лівий клац одразу атакує.",
"left_click_menu": "Меню на лівий клац миші",
"attack_ratio_label": "⚔️ Коефіцієнт атаки",
"attack_ratio_desc": "Який відсоток ваших бере учать в атаці (1100%)",
"troop_ratio_desc": "Налаштуйте співвідношення між військами (для бою) та працівниками (для видобування золота) (1–100%)",
"attack_ratio_desc": "Який відсоток ваших військ відправляти в наступ (1100%)",
"territory_patterns_label": "🏳️ Скіни території",
"territory_patterns_desc": "Виберіть, чи показувати скіни територій у грі",
"performance_overlay_label": "Оверлей продуктивности",
"performance_overlay_desc": "Увімкнення/вимкнення оверлея продуктивности. Якщо увімкнено, буде показано оверлей продуктивности. Натисніть Shift+D під час гри, щоб увімкнути/вимкнути його.",
"performance_overlay_desc": "Перемкнути оверлей продуктивности. Якщо увімкнено, буде показано оверлей продуктивности. Натисніть Shift+D під час гри, щоб перемкнути його.",
"easter_writing_speed_label": "Множник швидкості друку",
"easter_writing_speed_desc": "Налаштуйте швидкість, з якою ви удаєте, що програмуєте (x1–x100)",
"easter_bug_count_label": "Кількість багів",
"easter_bug_count_desc": "Кількість багів, що ви вважаєте прийнятною (0–1000, емоційно)",
"press_a_key": "Натисніть клавішу",
"view_options": "Налаштування вигляду",
"toggle_view": "Змінити вигляд",
"toggle_view": "Перемкнути вигляд",
"toggle_view_desc": "Альтернативний вигляд (рельєф/країни)",
"build_controls": "Керування розміщенням",
"build_city": "Розмістити місто",
"build_city_desc": "Розмістити місто під указівником.",
"build_factory": "Розмістити фабрику",
"build_factory_desc": "Будувати фабрику під указівником.",
"build_factory_desc": "Розмістити фабрику під указівником.",
"build_defense_post": "Розмістити пункт оборони",
"build_defense_post_desc": "Розмістити пункт оборони під указівником.",
"build_port": "Розмістити порт",
"build_port_desc": "Розмістити порт під указівником.",
"build_warship": "Розмістити військовий корабель",
"build_warship_desc": "Будувати військовий корабель під указівником.",
"build_warship_desc": "Розмістити військовий корабель під указівником.",
"build_missile_silo": "Розмістити ракетну шахту",
"build_missile_silo_desc": "Розмістити ракетну шахту під указівником.",
"build_sam_launcher": "Розмістити ПУ ЗРК",
@@ -382,6 +485,11 @@
"build_hydrogen_bomb_desc": "Розмістити водневу бомбу під указівником.",
"build_mirv": "Розмістити РГЧ ІН",
"build_mirv_desc": "Розмістити РГЧ ІН під указівником.",
"menu_shortcuts": "Скорочення меню",
"build_menu_modifier": "Модифікатор меню будівництва",
"build_menu_modifier_desc": "Утримуйте цю клавішу під час клацання, щоб відкрити меню будівництва.",
"emoji_menu_modifier": "Модифікатор меню емоджі",
"emoji_menu_modifier_desc": "Утримуйте цю клавішу під час клацання, щоб відкрити меню емоджі.",
"attack_ratio_controls": "Керування коефіцієнтом атаки",
"attack_ratio_up": "Збільшити коефіцієнт атаки",
"attack_ratio_up_desc": "Збільшити коефіцієнт атаки на 10%",
@@ -392,6 +500,8 @@
"boat_attack_desc": "Відправити човен на клітинку під указівником.",
"ground_attack": "Наземна атака",
"ground_attack_desc": "Відправити наземну атаку на клітинку під указівником.",
"swap_direction": "Змінити напрямок ракети",
"swap_direction_desc": "Перемкнути напрямок ракети (угору/вниз).",
"zoom_controls": "Масштабування",
"zoom_out": "Зменшити масштаб",
"zoom_out_desc": "Зменшити масштаб мапи",
@@ -412,11 +522,12 @@
"unbind": "Звільнити",
"on": "Увімкнено",
"off": "Вимкнено",
"toggle_terrain": "Увімкнення/вимкнення рельєфу",
"toggle_terrain": "Перемикання рельєфу",
"exit_game_label": "Вийти з гри",
"exit_game_info": "Повернутися до головного меню",
"background_music_volume": "Гучність фонової музики",
"sound_effects_volume": "Гучність звукових ефектів"
"sound_effects_volume": "Гучність звукових ефектів",
"keybind_conflict_error": "Клавішу {key} вже привʼязано до іншої дії."
},
"chat": {
"title": "Швидкий чат",
@@ -516,7 +627,7 @@
"warship": "Захоплює торгові кораблі, знищує кораблі та човни",
"port": "Відправляє торгові кораблі для генерації золота",
"defense_post": "Підсилює оборону найближчих кордонів",
"city": "Збільшує максимальне населення",
"city": "Збільшує ліміт населення",
"factory": "Прокладає залізничні колії та створює поїзди"
},
"not_enough_money": "Недостатньо коштів"
@@ -529,6 +640,7 @@
"other_team": "Команда «{team}» перемогла!",
"you_won": "Ви перемогли!",
"other_won": "Гравець {player} переміг!",
"nation_won": "Нація {nation} перемогла!",
"exit": "Вийти з гри",
"keep": "Продовжити гру",
"spectate": "Спостерігати",
@@ -537,19 +649,19 @@
"ofm_winter_description": "Приєднуйтеся до турніру та змагайтеся з найкращими гравцями",
"join_tournament": "Приєднатися до турніру",
"join_discord": "Приєднуйтеся до нашої спільноти Discord!",
"discord_description": "Спілкуйтеся з іншими гравцями, отримуйте новини та діліться стратегіями",
"discord_description": "Звʼязуйтеся з гравцями, відкривайте нові можливості та вигравайте призи!",
"join_server": "Приєднатися до сервера",
"youtube_tutorial": "Потрібна допомога?"
},
"leaderboard": {
"title": "Таблиця лідерів",
"hide": "Приховати",
"rank": "Місце",
"rank": "Ранг",
"player": "Гравець",
"team": "Команда",
"owned": "Влада",
"gold": "Золото",
"troops": "Війська",
"maxtroops": "Ліміт військ",
"launchers": "Установки",
"sams": "ЗРК",
"warships": "Військові кораблі",
@@ -565,6 +677,7 @@
"team": "Команда",
"alliance_timeout": "Кінець союзу через",
"troops": "Війська",
"maxtroops": "Ліміт військ",
"a_troops": "Наступальні війська",
"gold": "Золото",
"ports": "Порти",
@@ -575,7 +688,9 @@
"warships": "Військові кораблі",
"health": "Здоровʼя",
"attitude": "Ставлення",
"levels": "Рівні"
"levels": "Рівні",
"wilderness_title": "Пустир",
"irradiated_wilderness_title": "Радіоактивний пустир"
},
"events_display": {
"retreating": "відступає",
@@ -600,8 +715,21 @@
"alliance_renewed": "Союз із {name} було поновлено",
"wants_to_renew_alliance": "{name} хоче поновити ваш союз",
"ignore": "Ігнорувати",
"unit_voluntarily_deleted": "Обʼєкт добровільно видалено",
"betrayal_debuff_ends": "Залишилося {time} сек до закінчення покарання зрадника"
"unit_voluntarily_deleted": "Споруду добровільно видалено",
"betrayal_debuff_ends": "Залишилося {time} сек до закінчення покарання зрадника",
"attack_cancelled_retreat": "Атаку скасовано, {troops} солдатів загинули під час відступу",
"received_gold_from_captured_ship": "Отримано {gold} золота з корабля, захопленого у {name}",
"received_gold_from_trade": "Отримано {gold} золота від торгівлі з {name}",
"missile_intercepted": "{unit} перехоплює ракету",
"mirv_warheads_intercepted": "{count, plural, one {Перехоплено {count} боєголовку РГЧ ІН} few {Перехоплено {count} боєголовки РГЧ ІН} many {Перехоплено {count} боєголовок РГЧ ІН} other {Перехоплено {count} боєголовок РГЧ ІН}}",
"sent_troops_to_player": "Відправлено {troops} військ до {name}",
"received_troops_from_player": "Отримано {troops} військ від {name}",
"sent_gold_to_player": "Надіслано {gold} золота для {name}",
"received_gold_from_player": "Отримано {gold} золота від {name}",
"unit_captured_by_enemy": "{name} захоплює вашу споруду «{unit}»",
"captured_enemy_unit": "Захоплено споруду «{unit}» у {name}",
"unit_destroyed": "Вашу споруду «{unit}» було знищено",
"no_boats_available": "Немає доступних човнів, максимум — {max}"
},
"unit_info_modal": {
"structure_info": "Інформація про споруду",
@@ -653,7 +781,10 @@
"send_alliance": "Надіслати союз",
"send_troops": "Надіслати війська",
"send_gold": "Надіслати золото",
"emotes": "Емоджі"
"emotes": "Емоджі",
"arc_up": "Верхня дуга",
"arc_down": "Нижня дуга",
"flip_rocket_trajectory": "Обернути траєкторію ракети"
},
"send_troops_modal": {
"title_with_name": "Надіслати війська до {name}",
@@ -672,7 +803,7 @@
"replay_panel": {
"replay_speed": "Швидкість відтворення",
"game_speed": "Швидкість гри",
"fastest_game_speed": "Максимальна"
"fastest_game_speed": "Макс."
},
"error_modal": {
"crashed": "Гра крашнулася!",
@@ -698,27 +829,33 @@
"frame": "Кадр:",
"tick_exec": "Виконання на тік:",
"tick_delay": "Затримка на тік:",
"layers_header": "Шари (сер. / макс., відсортовано за заг. часом):"
"layers_header": "Шари (сер. / макс., відсортовано за загальним часом):"
},
"heads_up_message": {
"choose_spawn": "Оберіть стартове розташування",
"random_spawn": "Випадкову появу увімкнено. Обираємо стартове розташування за вас..."
"random_spawn": "Випадкову появу увімкнено. Обираємо стартове розташування за вас...",
"singleplayer_game_paused": "Гру призупинено",
"multiplayer_game_paused": "Гра призупинена творцем лобі"
},
"territory_patterns": {
"title": "Скіни",
"colors": "Кольори",
"purchase": "Придбати",
"show_only_owned": "Мої скіни",
"all_owned": "Усі скіни придбані! Повертайтеся пізніше за новими товарами.",
"not_logged_in": "Вхід не здійснено",
"blocked": {
"login": "Ви повинні ввійти, щоб отримати доступ до цього скіна.",
"purchase": "Придбайте цей скін, щоб розблокувати його."
},
"pattern": {
"default": "Типово"
}
},
"select_skin": "Оберіть скін",
"selected": "обрано"
},
"flag_input": {
"title": "Виберіть прапор",
"title": "Вибір прапора",
"button_title": "Обери прапор!",
"search_flag": "Пошук..."
},
@@ -732,8 +869,8 @@
"contact_admin": "Якщо ви вважаєте, що бачите це повідомлення помилково, зверніться до адміністратора сайту."
},
"radial_menu": {
"delete_unit_title": "Видалити обʼєкт",
"delete_unit_description": "Клацніть, щоб видалити найближчий обʼєкт"
"delete_unit_title": "Видалити споруду",
"delete_unit_description": "Клацніть, щоб видалити найближчу споруду"
},
"discord_user_header": {
"avatar_alt": "Аватар"
@@ -743,7 +880,7 @@
"ship_arrivals": "Прибуття кораблів",
"nuke_stats": "Статистика бомбардувань",
"player_metrics": "Статистика гравця",
"building": "Будівництво",
"building": "Споруда",
"ship_type": "Тип корабля",
"weapon": "Зброя",
"built": "Побудовано",
@@ -762,7 +899,7 @@
"gold": "Золото",
"workers": "Робітники",
"war": "Війни",
"trade": "Обмін",
"trade": "Торгівля",
"steal": "Украдено",
"unit": {
"city": "Місто",
@@ -786,8 +923,9 @@
"mode": "Режим",
"mode_ffa": "Усі проти всіх",
"mode_team": "Команда",
"view": "Оглянути",
"replay": "Повтор",
"details": "Подробиці",
"ranking": "Рейтинг",
"started": "Почато",
"map": "Мапа",
"difficulty": "Складність",
@@ -796,13 +934,20 @@
"player_stats_tree": {
"public": "Публічний",
"private": "Приватний",
"singleplayer": "Гра наодинці",
"singleplayer": "Соло",
"mode": "Режим",
"stats_wins": "Перемоги",
"stats_losses": "Поразки",
"stats_wlr": "Співвідношення перемог і поразок",
"stats_wlr": "Коефіцієнт перемог і поразок",
"stats_games_played": "Зіграні ігри",
"mode_ffa": "Усі проти всіх",
"mode_team": "Команда"
"mode_team": "Команда",
"no_stats": "Немає даних для цієї вибірки."
},
"matchmaking_button": {
"play_ranked": "Рейтинговий підбір 1v1",
"description": "(АЛЬФА)",
"login_required": "Увійдіть, щоб грати в рейтинговому режимі!",
"must_login": "Ви повинні увійти, щоб грати в рейтинговому режимі."
}
}
+192 -47
View File
@@ -7,6 +7,7 @@
},
"common": {
"close": "关闭",
"back": "返回",
"available": "剩余",
"preset_max": "最大",
"summary_send": "发送",
@@ -17,26 +18,42 @@
"cap_tooltip": "接收者的可接收数量",
"target_dead": "目标已淘汰",
"target_dead_note": "你不能向已淘汰玩家发送资源。",
"none": "空"
"none": "空",
"copied": "已复制!",
"click_to_copy": "点击复制"
},
"main": {
"title": "OpenFront (ALPHA)",
"title": "OpenFront (内测版)",
"join_discord": "Discord",
"login_discord": "用 Discord 登录",
"sign_in": "登录",
"discord_avatar_alt": "Discord 头像",
"user_avatar_alt": "{username} 的头像",
"checking_login": "正在检查登录...",
"logged_in": "登录成功!",
"log_out": "退出登录",
"create_lobby": "创建房间",
"join_lobby": "加入房间",
"single_player": "单人游戏",
"create": "创建房间",
"join": "加入房间",
"solo": "单人模式",
"instructions": "操作说明",
"game_info": "游戏信息",
"wiki": "游戏百科",
"privacy_policy": "隐私政策",
"terms_of_service": "服务条款",
"reddit": "Reddit"
"copyright": "© OpenFront™ 和贡献者们",
"reddit": "Reddit",
"play": "游戏",
"news": "公告",
"store": "商店",
"settings": "设置",
"keys": "按键",
"stats": "统计",
"account": "账号",
"help": "帮助",
"menu": "菜单",
"pick_pattern": "选择一个图案!"
},
"news": {
"see_all_releases": "查看所有版本信息",
"github_link": "在 Github 上",
"title": "发行说明"
},
@@ -83,6 +100,8 @@
"radial_attack": "打开攻击菜单。",
"radial_info": "打开信息菜单。",
"radial_boat": "发送一艘运输船攻击选中的区域。仅当你与水域毗邻时才可用。",
"radial_donate_troops": "捐赠相当于你攻击比例的军队给该盟友。",
"radial_donate_gold": "打开黄金捐赠菜单,可快速向盟友发送黄金。",
"radial_close": "关闭菜单。",
"info_title": "信息菜单",
"info_enemy_desc": "包含以下信息:所选玩家的名称、黄金数量、军队数量、是否已停止与你贸易、是否对你发射了核弹,以及该玩家是否为叛徒。“停止贸易”表示你将无法从该玩家处获得金币,对方也无法通过商船向你发送金币。这种状态可能是手动触发(该玩家点击了“停止贸易”,此状态将持续,直到你们双方都点击“开始贸易”)或自动触发(当你背叛了联盟时,此状态会持续,直到你们重新结盟或5分钟后自动结束)。当玩家背叛并攻击其盟友时,“叛徒”状态将显示为“是”,持续30秒。下方图标表示你与该玩家的互动关系:",
@@ -114,7 +133,7 @@
"build_silo": "导弹发射井",
"build_silo_desc": "允许发射导弹。",
"build_sam": "防空塔",
"build_sam_desc": "可以截获100像素范围内的敌方导弹。原子弹、氢弹和单个MIRV弹头的拦截命中概率分别是100%、80%和50%。该防空导弹拥有7.5秒冷却。",
"build_sam_desc": "可以截获100像素范围内的敌方导弹。防空塔有7.5秒冷却时间。",
"build_atom": "原子弹",
"build_atom_desc": "小型爆弹可摧毁领土、建筑、船只。从最近的导弹发射井发射并坠落在你初次点击部署它的区域。",
"build_hydrogen": "氢弹",
@@ -129,12 +148,15 @@
"icon_embargo": "美元符号停止标志 - 禁商。该玩家已自动或手动停止与您的交易。",
"icon_request": "信封 - 结盟请求。该玩家已向你发送结盟请求。",
"info_enemy_panel": "敌人信息面板",
"exit_confirmation": "确定要退出游戏吗?"
"exit_confirmation": "确定要退出游戏吗?",
"bomb_direction": "原子弹 / 氢弹抛物线方向"
},
"single_modal": {
"title": "单人玩家",
"title": "单人模式",
"random_spawn": "随机出生点",
"allow_alliances": "允许结盟",
"toggle_achievements": "切换成就",
"sign_in_for_achievements": "登录以获取成就",
"options_title": "选项",
"bots": "机器人: ",
"bots_disabled": "已禁用",
@@ -145,6 +167,8 @@
"infinite_troops": "无限军队",
"compact_map": "紧凑地图",
"max_timer": "游戏时长(分钟)",
"max_timer_placeholder": "分钟",
"max_timer_invalid": "请输入一个有效的最大计时器值(1-120分钟)",
"disable_nukes": "禁用核弹",
"enables_title": "启用设置",
"start": "开始游戏"
@@ -156,10 +180,21 @@
},
"account_modal": {
"title": "账号",
"logged_in_as": "以 {email} 身份登录成功",
"connected_as": "已连接为",
"stats_overview": "统计概览",
"link_discord": "链接 Discord 帐号",
"log_out": "退出登录",
"sign_in_desc": "登录以保存您的统计数据和进度",
"or": "或",
"email_placeholder": "请输入您的电子邮件地址",
"get_magic_link": "获取魔法链接",
"linked_account": "以 {account_name} 身份登录成功",
"fetching_account": "正在获取帐户信息......",
"logged_in_with_discord": "使用 Discord 登录",
"recovery_email_sent": "账号找回邮件已发送至 {email}"
"recovery_email_sent": "账号找回邮件已发送至 {email}",
"not_found": "未找到",
"clear_session": "清除会话",
"failed_to_send_recovery_email": "发送恢复邮件失败",
"enter_email_address": "请输入电子邮件地址"
},
"stats_modal": {
"title": "统计",
@@ -167,11 +202,40 @@
"loading": "正在加载……",
"error": "加载军团统计数据时出错",
"no_stats": "暂无军团统计数据",
"no_data_yet": "暂无数据",
"clan": "军团",
"games": "游戏场数",
"win_score": "胜者积分",
"win_score_tooltip": "加权胜场数基于战队参与度和比赛难度计算",
"loss_score": "败者积分",
"win_loss_ratio": "胜负比"
"loss_score_tooltip": "加权败场数基于战队参与度和比赛难度计算",
"win_loss_ratio": "胜负比",
"ratio": "比率",
"rank": "排名",
"try_again": "再试一次"
},
"game_info_modal": {
"title": "游戏信息",
"players": "玩家",
"atoms": "原子弹",
"hydros": "氢弹",
"mirv": "MIRV",
"bombs": "炸弹",
"total_gold": "总计",
"all_gold": "总黄金",
"trade": "交易",
"conquest_gold": "已抢夺黄金",
"stolen_gold": "被军舰偷走",
"num_of_conquests": "征服的玩家数",
"duration": "时长",
"survival_time": "存活时长",
"war": "战争",
"economy": "经济",
"conquests": "征服数",
"pirate": "抢劫",
"conquered": "被征服",
"loading_game_info": "正在加载游戏统计数据",
"no_winner": "这场游戏最终无人胜出(或者一个人机国获胜了)"
},
"map": {
"map": "地图",
@@ -186,6 +250,7 @@
"asia": "亚洲",
"mars": "火星",
"southamerica": "南美洲",
"britanniaclassic": "不列颠尼亚(经典)",
"britannia": "不列颠尼亚",
"gatewaytotheatlantic": "大西洋枢纽",
"australia": "澳大利亚",
@@ -196,7 +261,7 @@
"betweentwoseas": "二海之间",
"faroeislands": "法罗群岛",
"deglaciatedantarctica": "冰消的南极洲",
"europeclassic": "欧洲 (经典)",
"europeclassic": "欧洲经典",
"falklandislands": "福克兰群岛",
"baikal": "贝加尔湖",
"halkidiki": "哈尔基季基",
@@ -206,19 +271,33 @@
"yenisei": "叶尼塞河",
"pluto": "冥王星",
"montreal": "蒙特利尔",
"newyorkcity": "纽约城",
"achiran": "阿基尔岛/阿伦群岛",
"baikalnukewars": "贝加尔湖(核战争)",
"fourislands": "四岛争霸",
"gulfofstlawrence": "圣劳伦斯湾",
"lisbon": "里斯本"
"lisbon": "里斯本",
"svalmel": "斯瓦尔梅尔",
"manicouagan": "马尼夸根陨石坑",
"lemnos": "利姆诺斯岛",
"sierpinski": "谢尔宾斯基分形",
"twolakes": "双湖",
"straitofhormuz": "霍尔木兹海峡",
"surrounded": "环岛",
"didier": "迪迪埃",
"didierfrance": "迪迪埃(法国)",
"amazonriver": "亚马逊河"
},
"map_categories": {
"continental": "大陆",
"regional": "地区",
"fantasy": "其他"
"fantasy": "其他",
"special": "特殊",
"arcade": "街机"
},
"map_component": {
"loading": "正在加载..."
"loading": "正在加载...",
"error": "错误"
},
"private_lobby": {
"title": "加入私人房间",
@@ -229,42 +308,55 @@
"checking": "正在确认房间...",
"not_found": "找不到房间。请检查 ID 然后重试。",
"error": "发生错误。请再试一次或联系支持人员。",
"joined_waiting": "加入成功!正在等待游戏开始...",
"version_mismatch": "这场游戏基于另一个版本,无法加入。"
"joined_waiting": "房间已加入!等待房主开始游戏……",
"version_mismatch": "这场游戏基于另一个版本,无法加入。",
"disabled_units": "禁用单位"
},
"public_lobby": {
"join": "加入下一场游戏",
"waiting": "等待中的玩家",
"teams_Duos": "/ 22人小队",
"teams_Trios": "/ 33人小队",
"teams_Quads": "/ 44人小队",
"teams_Duos": "{team_count} 个 2 人小队",
"teams_Trios": "{team_count} 个 3 人小队",
"teams_Quads": "{team_count} 个 4 人小队",
"waiting_for_players": "正在等待玩家",
"starting_game": "正在启动游戏……",
"teams_hvn": "人类 VS 国家",
"teams_hvn_detailed": "{num} 个人类 VS {num} 个国家",
"teams": "{num} 个队伍",
"players_per_team": "/ {num}"
"players_per_team": "每队 {num} 人",
"started": "已开始"
},
"matchmaking_modal": {
"title": "匹配中",
"title": "1v1 排位赛(内测版)",
"connecting": "正在连接到匹配服务器……",
"searching": "正在搜索游戏……",
"waiting_for_game": "正在等待游戏开始……"
"waiting_for_game": "正在等待游戏开始……",
"elo": "你的 ELO 分:{elo}"
},
"username": {
"enter_username": "输入用户名",
"not_string": "用户名必须是字符串。",
"too_short": "用户名最少包含 {min} 个字符。",
"too_long": "用户名不得超过 {max} 个字符。",
"invalid_chars": "用户名只能包含字母、数字、空格、下划线和 [方括号]。"
"invalid_chars": "用户名只能包含字母、数字、空格、下划线和 [方括号]。",
"tag": "标签",
"tag_too_short": "战队标签必须是 2-5 位字母或数字字符。",
"tag_invalid_chars": "战队标签只能包含字母和数字。"
},
"host_modal": {
"title": "私人房间",
"title": "创建私人房间",
"label": "私有",
"mode": "模式",
"team_count": "队伍数量",
"team_type": "队伍类型",
"options_title": "选项",
"bots": "机器人: ",
"bots_disabled": "禁用",
"player_immunity_duration": "PVP 豁免期限(分钟)",
"nations": "国家:",
"disable_nations": "禁用国家",
"max_timer": "游戏时长(分钟)",
"mins_placeholder": "分钟",
"instant_build": "立即建造",
"infinite_gold": "无限金钱",
"donate_gold": "捐赠金币",
@@ -283,7 +375,11 @@
"assigned_teams": "已分配的队伍",
"empty_teams": "空队伍",
"empty_team": "空",
"remove_player": "移除 {username}"
"remove_player": "移除 {username}",
"teams_Duos": "2人小队",
"teams_Trios": "3人小队",
"teams_Quads": "4人小队",
"teams_Humans Vs Nations": "人类 VS 国家"
},
"team_colors": {
"red": "红色",
@@ -301,16 +397,20 @@
"code_license": "代码采用 AGPL-3.0 许可证授权(无担保)"
},
"difficulty": {
"difficulty": "难度",
"Easy": "休闲",
"Medium": "平衡",
"Hard": "困难",
"Impossible": "地狱"
"difficulty": "国家难度",
"easy": "简单",
"medium": "中等",
"hard": "困难",
"impossible": "地狱"
},
"game_mode": {
"ffa": "混战",
"teams": "团队"
},
"public_game_modifier": {
"random_spawn": "随机出生点",
"compact_map": "紧凑地图"
},
"select_lang": {
"title": "选择语言"
},
@@ -327,7 +427,7 @@
"factory": "工厂"
},
"user_setting": {
"title": "用户设置",
"title": "设置",
"tab_basic": "基本设置",
"tab_keybinds": "热键绑定",
"dark_mode_label": "深色模式",
@@ -340,16 +440,18 @@
"special_effects_desc": "切换特效开关。停用以改进性能",
"structure_sprites_label": "建筑贴图",
"structure_sprites_desc": "切换建筑贴图",
"cursor_cost_label_label": "建造按钮显示消耗",
"cursor_cost_label_desc": "在建造按钮下显示花费",
"anonymous_names_label": "隐藏的名称",
"anonymous_names_desc": "将真实玩家名字替换为随机名字。",
"lobby_id_visibility_label": "隐藏的房间ID",
"lobby_id_visibility_desc": "在创建私人房间时隐藏房间ID",
"toggle_visibility": "切换是否可见",
"left_click_label": "左键单击打开菜单",
"left_click_desc": "开启时,先左键单击打开菜单,然后再点进攻。关闭时,左键将直接进攻。",
"left_click_menu": "左键点击菜单",
"attack_ratio_label": "⚔️ 攻击比例",
"attack_ratio_desc": "你要派出多少比例的军队进攻 (1100%)",
"troop_ratio_desc": "调整军队 (用于战斗) 和工人 (用于生产黄金) 之间的比例 (1-100%)",
"territory_patterns_label": "🏳️ 领土皮肤",
"territory_patterns_desc": "选择是否在游戏中显示领土皮肤",
"performance_overlay_label": "性能叠层",
@@ -358,6 +460,7 @@
"easter_writing_speed_desc": "调节你“假装写代码”的速度 (x1x100)",
"easter_bug_count_label": "Bug 计数",
"easter_bug_count_desc": "你能接受多少个 Bug (01000,心理承受范围)",
"press_a_key": "按下一个按键",
"view_options": "视图选项",
"toggle_view": "切换视图",
"toggle_view_desc": "备选视图 (地形/国家)",
@@ -382,6 +485,11 @@
"build_hydrogen_bomb_desc": "向鼠标位置发射氢弹。",
"build_mirv": "发射 MIRV",
"build_mirv_desc": "向鼠标位置发射 MIRV。",
"menu_shortcuts": "菜单快捷键",
"build_menu_modifier": "建造菜单编辑器",
"build_menu_modifier_desc": "按住此键并点击以打开建造菜单。",
"emoji_menu_modifier": "Emoji 表情菜单编辑器",
"emoji_menu_modifier_desc": "按住此键并点击以打开 Emoji 表情菜单。",
"attack_ratio_controls": "攻击比例控制",
"attack_ratio_up": "增加攻击比例",
"attack_ratio_up_desc": "增加 10% 攻击比例",
@@ -392,6 +500,8 @@
"boat_attack_desc": "向鼠标所指地块发送船只攻击。",
"ground_attack": "对地攻击",
"ground_attack_desc": "向鼠标所指地块发送船只攻击。",
"swap_direction": "调换火箭方向",
"swap_direction_desc": "切换火箭发射方向(上/下)。",
"zoom_controls": "缩放控制",
"zoom_out": "缩小",
"zoom_out_desc": "缩小地图",
@@ -416,7 +526,8 @@
"exit_game_label": "退出游戏",
"exit_game_info": "返回主菜单",
"background_music_volume": "背景音量",
"sound_effects_volume": "音效音量"
"sound_effects_volume": "音效音量",
"keybind_conflict_error": "按键 {key} 已经绑定到另一动作上了。"
},
"chat": {
"title": "快捷聊天",
@@ -529,6 +640,7 @@
"other_team": "{team} 队获胜了!",
"you_won": "你获胜了!",
"other_won": "{player} 获胜了!",
"nation_won": "国家 {nation} 获胜了!",
"exit": "退出游戏",
"keep": "继续游戏",
"spectate": "观战",
@@ -537,7 +649,7 @@
"ofm_winter_description": "加入竞技比赛,与最强玩家一较高下",
"join_tournament": "加入比赛",
"join_discord": "加入我们的 Discord 社区!",
"discord_description": "与其他玩家交流,获取最新消息,分享游戏战略",
"discord_description": "与玩家交流,发现新功能,并赢取奖品!",
"join_server": "加入服务器",
"youtube_tutorial": "需要帮助吗?"
},
@@ -549,7 +661,7 @@
"team": "队伍",
"owned": "已占领",
"gold": "黄金",
"troops": "军队",
"maxtroops": "最大军队",
"launchers": "导弹发射井",
"sams": "防空塔",
"warships": "军舰",
@@ -565,6 +677,7 @@
"team": "队伍",
"alliance_timeout": "结盟剩余时长",
"troops": "军队",
"maxtroops": "最大军队",
"a_troops": "进攻军队",
"gold": "黄金",
"ports": "港口",
@@ -575,7 +688,9 @@
"warships": "军舰",
"health": "生命值",
"attitude": "态度",
"levels": "等级"
"levels": "等级",
"wilderness_title": "荒野",
"irradiated_wilderness_title": "受辐射的荒野"
},
"events_display": {
"retreating": "正在撤退",
@@ -601,7 +716,20 @@
"wants_to_renew_alliance": "{name} 想与你续签盟约",
"ignore": "忽略",
"unit_voluntarily_deleted": "单位已自毁",
"betrayal_debuff_ends": "距离背叛减益效果结束还剩 {time} 秒"
"betrayal_debuff_ends": "距离背叛减益效果结束还剩 {time} 秒",
"attack_cancelled_retreat": "已取消进攻,在撤退时损失了 {troops} 兵力",
"received_gold_from_captured_ship": "捕获了 {name} 的商船,获得 {gold} 黄金",
"received_gold_from_trade": "与 {name} 贸易获得了 {gold} 黄金",
"missile_intercepted": "已拦截导弹 {unit}",
"mirv_warheads_intercepted": "{count, plural, one {{count} 个 MIRV 弹头被拦截} other {{count} 个 MIRV 弹头被拦截}}",
"sent_troops_to_player": "已向 {name} 发送 {troops} 军队",
"received_troops_from_player": "已从 {name} 收到 {troops} 军队",
"sent_gold_to_player": "已向 {name} 发送 {gold} 黄金",
"received_gold_from_player": "已从 {name} 收到 {gold} 黄金",
"unit_captured_by_enemy": "你的 {unit} 被 {name} 捕获",
"captured_enemy_unit": "已捕获 {name} 的 {unit}",
"unit_destroyed": "你的 {unit} 已被摧毁",
"no_boats_available": "无可用船,最多 {max} 个"
},
"unit_info_modal": {
"structure_info": "建筑信息",
@@ -653,7 +781,10 @@
"send_alliance": "请求结盟",
"send_troops": "发送军队",
"send_gold": "发送黄金",
"emotes": "表情符号"
"emotes": "表情符号",
"arc_up": "向上的弧",
"arc_down": "向下的弧",
"flip_rocket_trajectory": "翻转火箭轨道"
},
"send_troops_modal": {
"title_with_name": "向 {name} 发送军队",
@@ -702,20 +833,26 @@
},
"heads_up_message": {
"choose_spawn": "选择出生点",
"random_spawn": "随机出生点已启用。正在为你选择出生点……"
"random_spawn": "随机出生点已启用。正在为你选择出生点……",
"singleplayer_game_paused": "游戏已暂停",
"multiplayer_game_paused": "游戏已被房主暂停"
},
"territory_patterns": {
"title": "皮肤",
"colors": "颜色",
"purchase": "购买",
"show_only_owned": "我的皮肤",
"all_owned": "您已拥有所有皮肤!请稍后再来查看新皮肤。",
"not_logged_in": "未登录",
"blocked": {
"login": "您必须登录才能使用此皮肤。",
"purchase": "购买以解锁此皮肤。"
},
"pattern": {
"default": "默认"
}
},
"select_skin": "选择皮肤",
"selected": "已选择"
},
"flag_input": {
"title": "选择旗帜",
@@ -786,8 +923,9 @@
"mode": "模式",
"mode_ffa": "混战",
"mode_team": "团队",
"view": "查看",
"replay": "回放",
"details": "详情",
"ranking": "排行",
"started": "已开始",
"map": "地图",
"difficulty": "难度",
@@ -796,13 +934,20 @@
"player_stats_tree": {
"public": "公开",
"private": "私有",
"singleplayer": "单人玩家",
"singleplayer": "单人模式",
"mode": "模式",
"stats_wins": "胜场数",
"stats_losses": "败场数",
"stats_wlr": "胜败比",
"stats_games_played": "游戏场数",
"mode_ffa": "混战",
"mode_team": "团队"
"mode_team": "团队",
"no_stats": "所选项没有统计记录。"
},
"matchmaking_button": {
"play_ranked": "1v1 排位赛",
"description": "(内测版)",
"login_required": "登录后开始排位赛!",
"must_login": "您必须登录才能玩排位赛。"
}
}
+85
View File
@@ -0,0 +1,85 @@
{
"map": {
"height": 2048,
"num_land_tiles": 4194304,
"width": 2048
},
"map16x": {
"height": 512,
"num_land_tiles": 262144,
"width": 512
},
"map4x": {
"height": 1024,
"num_land_tiles": 1048576,
"width": 1024
},
"name": "The Box",
"nations": [
{
"coordinates": [10, 10],
"flag": "",
"name": "King of the Corner"
},
{
"coordinates": [1024, 300],
"flag": "",
"name": "Suspicious Ally"
},
{
"coordinates": [1650, 400],
"flag": "",
"name": "Evan The Dev"
},
{
"coordinates": [1024, 1024],
"flag": "",
"name": "Middle Defender"
},
{
"coordinates": [350, 1024],
"flag": "",
"name": "Punch Merchant"
},
{
"coordinates": [1700, 1024],
"flag": "",
"name": "Nuke Thrower"
},
{
"coordinates": [400, 1650],
"flag": "",
"name": "Fullsender"
},
{
"coordinates": [1024, 1750],
"flag": "",
"name": "Factory Builder"
},
{
"coordinates": [1650, 1650],
"flag": "",
"name": "Front Manager"
},
{
"coordinates": [700, 700],
"flag": "",
"name": "Box Fighter"
},
{
"coordinates": [1350, 700],
"flag": "",
"name": "Cage Liberator"
},
{
"coordinates": [700, 1350],
"flag": "",
"name": "Train Trader"
},
{
"coordinates": [1350, 1350],
"flag": "",
"name": "Non-peaceful Bot"
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

+34 -26
View File
@@ -47,34 +47,42 @@ export async function fetchPlayerById(
return false;
}
}
export async function getUserMe(): Promise<UserMeResponse | false> {
try {
const userAuthResult = await userAuth();
if (!userAuthResult) return false;
const { jwt } = userAuthResult;
// Get the user object
const response = await fetch(getApiBase() + "/users/@me", {
headers: {
authorization: `Bearer ${jwt}`,
},
});
if (response.status === 401) {
await logOut();
return false;
}
if (response.status !== 200) return false;
const body = await response.json();
const result = UserMeResponseSchema.safeParse(body);
if (!result.success) {
const error = z.prettifyError(result.error);
console.error("Invalid response", error);
return false;
}
return result.data;
} catch (e) {
return false;
let __userMe: Promise<UserMeResponse | false> | null = null;
export async function getUserMe(): Promise<UserMeResponse | false> {
if (__userMe !== null) {
return __userMe;
}
__userMe = (async () => {
try {
const userAuthResult = await userAuth();
if (!userAuthResult) return false;
const { jwt } = userAuthResult;
// Get the user object
const response = await fetch(getApiBase() + "/users/@me", {
headers: {
authorization: `Bearer ${jwt}`,
},
});
if (response.status === 401) {
await logOut();
return false;
}
if (response.status !== 200) return false;
const body = await response.json();
const result = UserMeResponseSchema.safeParse(body);
if (!result.success) {
const error = z.prettifyError(result.error);
console.error("Invalid response", error);
return false;
}
return result.data;
} catch (e) {
return false;
}
})();
return __userMe;
}
export async function createCheckoutSession(
+45 -16
View File
@@ -29,23 +29,52 @@ export async function handlePurchase(
window.location.href = url;
}
export async function fetchCosmetics(): Promise<Cosmetics | null> {
try {
const response = await fetch(`${getApiBase()}/cosmetics.json`);
if (!response.ok) {
console.error(`HTTP error! status: ${response.status}`);
return null;
}
const result = CosmeticsSchema.safeParse(await response.json());
if (!result.success) {
console.error(`Invalid cosmetics: ${result.error.message}`);
return null;
}
return result.data;
} catch (error) {
console.error("Error getting cosmetics:", error);
return null;
let __cosmetics: Promise<Cosmetics | null> | null = null;
let __cosmeticsHash: string | null = null;
function simpleHash(str: string): string {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return hash.toString(36);
}
export async function fetchCosmetics(): Promise<Cosmetics | null> {
if (__cosmetics !== null) {
return __cosmetics;
}
__cosmetics = (async () => {
try {
const response = await fetch(`${getApiBase()}/cosmetics.json`);
if (!response.ok) {
console.error(`HTTP error! status: ${response.status}`);
return null;
}
const result = CosmeticsSchema.safeParse(await response.json());
if (!result.success) {
console.error(`Invalid cosmetics: ${result.error.message}`);
return null;
}
const patternKeys = Object.keys(result.data.patterns).sort();
const hashInput = patternKeys
.map((k) => k + (result.data.patterns[k].product ? "sale" : ""))
.join(",");
__cosmeticsHash = simpleHash(hashInput);
return result.data;
} catch (error) {
console.error("Error getting cosmetics:", error);
return null;
}
})();
return __cosmetics;
}
export async function getCosmeticsHash(): Promise<string | null> {
await fetchCosmetics();
return __cosmeticsHash;
}
export function patternRelationship(
+155 -6
View File
@@ -3,6 +3,28 @@ declare global {
CrazyGames?: {
SDK: {
init: () => Promise<void>;
user: {
getUser(): Promise<{
username: string;
} | null>;
addAuthListener: (
listener: (
user: {
username: string;
} | null,
) => void,
) => void;
};
ad: {
requestAd: (
adType: string,
callbacks: {
adStarted: () => void;
adFinished: () => void;
adError: (error: any) => void;
},
) => void;
};
game: {
gameplayStart: () => Promise<void>;
gameplayStop: () => Promise<void>;
@@ -14,7 +36,9 @@ declare global {
[key: string]: string | number;
}) => string;
hideInviteButton: () => void;
inviteLink: (params: { [key: string]: string | number }) => string;
getInviteParam: (paramName: string) => string | null;
isInstantMultiplayer?: boolean;
};
};
};
@@ -24,6 +48,24 @@ declare global {
export class CrazyGamesSDK {
private initialized = false;
private isGameplayActive = false;
private readyPromise: Promise<void>;
private resolveReady!: () => void;
constructor() {
this.readyPromise = new Promise((resolve) => {
this.resolveReady = resolve;
});
}
async ready(): Promise<boolean> {
const timeout = new Promise<boolean>((resolve) => {
setTimeout(() => resolve(false), 3000);
});
const ready = this.readyPromise.then(() => true);
return Promise.race([ready, timeout]);
}
isOnCrazyGames(): boolean {
try {
@@ -34,9 +76,17 @@ export class CrazyGamesSDK {
}
return false;
} catch (e) {
console.log("[CrazyGames]: ", e);
// If we get a cross-origin error, we're definitely iframed
// Check our own referrer as fallback
return document.referrer.includes("crazygames");
const isCrazyGames = document.referrer.includes("crazygames");
console.log("[CrazyGames], contains referrer: ", isCrazyGames);
if (isCrazyGames) {
return true;
}
// Fallback: on safari private we can't get referrer, so just assume we are in crazygames if in iframe
return window.self !== window.top;
}
}
@@ -70,12 +120,63 @@ export class CrazyGamesSDK {
try {
await window.CrazyGames.SDK.init();
this.initialized = true;
this.resolveReady();
console.log("CrazyGames SDK initialized");
} catch (error) {
console.error("Failed to initialize CrazyGames SDK:", error);
}
}
async getUsername(): Promise<string | null> {
const isReady = await this.ready();
if (!isReady) {
return null;
}
try {
return (await window.CrazyGames!.SDK.user.getUser())?.username ?? null;
} catch (e) {
console.log("error getting CrazyGames username: ", e);
return null;
}
}
async addAuthListener(
listener: (
user: {
username: string;
} | null,
) => void,
): Promise<void> {
if (!(await this.ready())) {
return;
}
try {
console.log("registering CrazyGames auth listener");
window.CrazyGames!.SDK.user.addAuthListener(listener);
} catch (error) {
console.error("Failed to add auth listener:", error);
}
}
async isInstantMultiplayer(): Promise<boolean> {
const isReady = await this.ready();
if (!isReady) {
return false;
}
const gameId = await this.getInviteGameId();
if (gameId !== null) {
// Game id exists, meaning we are joining the game, not hosting it.
return false;
}
try {
return window.CrazyGames!.SDK.game.isInstantMultiplayer ?? false;
} catch (e) {
console.log("Error getting instant multiplayer: ", e);
return false;
}
}
async gameplayStart(): Promise<void> {
if (!this.isReady()) {
return;
@@ -156,7 +257,6 @@ export class CrazyGamesSDK {
if (!this.isReady()) {
return null;
}
try {
const options: {
gameId: string | number;
@@ -165,6 +265,9 @@ export class CrazyGamesSDK {
gameId,
};
const link = window.CrazyGames!.SDK.game.showInviteButton(options);
// Store the game so we know that we are host. This way when player refreshes page,
// It won't show up as "joining" a game we created.
localStorage.setItem(gameId, "true");
console.log("CrazyGames: invite button shown, link:", link);
return link;
} catch (error) {
@@ -186,20 +289,66 @@ export class CrazyGamesSDK {
}
}
getInviteGameId(): string | null {
createInviteLink(gameId: string): string | null {
if (!this.isReady()) {
console.warn("CrazyGames SDK not ready, cannot create invite link");
return null;
}
try {
const value = window.CrazyGames!.SDK.game.getInviteParam("gameId");
console.log(`CrazyGames: got invite gameId:`, value);
return value;
const link = window.CrazyGames!.SDK.game.inviteLink({ gameId });
console.log("CrazyGames: created invite link:", link);
return link;
} catch (error) {
console.error("Failed to create invite link:", error);
return null;
}
}
async getInviteGameId(): Promise<string | null> {
if (!(await this.ready())) {
return null;
}
try {
const gameId = window.CrazyGames!.SDK.game.getInviteParam("gameId");
if (gameId) {
console.log("[CrazyGames] found invite link", gameId);
// We already created this game, can't join a game we created.
return localStorage.getItem(gameId) === "true" ? null : gameId;
}
return null;
} catch (error) {
console.error(`Failed to get invite gameId:`, error);
return null;
}
}
requestMidgameAd(): Promise<void> {
return new Promise((resolve) => {
if (!this.isReady()) {
resolve();
return;
}
try {
const callbacks = {
adFinished: () => {
console.log("End midgame ad");
resolve();
},
adError: (error: any) => {
console.log("Error midgame ad", error);
resolve();
},
adStarted: () => console.log("Start midgame ad"),
};
window.CrazyGames!.SDK.ad.requestAd("midgame", callbacks);
} catch (error) {
console.error("Failed to request midgame ad:", error);
resolve();
}
});
}
}
export const crazyGamesSDK = new CrazyGamesSDK();
+71 -81
View File
@@ -1,70 +1,46 @@
import { LitElement, html } from "lit";
import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { isInIframe } from "./Utils";
const LEFT_FUSE = "gutter-ad-container-left";
const RIGHT_FUSE = "gutter-ad-container-right";
// Minimum screen width to show ads (larger than typical Chromebook)
const MIN_SCREEN_WIDTH = 1400;
@customElement("gutter-ads")
export class GutterAds extends LitElement {
@state()
private isVisible: boolean = false;
@state()
private adLoaded: boolean = false;
private leftAdType: string = "standard_iab_left2";
private rightAdType: string = "standard_iab_rght1";
private leftContainerId: string = "gutter-ad-container-left";
private rightContainerId: string = "gutter-ad-container-right";
private margin: string = "10px";
// Override createRenderRoot to disable shadow DOM
createRenderRoot() {
return this;
}
private readonly boundUserMeHandler = (event: Event) =>
this.onUserMe((event as CustomEvent<UserMeResponse | false>).detail);
static styles = css``;
connectedCallback() {
super.connectedCallback();
document.addEventListener(
"userMeResponse",
this.boundUserMeHandler as EventListener,
);
}
private onUserMe(userMeResponse: UserMeResponse | false): void {
const flares =
userMeResponse === false ? [] : (userMeResponse.player.flares ?? []);
const hasFlare = flares.some((flare) => flare.startsWith("pattern:"));
if (hasFlare) {
console.log("No ads because you have patterns");
window.enableAds = false;
} else {
console.log("No flares, showing ads");
this.show();
window.enableAds = true;
}
}
private isScreenLargeEnough(): boolean {
return window.innerWidth >= MIN_SCREEN_WIDTH;
document.addEventListener("userMeResponse", () => {
if (window.adsEnabled) {
console.log("showing gutter ads");
this.show();
} else {
console.log("not showing gutter ads");
}
});
}
// Called after the component's DOM is first rendered
firstUpdated() {
// DOM is guaranteed to be available here
console.log("GutterAd DOM is ready");
console.log("GutterAdModal DOM is ready");
}
public show(): void {
if (!this.isScreenLargeEnough()) {
console.log("Screen too small for gutter ads, skipping");
return;
}
if (isInIframe()) {
console.log("In iframe, showing gutter ads");
return;
}
console.log("showing GutterAds");
this.isVisible = true;
this.requestUpdate();
@@ -74,58 +50,57 @@ export class GutterAds extends LitElement {
});
}
public hide(): void {
this.isVisible = false;
console.log("hiding GutterAds");
this.destroyAds();
document.removeEventListener(
"userMeResponse",
this.boundUserMeHandler as EventListener,
);
this.requestUpdate();
}
private loadAds(): void {
console.log("loading ramp ads");
// Ensure the container elements exist before loading ads
const leftContainer = this.querySelector(`#${LEFT_FUSE}`);
const rightContainer = this.querySelector(`#${RIGHT_FUSE}`);
const leftContainer = this.querySelector(`#${this.leftContainerId}`);
const rightContainer = this.querySelector(`#${this.rightContainerId}`);
if (!leftContainer || !rightContainer) {
console.warn("Ad containers not found in DOM");
return;
}
if (!window.fusetag) {
console.warn("Fuse tag not available");
if (!window.ramp) {
console.warn("Playwire RAMP not available");
return;
}
if (this.adLoaded) {
console.log("Ads already loaded, skipping");
return;
}
try {
console.log("registering zones");
window.fusetag.que.push(() => {
window.fusetag.registerZone(LEFT_FUSE);
window.fusetag.registerZone(RIGHT_FUSE);
window.ramp.que.push(() => {
try {
window.ramp.spaAddAds([
{
type: this.leftAdType,
selectorId: this.leftContainerId,
},
{
type: this.rightAdType,
selectorId: this.rightContainerId,
},
]);
this.adLoaded = true;
console.log(
"Playwire ads loaded:",
this.leftAdType,
this.rightAdType,
);
} catch (e) {
console.log(e);
}
});
} catch (error) {
console.error("Failed to load fuse ads:", error);
this.hide();
console.error("Failed to load Playwire ads:", error);
}
}
private destroyAds(): void {
if (!window.fusetag) {
return;
}
window.fusetag.que.push(() => {
window.fusetag.destroyZone(LEFT_FUSE);
window.fusetag.destroyZone(RIGHT_FUSE);
});
this.requestUpdate();
}
disconnectedCallback() {
super.disconnectedCallback();
this.hide();
}
render() {
@@ -134,11 +109,26 @@ export class GutterAds extends LitElement {
}
return html`
<div class="fixed left-0 top-1/2 -translate-y-1/2 z-10">
<div id="${LEFT_FUSE}" data-fuse="lhs_sticky_vrec"></div>
<!-- Left Gutter Ad -->
<div
class="hidden xl:flex fixed left-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] z-[100] pointer-events-auto items-center justify-center"
style="margin-left: ${this.margin};"
>
<div
id="${this.leftContainerId}"
class="w-full h-full flex items-center justify-center p-2"
></div>
</div>
<div class="fixed right-0 top-1/2 -translate-y-1/2 z-10">
<div id="${RIGHT_FUSE}" data-fuse="rhs_sticky_vrec"></div>
<!-- Right Gutter Ad -->
<div
class="hidden xl:flex fixed right-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] z-[100] pointer-events-auto items-center justify-center"
style="margin-right: ${this.margin};"
>
<div
id="${this.rightContainerId}"
class="w-full h-full flex items-center justify-center p-2"
></div>
</div>
`;
}
+55 -2
View File
@@ -1,6 +1,6 @@
import { html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
import { customElement, query, state } from "lit/decorators.js";
import { translateText, TUTORIAL_VIDEO_URL } from "../client/Utils";
import { BaseModal } from "./components/BaseModal";
import "./components/Difficulties";
import { modalHeader } from "./components/ui/ModalHeader";
@@ -9,6 +9,7 @@ import { TroubleshootingModal } from "./TroubleshootingModal";
@customElement("help-modal")
export class HelpModal extends BaseModal {
@state() private keybinds: Record<string, string> = this.getKeybinds();
@query("#tutorial-video-iframe") private videoIframe?: HTMLIFrameElement;
private isKeybindObject(v: unknown): v is { value: string } {
return (
@@ -120,6 +121,47 @@ export class HelpModal extends BaseModal {
[&_p]:text-gray-300 [&_p]:mb-3 [&_strong]:text-white [&_strong]:font-bold
scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent"
>
<!-- Video Tutorial Section -->
<div class="flex items-center gap-3 mb-3">
<div class="text-blue-400">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polygon points="5 3 19 12 5 21 5 3"></polygon>
</svg>
</div>
<h3
class="text-xl font-bold uppercase tracking-widest text-white/90"
>
${translateText("help_modal.video_tutorial")}
</h3>
<div
class="flex-1 h-px bg-gradient-to-r from-blue-500/50 to-transparent"
></div>
</div>
<section
class="bg-white/5 rounded-xl border border-white/10 overflow-hidden mb-8"
>
<div class="relative w-full h-0 pb-[56.25%]">
<iframe
id="tutorial-video-iframe"
class="absolute top-0 left-0 w-full h-full"
src="${TUTORIAL_VIDEO_URL}"
title="${translateText("help_modal.video_tutorial_title")}"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
</div>
</section>
<!-- Troubleshooting Section -->
<div class="flex items-center gap-3 mb-3">
<div class="text-blue-400">
@@ -1200,5 +1242,16 @@ export class HelpModal extends BaseModal {
protected onOpen(): void {
this.keybinds = this.getKeybinds();
// Restore the video src when modal opens
if (this.videoIframe) {
this.videoIframe.src = TUTORIAL_VIDEO_URL;
}
}
protected onClose(): void {
// Clear the iframe src to stop video playback
if (this.videoIframe) {
this.videoIframe.src = "";
}
}
}
+9 -1
View File
@@ -115,6 +115,12 @@ export class HostLobbyModal extends BaseModal {
}
private async buildLobbyUrl(): Promise<string> {
if (crazyGamesSDK.isOnCrazyGames()) {
const link = crazyGamesSDK.createInviteLink(this.lobbyId);
if (link !== null) {
return link;
}
}
const config = await getServerConfigFromClient();
return `${window.location.origin}/${config.workerPath(this.lobbyId)}/game/${this.lobbyId}?lobby&s=${encodeURIComponent(this.lobbyUrlSuffix)}`;
}
@@ -125,7 +131,9 @@ export class HostLobbyModal extends BaseModal {
}
private updateHistory(url: string): void {
history.replaceState(null, "", url);
if (!crazyGamesSDK.isOnCrazyGames()) {
history.replaceState(null, "", url);
}
}
render() {
+1
View File
@@ -228,6 +228,7 @@ export class LangSelector extends LitElement {
"stats-modal",
"flag-input-modal",
"flag-input",
"matchmaking-button",
"token-login",
];
+79 -92
View File
@@ -43,7 +43,7 @@ import {
} from "./Transport";
import { UserSettingModal } from "./UserSettingModal";
import "./UsernameInput";
import { UsernameInput } from "./UsernameInput";
import { genAnonUsername, UsernameInput } from "./UsernameInput";
import {
getDiscordAvatarUrl,
incrementGamesPlayed,
@@ -163,19 +163,12 @@ declare global {
GIT_COMMIT: string;
INSTANCE_ID: string;
turnstile: any;
enableAds: boolean;
adsEnabled: boolean;
PageOS: {
session: {
newPageView: () => void;
};
};
fusetag: {
registerZone: (id: string) => void;
destroyZone: (id: string) => void;
pageInit: (options?: any) => void;
que: Array<() => void>;
destroySticky: () => void;
};
ramp: {
que: Array<() => void>;
passiveMode: boolean;
@@ -184,7 +177,25 @@ declare global {
settings?: {
slots?: any;
};
spaNewPage: (url: string) => void;
spaNewPage: (url?: string) => void;
// Video ad methods
onPlayerReady: (() => void) | null;
addUnits: (units: Array<{ type: string }>) => Promise<void>;
displayUnits: () => void;
};
Bolt: {
on: (unitType: string, event: string, callback: () => void) => void;
BOLT_AD_REQUEST_START: string;
BOLT_AD_IMPRESSION: string;
BOLT_AD_STARTED: string;
BOLT_FIRST_QUARTILE: string;
BOLT_MIDPOINT: string;
BOLT_THIRD_QUARTILE: string;
BOLT_AD_COMPLETE: string;
BOLT_AD_ERROR: string;
BOLT_AD_PAUSED: string;
BOLT_AD_CLICKED: string;
SHOW_HIDDEN_CONTAINER: string;
};
showPage?: (pageId: string) => void;
}
@@ -216,6 +227,7 @@ class Client {
private usernameInput: UsernameInput | null = null;
private flagInput: FlagInput | null = null;
private hostModal: HostPrivateLobbyModal;
private joinModal: JoinPrivateLobbyModal;
private publicLobby: PublicLobby;
private userSettings: UserSettings = new UserSettings();
@@ -433,57 +445,14 @@ class Client {
) {
console.warn("Matchmaking modal element not found");
}
const matchmakingButton = document.getElementById("matchmaking-button");
const matchmakingButtonLoggedOut = document.getElementById(
"matchmaking-button-logged-out",
);
const updateMatchmakingButton = (loggedIn: boolean) => {
if (!loggedIn) {
matchmakingButton?.classList.add("hidden");
matchmakingButtonLoggedOut?.classList.remove("hidden");
} else {
matchmakingButton?.classList.remove("hidden");
matchmakingButtonLoggedOut?.classList.add("hidden");
}
};
if (matchmakingButton) {
matchmakingButton.addEventListener("click", () => {
if (this.usernameInput?.isValid()) {
window.showPage?.("page-matchmaking");
this.publicLobby.leaveLobby();
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: this.usernameInput?.validationError,
color: "red",
duration: 3000,
},
}),
);
}
});
}
if (matchmakingButtonLoggedOut) {
matchmakingButtonLoggedOut.addEventListener("click", () => {
window.showPage?.("page-account");
});
}
const onUserMe = async (userMeResponse: UserMeResponse | false) => {
// Check if user has actual authentication (discord or email), not just a publicId
const loggedIn =
userMeResponse !== false &&
userMeResponse !== null &&
typeof userMeResponse === "object" &&
userMeResponse.user &&
(userMeResponse.user.discord !== undefined ||
userMeResponse.user.email !== undefined);
updateMatchmakingButton(loggedIn);
updateAccountNavButton(userMeResponse);
const hasLinkedAccount =
!crazyGamesSDK.isOnCrazyGames() &&
((userMeResponse || null)?.player?.flares?.length ?? 0) > 0;
console.log("ads enabled: ", hasLinkedAccount);
window.adsEnabled = !hasLinkedAccount && !crazyGamesSDK.isOnCrazyGames();
document.dispatchEvent(
new CustomEvent("userMeResponse", {
detail: userMeResponse,
@@ -524,10 +493,10 @@ class Client {
}
});
const hostModal = document.querySelector(
this.hostModal = document.querySelector(
"host-lobby-modal",
) as HostPrivateLobbyModal;
if (!hostModal || !(hostModal instanceof HostPrivateLobbyModal)) {
if (!this.hostModal || !(this.hostModal instanceof HostPrivateLobbyModal)) {
console.warn("Host private lobby modal element not found");
}
const hostLobbyButton = document.getElementById("host-lobby-button");
@@ -583,7 +552,11 @@ class Client {
}
// Attempt to join lobby
this.handleUrl();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => this.handleUrl());
} else {
this.handleUrl();
}
const onHashUpdate = () => {
// Reset the UI to its initial state
@@ -653,21 +626,38 @@ class Client {
updateSliderProgress(slider);
slider.addEventListener("input", () => updateSliderProgress(slider));
});
this.initializeFuseTag();
}
private handleUrl() {
private async handleUrl() {
// Wait for modal custom elements to be defined
await Promise.all([
customElements.whenDefined("join-private-lobby-modal"),
customElements.whenDefined("host-lobby-modal"),
]);
// Check if CrazyGames SDK is enabled first (no hash needed in CrazyGames)
if (crazyGamesSDK.isOnCrazyGames()) {
const lobbyId = crazyGamesSDK.getInviteGameId();
const lobbyId = await crazyGamesSDK.getInviteGameId();
console.log("got game id", lobbyId);
if (lobbyId && GAME_ID_REGEX.test(lobbyId)) {
console.log("game parsed successfully");
// Wait 2 seconds to ensure all elements are actually loaded,
// On low end-chromebooks the join modal was not registered in time.
await new Promise((resolve) => setTimeout(resolve, 2000));
window.showPage?.("page-join-private-lobby");
this.joinModal?.open(lobbyId);
console.log(`CrazyGames: joining lobby ${lobbyId} from invite param`);
return;
}
}
crazyGamesSDK.isInstantMultiplayer().then((isInstant) => {
if (isInstant) {
console.log(
`CrazyGames: joining instant multiplayer lobby from CrazyGames`,
);
this.hostModal.open();
}
});
const strip = () =>
history.replaceState(
@@ -790,7 +780,8 @@ class Client {
: this.flagInput.getCurrentFlag(),
},
turnstileToken: await this.getTurnstileToken(lobby),
playerName: this.usernameInput?.getCurrentUsername() ?? "",
playerName:
this.usernameInput?.getCurrentUsername() ?? genAnonUsername(),
clientID: lobby.clientID,
gameStartInfo: lobby.gameStartInfo ?? lobby.gameRecord?.info,
gameRecord: lobby.gameRecord,
@@ -848,7 +839,6 @@ class Client {
if (startingModal && startingModal instanceof GameStartingModal) {
startingModal.show();
}
this.gutterAds.hide();
},
() => {
this.joinModal.close();
@@ -859,6 +849,9 @@ class Client {
(ad as HTMLElement).style.display = "none";
});
if (window.PageOS?.session?.newPageView) {
window.PageOS.session.newPageView();
}
crazyGamesSDK.loadingStop();
crazyGamesSDK.gameplayStart();
document.body.classList.add("in-game");
@@ -909,8 +902,6 @@ class Client {
document.body.classList.remove("in-game");
crazyGamesSDK.gameplayStop();
this.gutterAds.hide();
this.publicLobby.leaveLobby();
}
@@ -932,28 +923,6 @@ class Client {
}
}
private initializeFuseTag() {
const tryInitFuseTag = (): boolean => {
if (window.fusetag && typeof window.fusetag.pageInit === "function") {
console.log("initializing fuse tag");
window.fusetag.que.push(() => {
window.fusetag.pageInit({
blockingFuseIds: ["lhs_sticky_vrec", "rhs_sticky_vrec"],
});
});
return true;
} else {
return false;
}
};
const interval = setInterval(() => {
if (tryInitFuseTag()) {
clearInterval(interval);
}
}, 100);
}
private async getTurnstileToken(
lobby: JoinLobbyEvent,
): Promise<string | null> {
@@ -965,7 +934,8 @@ class Client {
return null;
}
if (this.turnstileTokenPromise === null) {
// Always request a new token on crazygames.
if (this.turnstileTokenPromise === null || crazyGamesSDK.isOnCrazyGames()) {
console.log("No prefetched turnstile token, getting new token");
return (await getTurnstileToken())?.token ?? null;
}
@@ -981,6 +951,7 @@ class Client {
const tokenTTL = 3 * 60 * 1000;
if (Date.now() < token.createdAt + tokenTTL) {
console.log("Prefetched turnstile token is valid");
return token.token;
} else {
console.log("Turnstile token expired, getting new token");
@@ -989,11 +960,27 @@ class Client {
}
}
// Hide elements with no-crazygames class if on CrazyGames
const hideCrazyGamesElements = () => {
if (crazyGamesSDK.isOnCrazyGames()) {
document.querySelectorAll(".no-crazygames").forEach((el) => {
(el as HTMLElement).style.display = "none";
});
}
};
// Initialize the client when the DOM is loaded
const bootstrap = () => {
initLayout();
new Client().initialize();
initNavigation();
// Hide elements immediately
hideCrazyGamesElements();
// Also hide elements after a short delay to catch late-rendered components
setTimeout(hideCrazyGamesElements, 100);
setTimeout(hideCrazyGamesElements, 500);
};
if (document.readyState === "loading") {
+86 -33
View File
@@ -3,7 +3,7 @@ import { customElement, query, state } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { generateID } from "../core/Util";
import { getUserMe } from "./Api";
import { getUserMe, hasLinkedAccount } from "./Api";
import { getPlayToken } from "./Auth";
import { BaseModal } from "./components/BaseModal";
import "./components/Difficulties";
@@ -15,24 +15,15 @@ import { translateText } from "./Utils";
@customElement("matchmaking-modal")
export class MatchmakingModal extends BaseModal {
private gameCheckInterval: ReturnType<typeof setInterval> | null = null;
private connectTimeout: ReturnType<typeof setTimeout> | null = null;
@state() private connected = false;
@state() private socket: WebSocket | null = null;
@state() private gameID: string | null = null;
private elo = "unknown";
private elo: number | "unknown" = "unknown";
constructor() {
super();
this.id = "page-matchmaking";
document.addEventListener("userMeResponse", (event: Event) => {
const customEvent = event as CustomEvent;
if (customEvent.detail) {
const userMeResponse = customEvent.detail as UserMeResponse;
this.elo =
userMeResponse.player?.leaderboard?.oneVone?.elo?.toString() ??
"unknown";
this.requestUpdate();
}
});
}
createRenderRoot() {
@@ -125,18 +116,24 @@ export class MatchmakingModal extends BaseModal {
);
this.socket.onopen = async () => {
console.log("Connected to matchmaking server");
setTimeout(() => {
this.connectTimeout = setTimeout(async () => {
if (this.socket?.readyState !== WebSocket.OPEN) {
console.warn("[Matchmaking] socket not ready");
return;
}
// Set a delay so the user can see the "connecting" message,
// otherwise the "searching" message will be shown immediately.
// Also wait so people who back out immediately aren't added
// to the matchmaking queue.
this.socket.send(
JSON.stringify({
type: "join",
jwt: await getPlayToken(),
}),
);
this.connected = true;
this.requestUpdate();
}, 1000);
this.socket?.send(
JSON.stringify({
type: "join",
jwt: await getPlayToken(),
}),
);
}, 2000);
};
this.socket.onmessage = (event) => {
console.log(event.data);
@@ -145,6 +142,7 @@ export class MatchmakingModal extends BaseModal {
this.socket?.close();
console.log(`matchmaking: got game ID: ${data.gameId}`);
this.gameID = data.gameId;
this.gameCheckInterval = setInterval(() => this.checkGame(), 1000);
}
};
this.socket.onerror = (event: ErrorEvent) => {
@@ -157,7 +155,6 @@ export class MatchmakingModal extends BaseModal {
protected async onOpen(): Promise<void> {
const userMe = await getUserMe();
// Early return if modal was closed during async operation
if (!this.isModalOpen) {
return;
@@ -180,15 +177,21 @@ export class MatchmakingModal extends BaseModal {
this.close();
return;
}
this.elo = userMe.player.leaderboard?.oneVone?.elo ?? "unknown";
this.connected = false;
this.gameID = null;
this.connect();
this.gameCheckInterval = setInterval(() => this.checkGame(), 1000);
}
protected onClose(): void {
this.connected = false;
this.socket?.close();
if (this.connectTimeout) {
clearTimeout(this.connectTimeout);
this.connectTimeout = null;
}
if (this.gameCheckInterval) {
clearInterval(this.gameCheckInterval);
this.gameCheckInterval = null;
@@ -240,6 +243,7 @@ export class MatchmakingModal extends BaseModal {
@customElement("matchmaking-button")
export class MatchmakingButton extends LitElement {
@query("matchmaking-modal") private matchmakingModal?: MatchmakingModal;
@state() private isLoggedIn = false;
constructor() {
super();
@@ -247,6 +251,14 @@ export class MatchmakingButton extends LitElement {
async connectedCallback() {
super.connectedCallback();
// Listen for user authentication changes
document.addEventListener("userMeResponse", (event: Event) => {
const customEvent = event as CustomEvent;
if (customEvent.detail) {
const userMeResponse = customEvent.detail as UserMeResponse | false;
this.isLoggedIn = hasLinkedAccount(userMeResponse);
}
});
}
createRenderRoot() {
@@ -254,17 +266,58 @@ export class MatchmakingButton extends LitElement {
}
render() {
return html`
<div class="z-9999">
<o-button
@click="${this.open}"
translationKey="matchmaking_modal.title"
block
secondary
></o-button>
</div>
<matchmaking-modal></matchmaking-modal>
`;
return this.isLoggedIn
? html`
<button
@click="${this.handleLoggedInClick}"
class="no-crazygames w-full h-20 bg-purple-600 hover:bg-purple-500 text-white font-black uppercase tracking-widest rounded-xl transition-all duration-200 flex flex-col items-center justify-center group overflow-hidden relative"
title="${translateText("matchmaking_modal.title")}"
>
<span class="relative z-10 text-2xl">
${translateText("matchmaking_button.play_ranked")}
</span>
<span
class="relative z-10 text-xs font-medium text-purple-100 opacity-90 group-hover:opacity-100 transition-opacity"
>
${translateText("matchmaking_button.description")}
</span>
</button>
<matchmaking-modal></matchmaking-modal>
`
: html`
<button
@click="${this.handleLoggedOutClick}"
class="no-crazygames w-full h-20 bg-purple-600 hover:bg-purple-500 text-white font-black uppercase tracking-widest rounded-xl transition-all duration-200 flex flex-col items-center justify-center overflow-hidden relative cursor-pointer"
>
<span class="relative z-10 text-2xl">
${translateText("matchmaking_button.login_required")}
</span>
</button>
`;
}
private handleLoggedInClick() {
const usernameInput = document.querySelector("username-input") as any;
const publicLobby = document.querySelector("public-lobby") as any;
if (usernameInput?.isValid()) {
this.open();
publicLobby?.leaveLobby();
} else {
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
message: usernameInput?.validationError,
color: "red",
duration: 3000,
},
}),
);
}
}
private handleLoggedOutClick() {
window.showPage?.("page-account");
}
private open() {
+6 -15
View File
@@ -5,22 +5,9 @@ import { UserSettings } from "../core/game/UserSettings";
import { PlayerPattern } from "../core/Schemas";
import { renderPatternPreview } from "./components/PatternButton";
import { fetchCosmetics } from "./Cosmetics";
import { crazyGamesSDK } from "./CrazyGamesSDK";
import { translateText } from "./Utils";
// Module-level cosmetics cache to avoid refetching on every component mount
let cosmeticsCache: Promise<Cosmetics | null> | null = null;
function getCachedCosmetics(): Promise<Cosmetics | null> {
if (!cosmeticsCache) {
const fetchPromise = fetchCosmetics();
cosmeticsCache = fetchPromise.catch((err) => {
cosmeticsCache = null;
throw err;
});
}
return cosmeticsCache;
}
@customElement("pattern-input")
export class PatternInput extends LitElement {
@state() public pattern: PlayerPattern | null = null;
@@ -63,7 +50,7 @@ export class PatternInput extends LitElement {
super.connectedCallback();
this._abortController = new AbortController();
this.isLoading = true;
const cosmetics = await getCachedCosmetics();
const cosmetics = await fetchCosmetics();
if (!this.isConnected) return;
this.cosmetics = cosmetics;
this.updateFromSettings();
@@ -87,6 +74,10 @@ export class PatternInput extends LitElement {
}
render() {
if (crazyGamesSDK.isOnCrazyGames()) {
return html``;
}
const isDefault = this.pattern === null && this.selectedColor === null;
const showSelect = this.showSelectLabel && isDefault;
const buttonTitle = translateText("territory_patterns.title");
+6
View File
@@ -26,6 +26,7 @@ import "./components/FluentSlider";
import "./components/map/MapPicker";
import { modalHeader } from "./components/ui/ModalHeader";
import { fetchCosmetics } from "./Cosmetics";
import { crazyGamesSDK } from "./CrazyGamesSDK";
import { FlagInput } from "./FlagInput";
import { JoinLobbyEvent } from "./Main";
import { UsernameInput } from "./UsernameInput";
@@ -91,6 +92,9 @@ export class SinglePlayerModal extends BaseModal {
};
private renderNotLoggedInBanner(): TemplateResult {
if (crazyGamesSDK.isOnCrazyGames()) {
return html``;
}
return html`<div
class="px-3 py-2 text-xs font-bold uppercase tracking-wider transition-colors duration-200 rounded-lg bg-yellow-500/20 text-yellow-400 border border-yellow-500/30 whitespace-nowrap shrink-0"
>
@@ -850,6 +854,8 @@ export class SinglePlayerModal extends BaseModal {
const selectedColor = this.userSettings.getSelectedColor();
await crazyGamesSDK.requestMidgameAd();
this.dispatchEvent(
new CustomEvent("join-lobby", {
detail: {
+26 -11
View File
@@ -8,6 +8,7 @@ import {
MIN_USERNAME_LENGTH,
validateUsername,
} from "../core/validations/username";
import { crazyGamesSDK } from "./CrazyGamesSDK";
const usernameKey: string = "username";
@@ -39,8 +40,20 @@ export class UsernameInput extends LitElement {
connectedCallback() {
super.connectedCallback();
const stored = this.getStoredUsername();
const stored = this.getUsername();
this.parseAndSetUsername(stored);
crazyGamesSDK.getUsername().then((username) => {
if (username) {
this.parseAndSetUsername(username ?? genAnonUsername());
this.requestUpdate();
}
});
crazyGamesSDK.addAuthListener((user) => {
if (user) {
this.parseAndSetUsername(user?.username);
}
this.requestUpdate();
});
}
private parseAndSetUsername(fullUsername: string) {
@@ -52,6 +65,8 @@ export class UsernameInput extends LitElement {
this.clanTag = "";
this.baseUsername = fullUsername;
}
this.validateAndStore();
}
render() {
@@ -161,7 +176,7 @@ export class UsernameInput extends LitElement {
}
}
private getStoredUsername(): string {
private getUsername(): string {
const storedUsername = localStorage.getItem(usernameKey);
if (storedUsername) {
return storedUsername;
@@ -176,20 +191,20 @@ export class UsernameInput extends LitElement {
}
private generateNewUsername(): string {
const newUsername = "Anon" + this.uuidToThreeDigits();
const newUsername = genAnonUsername();
this.storeUsername(newUsername);
return newUsername;
}
private uuidToThreeDigits(): string {
const uuid = uuidv4();
const cleanUuid = uuid.replace(/-/g, "").toLowerCase();
const decimal = BigInt(`0x${cleanUuid}`);
const threeDigits = decimal % 1000n;
return threeDigits.toString().padStart(3, "0");
}
public isValid(): boolean {
return this._isValid;
}
}
export function genAnonUsername(): string {
const uuid = uuidv4();
const cleanUuid = uuid.replace(/-/g, "").toLowerCase();
const decimal = BigInt(`0x${cleanUuid}`);
const threeDigits = decimal % 1000n;
return "Anon" + threeDigits.toString().padStart(3, "0");
}
+2
View File
@@ -2,6 +2,8 @@ import IntlMessageFormat from "intl-messageformat";
import { MessageType } from "../core/game/Game";
import type { LangSelector } from "./LangSelector";
export const TUTORIAL_VIDEO_URL = "https://www.youtube.com/embed/EN2oOog3pSs";
export function renderDuration(totalSeconds: number): string {
if (totalSeconds <= 0) return "0s";
const minutes = Math.floor(totalSeconds / 60);
+10
View File
@@ -25,6 +25,16 @@ export abstract class BaseModal extends LitElement {
return this;
}
protected firstUpdated(): void {
if (this.modalEl) {
this.modalEl.onClose = () => {
if (this.isModalOpen) {
this.close();
}
};
}
}
disconnectedCallback() {
this.unregisterEscapeHandler();
super.disconnectedCallback();
+9 -2
View File
@@ -2,6 +2,7 @@ import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { getServerConfigFromClient } from "../../core/configuration/ConfigLoader";
import { UserSettings } from "../../core/game/UserSettings";
import { crazyGamesSDK } from "../CrazyGamesSDK";
import { copyToClipboard, translateText } from "../Utils";
@customElement("copy-button")
@@ -73,15 +74,21 @@ export class CopyButton extends LitElement {
return url;
}
private async resolveCopyText(): Promise<string> {
private async resolveCopyText(): Promise<string | null> {
if (this.copyText) return this.copyText;
if (crazyGamesSDK.isOnCrazyGames()) {
return crazyGamesSDK.createInviteLink(this.lobbyId);
}
if (!this.lobbyId) return "";
return await this.buildCopyUrl();
}
private async handleCopy() {
const text = await this.resolveCopyText();
if (!text) return;
if (!text) {
alert("Error copying game id");
return;
}
await copyToClipboard(
text,
() => (this.copySuccess = true),
+77 -13
View File
@@ -1,8 +1,16 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { customElement, state } from "lit/decorators.js";
import { getCosmeticsHash } from "../Cosmetics";
import { getGamesPlayed } from "../Utils";
const HELP_SEEN_KEY = "helpSeen";
const STORE_SEEN_HASH_KEY = "storeSeenHash";
@customElement("desktop-nav-bar")
export class DesktopNavBar extends LitElement {
@state() private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true";
@state() private _hasNewCosmetics = false;
createRenderRoot() {
return this;
}
@@ -18,6 +26,12 @@ export class DesktopNavBar extends LitElement {
this._updateActiveState(current);
});
}
// Check if cosmetics have changed
getCosmeticsHash().then((hash: string | null) => {
const seenHash = localStorage.getItem(STORE_SEEN_HASH_KEY);
this._hasNewCosmetics = hash !== null && hash !== seenHash;
});
}
disconnectedCallback() {
@@ -40,6 +54,30 @@ export class DesktopNavBar extends LitElement {
});
}
private showHelpDot(): boolean {
// Only show one dot at a time to prevent
// overwhelming users.
return getGamesPlayed() < 10 && !this._helpSeen;
}
private showStoreDot(): boolean {
return this._hasNewCosmetics && !this.showHelpDot();
}
private onHelpClick = () => {
localStorage.setItem(HELP_SEEN_KEY, "true");
this._helpSeen = true;
};
private onStoreClick = () => {
this._hasNewCosmetics = false;
getCosmeticsHash().then((hash: string | null) => {
if (hash !== null) {
localStorage.setItem(STORE_SEEN_HASH_KEY, hash);
}
});
};
render() {
return html`
<nav
@@ -104,11 +142,24 @@ export class DesktopNavBar extends LitElement {
data-page="page-news"
data-i18n="main.news"
></button>
<button
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500 relative"
data-page="page-item-store"
data-i18n="main.store"
></button>
<div class="relative no-crazygames">
<button
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-item-store"
data-i18n="main.store"
@click=${this.onStoreClick}
></button>
${this.showStoreDot()
? html`
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full animate-ping"
></span>
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full"
></span>
`
: ""}
</div>
<button
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-settings"
@@ -119,22 +170,35 @@ export class DesktopNavBar extends LitElement {
data-page="page-stats"
data-i18n="main.stats"
></button>
<button
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-help"
data-i18n="main.help"
></button>
<div class="relative">
<button
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
data-page="page-help"
data-i18n="main.help"
@click=${this.onHelpClick}
></button>
${this.showHelpDot()
? html`
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-400 rounded-full animate-ping"
></span>
<span
class="absolute -top-1 -right-1 w-2 h-2 bg-yellow-400 rounded-full"
></span>
`
: ""}
</div>
<lang-selector></lang-selector>
<button
id="nav-account-button"
class="nav-menu-item relative h-10 rounded-full overflow-hidden flex items-center justify-center gap-2 px-3 bg-transparent border border-white/20 text-white/80 hover:text-white cursor-pointer transition-colors [&.active]:text-white"
class="no-crazygames nav-menu-item relative h-10 rounded-full overflow-hidden flex items-center justify-center gap-2 px-3 bg-transparent border border-white/20 text-white/80 hover:text-white cursor-pointer transition-colors [&.active]:text-white"
data-page="page-account"
data-i18n-aria-label="main.account"
data-i18n-title="main.account"
>
<img
id="nav-account-avatar"
class="hidden w-8 h-8 rounded-full object-cover"
class="no-crazygames hidden w-8 h-8 rounded-full object-cover"
alt=""
data-i18n-alt="main.discord_avatar_alt"
referrerpolicy="no-referrer"
+12 -6
View File
@@ -124,18 +124,24 @@ export class MobileNavBar extends LitElement {
data-page="page-stats"
data-i18n="main.stats"
></button>
<button
class="nav-menu-item block w-full text-left font-bold uppercase tracking-[0.05em] text-white/70 transition-all duration-200 cursor-pointer hover:text-blue-600 hover:translate-x-2.5 hover:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] [&.active]:text-blue-600 [&.active]:translate-x-2.5 [&.active]:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] text-[clamp(18px,2.8vh,32px)] py-[clamp(0.2rem,0.8vh,0.75rem)]"
data-page="page-item-store"
data-i18n="main.store"
></button>
<div class="relative no-crazygames">
<button
class="nav-menu-item block w-full text-left font-bold uppercase tracking-[0.05em] text-white/70 transition-all duration-200 cursor-pointer hover:text-blue-600 hover:translate-x-2.5 hover:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] [&.active]:text-blue-600 [&.active]:translate-x-2.5 [&.active]:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] text-[clamp(18px,2.8vh,32px)] py-[clamp(0.2rem,0.8vh,0.75rem)]"
data-page="page-item-store"
data-i18n="main.store"
></button>
<span
class="absolute top-[45%] -translate-y-1/2 right-4 bg-gradient-to-br from-red-600 to-red-700 text-white text-[10px] font-black tracking-wider px-2.5 py-1 rounded rotate-12 shadow-lg shadow-red-600/50 animate-pulse pointer-events-none"
data-i18n="main.store_new_badge"
></span>
</div>
<button
class="nav-menu-item block w-full text-left font-bold uppercase tracking-[0.05em] text-white/70 transition-all duration-200 cursor-pointer hover:text-blue-600 hover:translate-x-2.5 hover:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] [&.active]:text-blue-600 [&.active]:translate-x-2.5 [&.active]:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] text-[clamp(18px,2.8vh,32px)] py-[clamp(0.2rem,0.8vh,0.75rem)]"
data-page="page-settings"
data-i18n="main.settings"
></button>
<button
class="nav-menu-item block w-full text-left font-bold uppercase tracking-[0.05em] text-white/70 transition-all duration-200 cursor-pointer hover:text-blue-600 hover:translate-x-2.5 hover:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] [&.active]:text-blue-600 [&.active]:translate-x-2.5 [&.active]:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] text-[clamp(18px,2.8vh,32px)] py-[clamp(0.2rem,0.8vh,0.75rem)]"
class="no-crazygames nav-menu-item block w-full text-left font-bold uppercase tracking-[0.05em] text-white/70 transition-all duration-200 cursor-pointer hover:text-blue-600 hover:translate-x-2.5 hover:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] [&.active]:text-blue-600 [&.active]:translate-x-2.5 [&.active]:drop-shadow-[0_0_20px_rgba(37,99,235,0.5)] text-[clamp(18px,2.8vh,32px)] py-[clamp(0.2rem,0.8vh,0.75rem)]"
data-page="page-account"
data-i18n="main.account"
></button>
+1 -1
View File
@@ -72,7 +72,7 @@ export class PatternButton extends LitElement {
return html`
<div
class="flex flex-col items-center justify-between gap-2 p-3 bg-white/5 backdrop-blur-sm border rounded-xl w-48 h-full transition-all duration-200 ${this
class="no-crazygames flex flex-col items-center justify-between gap-2 p-3 bg-white/5 backdrop-blur-sm border rounded-xl w-48 h-full transition-all duration-200 ${this
.selected
? "border-green-500 shadow-[0_0_15px_rgba(34,197,94,0.5)]"
: "hover:bg-white/10 hover:border-white/20 hover:shadow-xl border-white/10"}"
+1 -26
View File
@@ -138,32 +138,7 @@ export class PlayPage extends LitElement {
<!-- Matchmaking Buttons (Full Width across entire grid) -->
<div class="lg:col-span-12 flex flex-col gap-6">
<!-- Not Logged In Button -->
<button
id="matchmaking-button-logged-out"
class="w-full h-20 bg-purple-600 hover:bg-purple-500 text-white font-black uppercase tracking-widest rounded-xl transition-all duration-200 flex flex-col items-center justify-center overflow-hidden relative cursor-pointer"
>
<span
class="relative z-10 text-2xl"
data-i18n="matchmaking_button.login_required"
></span>
</button>
<!-- Logged In Button -->
<button
id="matchmaking-button"
class="hidden w-full h-20 bg-purple-600 hover:bg-purple-500 text-white font-black uppercase tracking-widest rounded-xl transition-all duration-200 flex flex-col items-center justify-center group overflow-hidden relative"
data-i18n-title="matchmaking_modal.title"
>
<span
class="relative z-10 text-2xl"
data-i18n="matchmaking_button.play_ranked"
></span>
<span
class="relative z-10 text-xs font-medium text-purple-100 opacity-90 group-hover:opacity-100 transition-opacity"
data-i18n="matchmaking_button.description"
></span>
</button>
<matchmaking-button></matchmaking-button>
</div>
</div>
</div>
+213
View File
@@ -0,0 +1,213 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
const VIDEO_AD_UNIT_TYPE = "precontent_ad_video";
@customElement("video-ad")
export class VideoAd extends LitElement {
@state()
private isVisible: boolean = true;
@property({ attribute: false })
onComplete?: () => void;
@property({ attribute: false })
onMidpoint?: () => void;
@property({ attribute: false })
onAdBlocked?: () => void;
private adLoadTimeout: ReturnType<typeof setTimeout> | null = null;
private rampCheckInterval: ReturnType<typeof setInterval> | null = null;
private rampWaitTimeout: ReturnType<typeof setTimeout> | null = null;
private adStarted = false;
// How long to wait for ad to start before assuming it's blocked
private static readonly AD_LOAD_TIMEOUT_MS = 8000;
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
// Set dimensions on the custom element itself (required by Playwire)
// Playwire requires explicit pixel dimensions, use max-width for responsiveness
this.style.display = "block";
this.style.width = "100%";
this.style.maxWidth = "800px";
this.style.aspectRatio = "16/9";
this.showVideoAd();
}
disconnectedCallback() {
super.disconnectedCallback();
// Clean up timeout if component is removed
if (this.adLoadTimeout) {
clearTimeout(this.adLoadTimeout);
this.adLoadTimeout = null;
}
if (this.rampCheckInterval) {
clearInterval(this.rampCheckInterval);
this.rampCheckInterval = null;
}
if (this.rampWaitTimeout) {
clearTimeout(this.rampWaitTimeout);
this.rampWaitTimeout = null;
}
}
public showVideoAd(): void {
if (!window.ramp) {
// Wait for ramp to be available, but give up after timeout
this.rampCheckInterval = setInterval(() => {
if (window.ramp && window.ramp.que) {
if (this.rampCheckInterval) {
clearInterval(this.rampCheckInterval);
this.rampCheckInterval = null;
}
if (this.rampWaitTimeout) {
clearTimeout(this.rampWaitTimeout);
this.rampWaitTimeout = null;
}
this.loadVideoAd();
}
}, 100);
// Stop polling after timeout (e.g. adblocker preventing ramp from loading)
this.rampWaitTimeout = setTimeout(() => {
if (this.rampCheckInterval) {
clearInterval(this.rampCheckInterval);
this.rampCheckInterval = null;
}
console.log("[VideoAd] Ramp SDK never loaded - possible adblocker");
this.handleAdBlocked();
}, VideoAd.AD_LOAD_TIMEOUT_MS);
return;
}
this.loadVideoAd();
}
private loadVideoAd(): void {
// Start timeout to detect if ad doesn't load (e.g., due to adblocker)
this.adLoadTimeout = setTimeout(() => {
if (!this.adStarted) {
console.log("[VideoAd] Ad load timeout - possible adblocker detected");
this.handleAdBlocked();
}
}, VideoAd.AD_LOAD_TIMEOUT_MS);
// Set up event listeners when player is ready, chaining any existing handler
const prevOnPlayerReady = window.ramp.onPlayerReady;
window.ramp.onPlayerReady = () => {
if (prevOnPlayerReady) prevOnPlayerReady();
if (window.Bolt) {
// Listen for ad start to know ad is loading successfully
window.Bolt.on(
VIDEO_AD_UNIT_TYPE,
window.Bolt.BOLT_AD_STARTED ?? "boltAdStarted",
() => {
console.log("[VideoAd] Ad started");
this.adStarted = true;
// Clear the timeout since ad is playing
if (this.adLoadTimeout) {
clearTimeout(this.adLoadTimeout);
this.adLoadTimeout = null;
}
},
);
window.Bolt.on(VIDEO_AD_UNIT_TYPE, window.Bolt.BOLT_AD_COMPLETE, () => {
console.log("[VideoAd] Ad completed");
this.hideElement();
});
window.Bolt.on(VIDEO_AD_UNIT_TYPE, window.Bolt.BOLT_AD_ERROR, () => {
console.log("[VideoAd] Ad error/no fill");
this.handleAdBlocked();
});
window.Bolt.on(VIDEO_AD_UNIT_TYPE, window.Bolt.BOLT_MIDPOINT, () => {
console.log("[VideoAd] Ad midpoint");
if (this.onMidpoint) {
this.onMidpoint();
}
});
window.Bolt.on(
VIDEO_AD_UNIT_TYPE,
window.Bolt.SHOW_HIDDEN_CONTAINER ?? "showHiddenContainer",
() => {
console.log("[VideoAd] Ad finished");
this.hideElement();
},
);
}
};
// Queue the video ad initialization
window.ramp.que.push(() => {
const pwUnits = [{ type: VIDEO_AD_UNIT_TYPE }];
window.ramp
.addUnits(pwUnits)
.then(() => {
window.ramp.displayUnits();
})
.catch((e: Error) => {
console.error("[VideoAd] Error adding units:", e);
window.ramp.displayUnits();
});
});
}
private handleAdBlocked(): void {
// Clear timeout if still pending
if (this.adLoadTimeout) {
clearTimeout(this.adLoadTimeout);
this.adLoadTimeout = null;
}
// Call the callback if provided
if (this.onAdBlocked) {
this.onAdBlocked();
}
}
private hideElement(): void {
this.style.display = "none";
this.isVisible = false;
// Call the callback if provided
if (this.onComplete) {
this.onComplete();
}
// Also dispatch event for backwards compatibility
this.dispatchEvent(
new CustomEvent("ad-complete", {
bubbles: true,
composed: true,
}),
);
}
render() {
if (!this.isVisible) {
return html``;
}
// Provide a container for the Playwire video player to render into
// Structure matches Playwire example: wrapper > game-video-ad > precontent-video-location
return html`
<div
class="game-video-ad"
style="width: 100%; height: 100%; overflow: hidden;"
>
<div
id="precontent-video-location"
style="width: 100%; height: 100%;"
></div>
</div>
`;
}
}
@@ -5,10 +5,14 @@ import {
GOLD_INDEX_TRAIN_OTHER,
GOLD_INDEX_TRAIN_SELF,
GOLD_INDEX_WAR,
PLAYER_INDEX_BOT,
PLAYER_INDEX_HUMAN,
PLAYER_INDEX_NATION,
} from "../../../../core/StatsSchemas";
export enum RankType {
Conquests = "Conquests",
ConquestHumans = "ConquestHumans",
ConquestBots = "ConquestBots",
Atoms = "Atoms",
Hydros = "Hydros",
MIRV = "MIRV",
@@ -27,7 +31,7 @@ export interface PlayerInfo {
tag?: string;
killedAt?: number;
gold: bigint[];
conquests: number;
conquests: bigint[];
flag?: string;
winner: boolean;
atoms: number;
@@ -79,12 +83,13 @@ export class Ranking {
username = match[2];
}
const gold = (stats.gold ?? []).map((v) => BigInt(v ?? 0));
const conquests = (stats.conquests ?? []).map((v) => BigInt(v ?? 0));
players[player.clientID] = {
id: player.clientID,
rawUsername: player.username,
username,
tag: player.clanTag,
conquests: Number(stats.conquests) || 0,
conquests,
flag: player.cosmetics?.flag ?? undefined,
killedAt: stats.killedAt !== null ? Number(stats.killedAt) : undefined,
gold,
@@ -125,8 +130,13 @@ export class Ranking {
return (player.killedAt / Math.max(this.duration, 1)) * 10;
}
return 100;
case RankType.Conquests:
return player.conquests;
case RankType.ConquestHumans:
return Number(player.conquests[PLAYER_INDEX_HUMAN] ?? 0n);
case RankType.ConquestBots:
return (
Number(player.conquests[PLAYER_INDEX_BOT] ?? 0n) +
Number(player.conquests[PLAYER_INDEX_NATION] ?? 0n)
);
case RankType.Atoms:
return player.atoms;
case RankType.Hydros:
@@ -63,7 +63,8 @@ export class PlayerRow extends LitElement {
private renderPlayerInfo() {
switch (this.rankType) {
case RankType.Lifetime:
case RankType.Conquests:
case RankType.ConquestHumans:
case RankType.ConquestBots:
return this.renderScoreAsBar();
case RankType.Atoms:
case RankType.Hydros:
@@ -10,19 +10,25 @@ const economyRankings = new Set([
RankType.NavalTrade,
RankType.TrainTrade,
]);
const tradeRankings = new Set([RankType.NavalTrade, RankType.TrainTrade]);
const bombRankings = new Set([RankType.Atoms, RankType.Hydros, RankType.MIRV]);
const warRankings = new Set([
RankType.Conquests,
RankType.ConquestHumans,
RankType.ConquestBots,
RankType.Atoms,
RankType.Hydros,
RankType.MIRV,
]);
const tradeRankings = new Set([RankType.NavalTrade, RankType.TrainTrade]);
const bombRankings = new Set([RankType.Atoms, RankType.Hydros, RankType.MIRV]);
const conquestRankings = new Set([
RankType.ConquestHumans,
RankType.ConquestBots,
]);
const isEconomyRanking = (t: RankType) => economyRankings.has(t);
const isTradeRanking = (t: RankType) => tradeRankings.has(t);
const isBombRanking = (t: RankType) => bombRankings.has(t);
const isWarRanking = (t: RankType) => warRankings.has(t);
const isConquestRanking = (t: RankType) => conquestRankings.has(t);
@customElement("ranking-controls")
export class RankingControls extends LitElement {
@@ -41,7 +47,7 @@ export class RankingControls extends LitElement {
"game_info_modal.duration",
)}
${this.renderButton(
RankType.Conquests,
RankType.ConquestHumans,
isWarRanking(this.rankType),
"game_info_modal.war",
)}
@@ -78,8 +84,8 @@ export class RankingControls extends LitElement {
"game_info_modal.bombs",
)}
${this.renderSubButton(
RankType.Conquests,
this.rankType === RankType.Conquests,
RankType.ConquestHumans,
isConquestRanking(this.rankType),
"game_info_modal.conquests",
)}
</div>
@@ -14,7 +14,7 @@ export class RankingHeader extends LitElement {
render() {
return html`
<li
class="text-lg border-white/5 bg-white/[0.02] text-white/60 text-xs uppercase tracking-wider relative pt-2 pb-2 pr-5 pl-5 flex justify-between items-center"
class="h-[30px] text-lg border-white/5 bg-white/[0.02] text-white/60 text-xs uppercase tracking-wider relative pt-2 pb-2 pr-5 pl-5 flex justify-between items-center"
>
${this.renderHeaderContent()}
</li>
@@ -27,10 +27,21 @@ export class RankingHeader extends LitElement {
return html`<div class="w-full">
${translateText("game_info_modal.survival_time")}
</div>`;
case RankType.Conquests:
return html`<div class="w-full">
${translateText("game_info_modal.num_of_conquests")}
</div>`;
case RankType.ConquestHumans:
case RankType.ConquestBots:
return html`
<div class="flex justify-between sm:px-17.5 w-full">
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.num_of_conquests_humans"),
RankType.ConquestHumans,
)}
/
${this.renderMultipleChoiceHeaderButton(
translateText("game_info_modal.num_of_conquests_bots"),
RankType.ConquestBots,
)}
</div>
`;
case RankType.Atoms:
case RankType.Hydros:
case RankType.MIRV:
@@ -149,7 +149,7 @@ export class PlayerStatsTreeView extends LitElement {
attacks: this.mergeStatArrays(base.attacks, next.attacks),
betrayals: this.mergeStatValue(base.betrayals, next.betrayals),
killedAt: this.mergeStatValue(base.killedAt, next.killedAt),
conquests: this.mergeStatValue(base.conquests, next.conquests),
conquests: this.mergeStatArrays(base.conquests, next.conquests),
boats: this.mergeStatRecord(base.boats, next.boats),
bombs: this.mergeStatRecord(base.bombs, next.bombs),
gold: this.mergeStatArrays(base.gold, next.gold),
@@ -203,7 +203,7 @@ export class PlayerStatsTreeView extends LitElement {
attacks: stats.attacks ? [...stats.attacks] : undefined,
betrayals: stats.betrayals,
killedAt: stats.killedAt,
conquests: stats.conquests,
conquests: stats.conquests ? [...stats.conquests] : undefined,
boats: stats.boats ? { ...stats.boats } : undefined,
bombs: stats.bombs ? { ...stats.bombs } : undefined,
gold: stats.gold ? [...stats.gold] : undefined,
+18 -2
View File
@@ -6,7 +6,6 @@ import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler";
import { FrameProfiler } from "./FrameProfiler";
import { TransformHandler } from "./TransformHandler";
import { UIState } from "./UIState";
import { AdTimer } from "./layers/AdTimer";
import { AlertFrame } from "./layers/AlertFrame";
import { BuildMenu } from "./layers/BuildMenu";
import { ChatDisplay } from "./layers/ChatDisplay";
@@ -20,6 +19,7 @@ import { GameLeftSidebar } from "./layers/GameLeftSidebar";
import { GameRightSidebar } from "./layers/GameRightSidebar";
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
import { ImmunityTimer } from "./layers/ImmunityTimer";
import { InGameHeaderAd } from "./layers/InGameHeaderAd";
import { Layer } from "./layers/Layer";
import { Leaderboard } from "./layers/Leaderboard";
import { MainRadialMenu } from "./layers/MainRadialMenu";
@@ -34,6 +34,7 @@ import { ReplayPanel } from "./layers/ReplayPanel";
import { SAMRadiusLayer } from "./layers/SAMRadiusLayer";
import { SettingsModal } from "./layers/SettingsModal";
import { SpawnTimer } from "./layers/SpawnTimer";
import { SpawnVideoAd } from "./layers/SpawnVideoAd";
import { StructureIconsLayer } from "./layers/StructureIconsLayer";
import { StructureLayer } from "./layers/StructureLayer";
import { TeamStats } from "./layers/TeamStats";
@@ -244,6 +245,20 @@ export function createRenderer(
}
immunityTimer.game = game;
const inGameHeaderAd = document.querySelector(
"in-game-header-ad",
) as InGameHeaderAd;
if (!(inGameHeaderAd instanceof InGameHeaderAd)) {
console.error("in-game header ad not found");
}
inGameHeaderAd.game = game;
const spawnVideoAd = document.querySelector("spawn-video-ad") as SpawnVideoAd;
if (!(spawnVideoAd instanceof SpawnVideoAd)) {
console.error("spawn video ad not found");
}
spawnVideoAd.game = game;
// When updating these layers please be mindful of the order.
// Try to group layers by the return value of shouldTransform.
// Not grouping the layers may cause excessive calls to context.save() and context.restore().
@@ -287,7 +302,8 @@ export function createRenderer(
playerPanel,
headsUpMessage,
multiTabModal,
new AdTimer(game),
inGameHeaderAd,
spawnVideoAd,
alertFrame,
performanceOverlay,
];
-28
View File
@@ -1,28 +0,0 @@
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
const AD_SHOW_TICKS = 2 * 60 * 10; // 2 minute
export class AdTimer implements Layer {
private isHidden: boolean = false;
constructor(private g: GameView) {}
init() {}
public async tick() {
if (this.isHidden) {
return;
}
const gameTicks = this.g.ticks() - this.g.config().numSpawnPhaseTurns();
if (gameTicks > AD_SHOW_TICKS) {
console.log("destroying sticky ads");
window.fusetag?.que?.push(() => {
window.fusetag?.destroySticky?.();
});
this.isHidden = true;
return;
}
}
}
+3
View File
@@ -142,6 +142,9 @@ export class ChatModal extends LitElement {
player
? "selected"
: ""}"
style="border: 2px solid ${player
.territoryColor()
.toHex()};"
@click=${() => this.selectPlayer(player)}
>
${player.name()}
+10 -5
View File
@@ -114,10 +114,15 @@ export class GameRightSidebar extends LitElement implements Layer {
private onPauseButtonClick() {
this.isPaused = !this.isPaused;
if (this.isPaused) {
crazyGamesSDK.gameplayStop();
} else {
crazyGamesSDK.gameplayStart();
}
this.eventBus.emit(new PauseGameIntentEvent(this.isPaused));
}
private onExitButtonClick() {
private async onExitButtonClick() {
const isAlive = this.game.myPlayer()?.isAlive();
if (isAlive) {
const isConfirmed = confirm(
@@ -125,10 +130,10 @@ export class GameRightSidebar extends LitElement implements Layer {
);
if (!isConfirmed) return;
}
crazyGamesSDK.gameplayStop().then(() => {
// redirect to the home page
window.location.href = "/";
});
await crazyGamesSDK.requestMidgameAd();
await crazyGamesSDK.gameplayStop();
// redirect to the home page
window.location.href = "/";
}
private onSettingsButtonClick() {
@@ -0,0 +1,112 @@
import { LitElement, html } from "lit";
import { customElement } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
const AD_SHOW_TICKS = 2 * 60 * 10; // 2 minutes
const HEADER_AD_TYPE = "standard_iab_head1";
const HEADER_AD_CONTAINER_ID = "header-ad-container";
const TWO_XL_BREAKPOINT = 1536;
@customElement("in-game-header-ad")
export class InGameHeaderAd extends LitElement implements Layer {
public game: GameView;
private isHidden: boolean = false;
private adLoaded: boolean = false;
private shouldShow: boolean = false;
createRenderRoot() {
return this;
}
init() {
this.showHeaderAd();
}
private showHeaderAd(): void {
// Don't show header ad on screens smaller than 2xl
if (window.innerWidth < TWO_XL_BREAKPOINT) {
return;
}
if (!window.adsEnabled) {
return;
}
this.shouldShow = true;
this.requestUpdate();
// Wait for the element to render before loading the ad
this.updateComplete.then(() => {
this.loadAd();
});
}
private loadAd(): void {
if (!window.ramp) {
console.warn("Playwire RAMP not available for header ad");
return;
}
try {
window.ramp.que.push(() => {
try {
window.ramp.spaAddAds([
{
type: HEADER_AD_TYPE,
selectorId: HEADER_AD_CONTAINER_ID,
},
]);
this.adLoaded = true;
console.log("Header ad loaded:", HEADER_AD_TYPE);
} catch (e) {
console.error("Failed to add header ad:", e);
}
});
} catch (error) {
console.error("Failed to load header ad:", error);
}
}
private hideHeaderAd(): void {
this.shouldShow = false;
this.adLoaded = false;
this.requestUpdate();
}
public tick() {
if (this.isHidden) {
return;
}
const gameTicks =
this.game.ticks() - this.game.config().numSpawnPhaseTurns();
if (gameTicks > AD_SHOW_TICKS) {
console.log("destroying header ad and refreshing PageOS");
this.hideHeaderAd();
this.isHidden = true;
if (window.PageOS?.session?.newPageView) {
window.PageOS.session.newPageView();
}
return;
}
}
shouldTransform(): boolean {
return false;
}
render() {
if (!this.shouldShow) {
return html``;
}
return html`
<div
id="${HEADER_AD_CONTAINER_ID}"
class="hidden 2xl:flex fixed top-0 left-1/2 -translate-x-1/2 z-[100] justify-center items-center pointer-events-auto p-0 -mt-[20px]"
></div>
`;
}
}
@@ -504,6 +504,14 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
</div>
`
: ""}
${unit.type() === UnitType.TransportShip
? html`
<div class="text-sm">
${translateText("player_info_overlay.troops")}:
${renderTroops(unit.troops())}
</div>
`
: ""}
</div>
</div>
`;
@@ -17,7 +17,6 @@ import allianceIcon from "/images/AllianceIconWhite.svg?url";
import boatIcon from "/images/BoatIconWhite.svg?url";
import buildIcon from "/images/BuildIconWhite.svg?url";
import chatIcon from "/images/ChatIconWhite.svg?url";
import checkmarkIcon from "/images/CheckmarkIconWhite.svg?url";
import donateGoldIcon from "/images/DonateGoldIconWhite.svg?url";
import donateTroopIcon from "/images/DonateTroopIconWhite.svg?url";
import emojiIcon from "/images/EmojiIconWhite.svg?url";
@@ -219,15 +218,6 @@ const allyBreakElement: MenuElement = {
!!params.playerActions?.interaction?.canBreakAlliance,
color: COLORS.breakAlly,
icon: traitorIcon,
subMenu: () => [allyBreakCancelElement, allyBreakConfirmElement],
};
const allyBreakConfirmElement: MenuElement = {
id: "ally_break_confirm",
name: "confirm",
disabled: () => false,
color: COLORS.breakAlly,
icon: checkmarkIcon,
action: (params: MenuElementParams) => {
params.playerActionHandler.handleBreakAlliance(
params.myPlayer,
@@ -237,17 +227,6 @@ const allyBreakConfirmElement: MenuElement = {
},
};
const allyBreakCancelElement: MenuElement = {
id: "ally_break_cancel",
name: "cancel",
disabled: () => false,
color: COLORS.info,
icon: xIcon,
action: (params: MenuElementParams) => {
params.closeMenu();
},
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const allyDonateGoldElement: MenuElement = {
id: "ally_donate_gold",
@@ -638,10 +617,7 @@ export const rootMenuElement: MenuElement = {
icon: infoIcon,
color: COLORS.info,
subMenu: (params: MenuElementParams) => {
let ally = allyRequestElement;
if (params.selected?.isAlliedWith(params.myPlayer)) {
ally = allyBreakElement;
}
const isAllied = params.selected?.isAlliedWith(params.myPlayer);
const tileOwner = params.game.owner(params.tile);
const isOwnTerritory =
@@ -651,10 +627,10 @@ export const rootMenuElement: MenuElement = {
const menuItems: (MenuElement | null)[] = [
infoMenuElement,
...(isOwnTerritory
? [deleteUnitElement, ally, buildMenuElement]
? [deleteUnitElement, allyRequestElement, buildMenuElement]
: [
boatMenuElement,
ally,
isAllied ? allyBreakElement : boatMenuElement,
allyRequestElement,
isFriendlyTarget(params) && !isDisconnectedTarget(params)
? donateGoldRadialElement
: attackMenuElement,
+9 -2
View File
@@ -1,9 +1,10 @@
import { html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { crazyGamesSDK } from "src/client/CrazyGamesSDK";
import { PauseGameIntentEvent } from "src/client/Transport";
import { EventBus } from "../../../core/EventBus";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
import { PauseGameIntentEvent } from "../../Transport";
import { translateText } from "../../Utils";
import SoundManager from "../../sound/SoundManager";
import { Layer } from "./Layer";
@@ -105,8 +106,14 @@ export class SettingsModal extends LitElement implements Layer {
}
private pauseGame(pause: boolean) {
if (this.shouldPause && !this.wasPausedWhenOpened)
if (this.shouldPause && !this.wasPausedWhenOpened) {
if (pause) {
crazyGamesSDK.gameplayStop();
} else {
crazyGamesSDK.gameplayStart();
}
this.eventBus.emit(new PauseGameIntentEvent(pause));
}
}
private onTerrainButtonClick() {
@@ -0,0 +1,67 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { crazyGamesSDK } from "src/client/CrazyGamesSDK";
import { getGamesPlayed } from "src/client/Utils";
import { GameType } from "src/core/game/Game";
import { GameView } from "../../../core/game/GameView";
import "../../components/VideoAd";
import { Layer } from "./Layer";
@customElement("spawn-video-ad")
export class SpawnVideoAd extends LitElement implements Layer {
public game: GameView;
@state() private shouldShow = false;
@state() private adComplete = false;
createRenderRoot() {
return this;
}
init() {
if (
!window.adsEnabled ||
window.innerWidth < 768 ||
crazyGamesSDK.isOnCrazyGames() ||
this.game.config().gameConfig().gameType === GameType.Singleplayer ||
getGamesPlayed() < 3 || // Don't show to new players
getGamesPlayed() % 3 !== 0 // Only show 1 in 3 times
) {
return;
}
this.shouldShow = true;
}
tick() {
if (this.adComplete) return;
// Hide when spawn phase ends
if (this.shouldShow && !this.game.inSpawnPhase()) {
this.shouldShow = false;
this.requestUpdate();
}
}
private handleComplete = () => {
this.adComplete = true;
this.shouldShow = false;
};
shouldTransform(): boolean {
return false;
}
render() {
if (!this.shouldShow || this.adComplete) {
return html``;
}
return html`
<div class="fixed bottom-0 left-0 z-[9999] pointer-events-auto">
<video-ad
style="width: 400px; max-width: 400px; height: 225px; aspect-ratio: auto;"
.onComplete="${this.handleComplete}"
></video-ad>
</div>
`;
}
}
+1 -1
View File
@@ -130,7 +130,7 @@ export class UnitDisplay extends LitElement implements Layer {
return html`
<div
class="hidden 2xl:flex lg:flex fixed bottom-4 left-1/2 transform -translate-x-1/2 z-1100 2xl:flex-row xl:flex-col lg:flex-col 2xl:gap-5 xl:gap-2 lg:gap-2 justify-center items-center"
class="hidden min-[1200px]:flex fixed bottom-4 left-1/2 transform -translate-x-1/2 z-[1100] 2xl:flex-row xl:flex-col min-[1200px]:flex-col 2xl:gap-5 xl:gap-2 min-[1200px]:gap-2 justify-center items-center"
>
<div class="bg-gray-800/70 backdrop-blur-xs rounded-lg p-0.5">
<div class="grid grid-rows-1 auto-cols-max grid-flow-col gap-1 w-fit">
+4 -4
View File
@@ -1,9 +1,10 @@
import { LitElement, TemplateResult, html } from "lit";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import {
getGamesPlayed,
isInIframe,
translateText,
TUTORIAL_VIDEO_URL,
} from "../../../client/Utils";
import { ColorPalette, Pattern } from "../../../core/CosmeticSchemas";
import { EventBus } from "../../../core/EventBus";
@@ -131,9 +132,7 @@ export class WinModal extends LitElement implements Layer {
<div class="relative w-full pb-[56.25%]">
<iframe
class="absolute top-0 left-0 w-full h-full rounded-sm"
src="${this.isVisible
? "https://www.youtube.com/embed/EN2oOog3pSs"
: ""}"
src="${this.isVisible ? TUTORIAL_VIDEO_URL : ""}"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
@@ -250,6 +249,7 @@ export class WinModal extends LitElement implements Layer {
}
async show() {
crazyGamesSDK.gameplayStop();
await this.loadPatternContent();
this.isVisible = true;
this.requestUpdate();
+1 -1
View File
@@ -248,7 +248,7 @@ export const AllPlayersStatsSchema = z.record(ID, PlayerStatsSchema);
export const UsernameSchema = z
.string()
.regex(/^[a-zA-Z0-9_ [\]üÜ]+$/u)
.regex(/^[a-zA-Z0-9_ [\]üÜ.]+$/u)
.min(3)
.max(27);
const countryCodes = countries.filter((c) => !c.restricted).map((c) => c.code);
+6 -1
View File
@@ -62,6 +62,11 @@ export const ATTACK_INDEX_SENT = 0; // Outgoing attack troops
export const ATTACK_INDEX_RECV = 1; // Incmoing attack troops
export const ATTACK_INDEX_CANCEL = 2; // Cancelled attack troops
// Player types
export const PLAYER_INDEX_HUMAN = 0;
export const PLAYER_INDEX_NATION = 1;
export const PLAYER_INDEX_BOT = 2;
// Boats
export const BOAT_INDEX_SENT = 0; // Boats launched
export const BOAT_INDEX_ARRIVE = 1; // Boats arrived
@@ -102,7 +107,7 @@ export const PlayerStatsSchema = z
attacks: AtLeastOneNumberSchema.optional(),
betrayals: BigIntStringSchema.optional(),
killedAt: BigIntStringSchema.optional(),
conquests: BigIntStringSchema.optional(),
conquests: AtLeastOneNumberSchema.optional(),
boats: z.partialRecord(BoatUnitSchema, AtLeastOneNumberSchema).optional(),
bombs: z.partialRecord(BombUnitSchema, AtLeastOneNumberSchema).optional(),
gold: AtLeastOneNumberSchema.optional(),
+3 -3
View File
@@ -734,7 +734,7 @@ export class DefaultConfig implements Config {
if (playerInfo.playerType === PlayerType.Nation) {
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
return 18_750;
return 12_500;
case Difficulty.Medium:
return 25_000; // Like humans
case Difficulty.Hard:
@@ -769,7 +769,7 @@ export class DefaultConfig implements Config {
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
return maxTroops * 0.75;
return maxTroops * 0.5;
case Difficulty.Medium:
return maxTroops * 1; // Like humans
case Difficulty.Hard:
@@ -796,7 +796,7 @@ export class DefaultConfig implements Config {
if (player.type() === PlayerType.Nation) {
switch (this._gameConfig.difficulty) {
case Difficulty.Easy:
toAdd *= 0.95;
toAdd *= 0.9;
break;
case Difficulty.Medium:
toAdd *= 1; // Like humans
+1 -1
View File
@@ -114,7 +114,7 @@ export class TransportShipExecution implements Execution {
mg.displayIncomingUnit(
this.boat.id(),
// TODO TranslateText
`Naval invasion incoming from ${this.attacker.displayName()}`,
`Naval invasion incoming from ${this.attacker.displayName()} (${renderTroops(this.boat.troops())})`,
MessageType.NAVAL_INVASION_INBOUND,
this.target.id(),
);
+4
View File
@@ -51,6 +51,7 @@ export class FetchGameMapLoader implements GameMapLoader {
}
private async loadBinaryFromUrl(url: string) {
const startTime = performance.now();
const response = await fetch(url);
if (!response.ok) {
@@ -58,6 +59,9 @@ export class FetchGameMapLoader implements GameMapLoader {
}
const data = await response.arrayBuffer();
console.log(
`[MapLoader] ${url}: ${(performance.now() - startTime).toFixed(0)}ms`,
);
return new Uint8Array(data);
}
+2
View File
@@ -111,6 +111,7 @@ export enum GameMapType {
Manicouagan = "Manicouagan",
Lemnos = "Lemnos",
Sierpinski = "Sierpinski",
TheBox = "The Box",
TwoLakes = "Two Lakes",
StraitOfHormuz = "Strait of Hormuz",
Surrounded = "Surrounded",
@@ -177,6 +178,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.Surrounded,
],
arcade: [
GameMapType.TheBox,
GameMapType.Didier,
GameMapType.DidierFrance,
GameMapType.Sierpinski,
+6
View File
@@ -104,6 +104,8 @@ export class GameImpl implements Game {
private _config: Config,
private _stats: Stats,
) {
const constructorStart = performance.now();
this._terraNullius = new TerraNulliusImpl();
this._width = _map.width();
this._height = _map.height();
@@ -124,6 +126,10 @@ export class GameImpl implements Game {
{ cachePaths: true },
);
}
console.log(
`[GameImpl] Constructor total: ${(performance.now() - constructorStart).toFixed(0)}ms`,
);
}
private populateTeams() {
+18 -8
View File
@@ -24,11 +24,14 @@ import {
OTHER_INDEX_LOST,
OTHER_INDEX_UPGRADE,
OtherUnitType,
PLAYER_INDEX_BOT,
PLAYER_INDEX_HUMAN,
PLAYER_INDEX_NATION,
PlayerStats,
unitTypeToBombUnit,
unitTypeToOtherUnit,
} from "../StatsSchemas";
import { Player, TerraNullius, UnitType } from "./Game";
import { Player, PlayerType, TerraNullius, UnitType } from "./Game";
import { Stats } from "./Stats";
type BigIntLike = bigint | number;
@@ -41,6 +44,12 @@ function _bigint(value: BigIntLike): bigint {
}
}
const conquest_by_type: Record<PlayerType, number> = {
[PlayerType.Human]: PLAYER_INDEX_HUMAN,
[PlayerType.Nation]: PLAYER_INDEX_NATION,
[PlayerType.Bot]: PLAYER_INDEX_BOT,
};
export class StatsImpl implements Stats {
private readonly data: AllPlayersStats = {};
@@ -138,14 +147,12 @@ export class StatsImpl implements Stats {
p.units[type][index] += _bigint(value);
}
private _addConquest(player: Player) {
private _addConquest(player: Player, index: number) {
const p = this._makePlayerStats(player);
if (p === undefined) return;
if (p.conquests === undefined) {
p.conquests = _bigint(1);
} else {
p.conquests += _bigint(1);
}
p.conquests ??= [0n];
while (p.conquests.length <= index) p.conquests.push(0n);
p.conquests[index] += _bigint(1);
}
private _addPlayerKilled(player: Player, tick: number) {
@@ -249,7 +256,10 @@ export class StatsImpl implements Stats {
goldWar(player: Player, captured: Player, gold: BigIntLike): void {
this._addGold(player, GOLD_INDEX_WAR, gold);
this._addConquest(player);
const conquestType = conquest_by_type[captured.type()];
if (conquestType !== undefined) {
this._addConquest(player, conquestType);
}
}
unitBuild(player: Player, type: OtherUnitType): void {
+1 -1
View File
@@ -80,7 +80,7 @@ export class WorkerClient {
this.messageHandlers.delete(messageId);
reject(new Error("Worker initialization timeout"));
}
}, 5000); // 5 second timeout
}, 20000); // 20 second timeout
});
}
+2 -3
View File
@@ -293,17 +293,16 @@ export class GameServer {
const parsed = ClientMessageSchema.safeParse(JSON.parse(message));
if (!parsed.success) {
const error = z.prettifyError(parsed.error);
this.log.error("Failed to parse client message", error, {
this.log.warn(`Failed to parse client message ${error}`, {
clientID: client.clientID,
});
client.ws.send(
JSON.stringify({
type: "error",
error,
message,
message: `Server could not parse message from client: ${message}`,
} satisfies ServerErrorMessage),
);
client.ws.close(1002, "ClientMessageSchema");
return;
}
const clientMsg = parsed.data;
+2
View File
@@ -62,8 +62,10 @@ const frequency: Partial<Record<GameMapName, number>> = {
StraitOfHormuz: 4,
Surrounded: 4,
DidierFrance: 1,
Didier: 1,
AmazonRiver: 3,
Sierpinski: 10,
TheBox: 3,
};
interface MapWithMode {
+1 -1
View File
@@ -531,7 +531,7 @@ async function startMatchmakingPolling(gm: GameManager) {
// Wait a few seconds to allow clients to connect.
console.log(`Starting game ${gameId}`);
game.start();
}, 5000);
}, 7000);
}
} catch (error) {
log.error(`Error polling lobby:`, error);
+8 -8
View File
@@ -56,7 +56,7 @@ describe("Ranking class", () => {
cosmetics: { flag: "USA" },
stats: {
units: { port: [2n, 0n, 0n, 2n] },
conquests: 5n,
conquests: [5n],
gold: [0n, 100n, 20n, 0n, 15n, 5n], // total 140
bombs: {
abomb: [1n],
@@ -71,7 +71,7 @@ describe("Ranking class", () => {
username: "Bob",
stats: {
units: { city: [2n, 0n, 0n, 2n] },
conquests: 8n,
conquests: [8n],
gold: [0n, 50n, 10n, 5n], // total 65, no train trade
bombs: {
abomb: [0n],
@@ -86,7 +86,7 @@ describe("Ranking class", () => {
username: "Charlie",
stats: {
// no units, but has conquests/killedAt to count as played
conquests: 8n,
conquests: [8n],
killedAt: BigInt(600),
gold: [0n, 10n, 2n, 10n, 0n, 5n], // total 27
bombs: {},
@@ -110,21 +110,21 @@ describe("Ranking class", () => {
test("summarizes players correctly", () => {
const r = new Ranking(makeSession());
const players = r.sortedBy(RankType.Conquests);
const players = r.sortedBy(RankType.ConquestHumans);
expect(players.length).toBe(3);
const p1 = players.find((p) => p.id === "p1")!;
expect(p1.username).toBe("Alice");
expect(p1.flag).toBe("USA");
expect(p1.conquests).toBe(5);
expect(p1.conquests).toStrictEqual([5n]);
expect(p1.atoms).toBe(1);
expect(p1.mirv).toBe(2);
});
test("correctly identifies winner", () => {
const r = new Ranking(makeSession());
const p2 = r.sortedBy(RankType.Conquests).find((p) => p.id === "p2")!;
const p2 = r.sortedBy(RankType.ConquestHumans).find((p) => p.id === "p2")!;
expect(p2.winner).toBe(true);
});
@@ -157,7 +157,7 @@ describe("Ranking class", () => {
test("lifetime score is percentage of duration", () => {
const r = new Ranking(makeSession());
const p3 = r.sortedBy(RankType.Conquests).find((p) => p.id === "p3")!;
const p3 = r.sortedBy(RankType.ConquestHumans).find((p) => p.id === "p3")!;
const expected = Number(BigInt(600)) / gameDuration;
expect(r.score(p3, RankType.Lifetime)).toBe(expected);
});
@@ -170,7 +170,7 @@ describe("Ranking class", () => {
test("winners should be ahead of players with same score", () => {
const r = new Ranking(makeSession());
const sortedPlayers = r.sortedBy(RankType.Conquests);
const sortedPlayers = r.sortedBy(RankType.ConquestHumans);
expect(sortedPlayers[0].id).toBe("p2"); // p2 & p3 same score but winner first
});
+2 -2
View File
@@ -162,14 +162,14 @@ describe("Stats", () => {
expect(stats.stats()).toStrictEqual({
client1: {
gold: [0n, 1n],
conquests: 1n,
conquests: [1n],
},
});
stats.goldWar(player1, player2, 1);
expect(stats.stats()).toStrictEqual({
client1: {
gold: [0n, 2n],
conquests: 2n,
conquests: [2n],
},
});
});
@@ -341,6 +341,7 @@ describe("RadialMenuElements", () => {
isPlayer: vi.fn(() => true),
} as unknown as PlayerView;
mockParams.selected = allyPlayer;
mockGame.owner = vi.fn(() => allyPlayer);
const subMenu = rootMenuElement.subMenu!(mockParams);
const allyMenu = subMenu.find((item) => item.id === "ally_break");
+2 -40
View File
@@ -75,12 +75,6 @@ const makeParams = (opts?: Partial<MenuElementParams>): MenuElementParams => {
const findAllyBreak = (items: any[]) =>
items.find((i) => i && i.id === "ally_break");
const findAllyBreakConfirm = (items: any[]) =>
items.find((i) => i && i.id === "ally_break_confirm");
const findAllyBreakCancel = (items: any[]) =>
items.find((i) => i && i.id === "ally_break_cancel");
describe("RadialMenuElements ally break", () => {
test("shows break option with correct color when allied", () => {
const params = makeParams();
@@ -91,29 +85,12 @@ describe("RadialMenuElements ally break", () => {
expect(ally.color).toBe(COLORS.breakAlly);
});
test("break option opens confirmation submenu", () => {
test("break action calls handleBreakAlliance and closes menu", () => {
const params = makeParams();
const items = rootMenuElement.subMenu!(params);
const ally = findAllyBreak(items)!;
expect(ally.subMenu).toBeDefined();
const subMenuItems = ally.subMenu!(params);
expect(subMenuItems.length).toBe(2);
const confirmItem = findAllyBreakConfirm(subMenuItems);
const cancelItem = findAllyBreakCancel(subMenuItems);
expect(confirmItem).toBeTruthy();
expect(cancelItem).toBeTruthy();
});
test("confirm action calls handleBreakAlliance and closes menu", () => {
const params = makeParams();
const items = rootMenuElement.subMenu!(params);
const ally = findAllyBreak(items)!;
const subMenuItems = ally.subMenu!(params);
const confirmItem = findAllyBreakConfirm(subMenuItems)!;
confirmItem.action!(params);
ally.action!(params);
expect(params.playerActionHandler.handleBreakAlliance).toHaveBeenCalledWith(
params.myPlayer,
@@ -121,19 +98,4 @@ describe("RadialMenuElements ally break", () => {
);
expect(params.closeMenu).toHaveBeenCalled();
});
test("cancel action closes menu without breaking alliance", () => {
const params = makeParams();
const items = rootMenuElement.subMenu!(params);
const ally = findAllyBreak(items)!;
const subMenuItems = ally.subMenu!(params);
const cancelItem = findAllyBreakCancel(subMenuItems)!;
cancelItem.action!(params);
expect(
params.playerActionHandler.handleBreakAlliance,
).not.toHaveBeenCalled();
expect(params.closeMenu).toHaveBeenCalled();
});
});