rts-commander / localization.py
Luigi's picture
feat(endgame): elimination-based game over + overlay; fix LLM chat_format and i18n draw
a510770
raw
history blame
24.4 kB
"""
Localization/Translation System
Supports: English, French, Traditional Chinese
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Iterable
@dataclass(frozen=True)
class LanguageMetadata:
display_name: str
ai_language_name: str
ai_example_summary: str
TRANSLATIONS: Dict[str, Dict[str, str]] = {
"en": {
"game.window.title": "Minimalist RTS",
"game.language.display": "Language: {language}",
"game.win.banner": "{winner} Wins!",
"game.winner.player": "Player",
"game.winner.enemy": "Enemy",
"hud.topbar.credits": "Credits: {amount}",
"hud.topbar.power": "Power: {produced}/{consumed}",
"hud.topbar.intel.summary": "Intel: {summary}",
"hud.topbar.intel.waiting": "Intel: Awaiting report",
"hud.intel.header.offline": "Intel: Offline",
"hud.intel.header.refresh": "Intel: {mode} refresh",
"hud.intel.mode.auto": "Auto",
"hud.intel.mode.manual": "Manual",
"hud.intel.status.model_missing": "Model not available",
"hud.intel.status.updating": "Status: Updating...",
"hud.intel.status.updated": "Updated {seconds}s ago",
"hud.intel.status.waiting": "Waiting for first report",
"hud.intel.cycle": "Cycle: ~{seconds}s",
"hud.section.infantry": "Infantry",
"hud.section.vehicles": "Vehicles",
"hud.section.support": "Support",
"hud.section.structures": "Structures",
"hud.button.cost": "{cost} cr",
"unit.infantry": "Infantry",
"unit.tank": "Tank",
"unit.artillery": "Artillery",
"unit.helicopter": "Helicopter",
"unit.harvester": "Harvester",
"building.hq": "Headquarters",
"building.barracks": "Barracks",
"building.war_factory": "War Factory",
"building.refinery": "Refinery",
"building.power_plant": "Power Plant",
"building.turret": "Defense Turret",
"faction.allies": "Allies",
"faction.soviets": "Soviets",
"intel.unavailable": "Intel unavailable",
"intel.error": "Intel error: {error}",
"notification.insufficient_credits": "⚠️ Insufficient credits! Need {cost} cr, have {current} cr",
"notification.low_power": "⚠️ LOW POWER! Build more Power Plants or production will be slow!",
"notification.no_power": "🚨 CRITICAL POWER SHORTAGE! Buildings offline!",
"notification.building_requires": "⚠️ Cannot build {building}: requires {requirement}",
"notification.unit_requires": "⚠️ Cannot train {unit}: requires {requirement}",
"notification.building_placed": "Building {building}",
"notification.unit_training": "Training {unit}",
"notification.building_cancelled": "Building cancelled",
"notification.building_too_far_from_hq": "⚠️ Too far from HQ: build closer to your Headquarters",
"notification.building_limit_one": "⚠️ Only one {building} can be constructed",
"notification.production_building_selected": "🏭 Production will use: {building}",
"hud.production.source.auto": "Auto",
"hud.production.source.label": "Using:",
"hud.production.source.clear": "Clear selection",
"notification.units_moving": "Moving {count} units",
"notification.units_selected": "Selected {count} units",
"notification.units_attacking": "🎯 Attacking enemy {target}!",
"notification.click_to_place": "🏗️ Click to place {building}",
"notification.language_changed": "Language changed to {language}",
"language.en": "English",
"language.fr": "French",
"language.zh-TW": "Traditional Chinese",
"menu.quick_actions.title": "🎯 Quick Actions",
"menu.actions.select_all": "Select All Units",
"menu.actions.select_all.tooltip": "Select all your units (hotkey: Ctrl+A)",
"menu.actions.stop": "Stop Selected",
"menu.actions.stop.tooltip": "Stop selected units",
"menu.actions.attack_move": "Attack Move",
"menu.actions.attack_move.tooltip": "Attack enemies while moving (hotkey: A)",
"menu.actions.restart": "Restart",
"menu.control_groups.title": "🎮 Control Groups",
"menu.stats.title": "📈 Game Stats",
"menu.stats.player_units": "Player Units:",
"menu.stats.enemy_units": "Enemy Units:",
"menu.stats.buildings": "Buildings:",
"status.connected": "Connected",
"status.disconnected": "Disconnected",
"game.header.title": "🎮 RTS Commander",
"menu.build.title": "🏗️ Build Menu",
"menu.units.title": "⚔️ Train Units",
"menu.selection.title": "📊 Selection Info",
"menu.selection.none": "No units selected",
"menu.production_queue.title": "🏭 Production Queue",
"menu.production_queue.empty": "Queue is empty",
"control_groups.hint": "Ctrl+[1-9] to assign, [1-9] to select",
"hud.topbar.tick": "Tick:",
"hud.topbar.units": "Units:",
"hud.nuke.charging": "Charging:",
"hud.nuke.ready": "☢️ READY (Press N)",
"notification.moving_units": "Moving {count} units",
"notification.moving_units_distant": "Moving {count} units to distant location",
"notification.connection_error": "Connection error",
"notification.disconnected": "Disconnected from server",
"hud.intel.requesting": "🤖 Requesting tactical analysis...",
"notification.nuke_launched": "💥 NUKE LAUNCHED!",
"notification.nuke_cancelled": "Nuke launch cancelled",
"hud.nuke.select_target": "Select nuke target (Right-click)",
"notification.group.empty": "Group {group}: Empty",
"notification.group.destroyed": "Group {group}: All units destroyed",
"notification.group.assigned": "Group {group} assigned: {count} units",
"notification.group.selected": "Group {group} selected: {count} units",
"hud.intel.header.active": "🤖 Intel: Active",
"hud.intel.header.error": "🤖 Intel: Error",
"hud.intel.status.failed": "Analysis failed",
"hud.intel.source.llm": "Source: Large Language Model",
"hud.intel.source.heuristic": "Source: Heuristic analysis",
"menu.sound.enable": "Enable sound",
"menu.sound.disable": "Disable sound",
"notification.sound.enabled": "Sound enabled",
"notification.sound.disabled": "Sound disabled",
"menu.camera.zoom_in": "Zoom In",
"menu.camera.zoom_out": "Zoom Out",
"menu.camera.reset": "Reset View",
"hud.intel.refresh.tooltip": "Refresh Intel",
"hud.model.download.starting": "Downloading model…",
"hud.model.download.progress": "Downloading: {percent}% ({note})",
"hud.model.download.retry": "Retrying download…",
"hud.model.download.done": "Model ready",
"hud.model.download.error": "Model download failed",
"game.draw.banner": "Draw!",
},
"fr": {
"game.window.title": "RTS Minimaliste",
"game.language.display": "Langue : {language}",
"game.win.banner": "{winner} gagne !",
"game.winner.player": "Joueur",
"game.winner.enemy": "Ennemi",
"hud.topbar.credits": "Crédits : {amount}",
"hud.topbar.power": "Énergie : {produced}/{consumed}",
"hud.topbar.intel.summary": "Renseignement : {summary}",
"hud.topbar.intel.waiting": "Renseignement : en attente",
"hud.intel.header.offline": "Renseignement : hors ligne",
"hud.intel.header.refresh": "Renseignement : cycle {mode}",
"hud.intel.mode.auto": "auto",
"hud.intel.mode.manual": "manuel",
"hud.intel.status.model_missing": "Modèle indisponible",
"hud.intel.status.updating": "Statut : mise à jour…",
"hud.intel.status.updated": "Mis à jour il y a {seconds}s",
"hud.intel.status.waiting": "En attente du premier rapport",
"hud.intel.cycle": "Cycle : ~{seconds}s",
"hud.section.infantry": "Infanterie",
"hud.section.vehicles": "Véhicules",
"hud.section.support": "Soutien",
"hud.section.structures": "Bâtiments",
"hud.button.cost": "{cost} cr",
"unit.infantry": "Infanterie",
"unit.tank": "Char",
"unit.artillery": "Artillerie",
"unit.helicopter": "Hélicoptère",
"unit.harvester": "Collecteur",
"building.hq": "QG",
"building.barracks": "Caserne",
"building.war_factory": "Usine",
"building.refinery": "Raffinerie",
"building.power_plant": "Centrale",
"building.turret": "Tourelle",
"faction.allies": "Alliés",
"faction.soviets": "Soviétiques",
"intel.unavailable": "Renseignement indisponible",
"intel.error": "Erreur renseignement : {error}",
"notification.insufficient_credits": "⚠️ Crédits insuffisants ! Besoin de {cost} cr, vous avez {current} cr",
"notification.low_power": "⚠️ ÉNERGIE FAIBLE ! Construisez plus de centrales ou la production sera lente !",
"notification.no_power": "🚨 PANNE D'ÉNERGIE CRITIQUE ! Bâtiments hors ligne !",
"notification.building_requires": "⚠️ Impossible de construire {building} : nécessite {requirement}",
"notification.unit_requires": "⚠️ Impossible d'entraîner {unit} : nécessite {requirement}",
"notification.building_placed": "Construction de {building}",
"notification.unit_training": "Entraînement de {unit}",
"notification.building_cancelled": "Construction annulée",
"notification.building_too_far_from_hq": "⚠️ Trop éloigné du QG : construisez plus près de votre Quartier Général",
"notification.building_limit_one": "⚠️ Un seul {building} peut être construit",
"notification.production_building_selected": "🏭 La production utilisera : {building}",
"hud.production.source.auto": "Auto",
"hud.production.source.label": "Utilise :",
"hud.production.source.clear": "Effacer la sélection",
"notification.units_moving": "Déplacement de {count} unités",
"notification.units_selected": "{count} unités sélectionnées",
"notification.units_attacking": "🎯 Attaque de {target} ennemi !",
"notification.click_to_place": "🏗️ Cliquez pour placer {building}",
"notification.language_changed": "Langue changée en {language}",
"language.en": "Anglais",
"language.fr": "Français",
"language.zh-TW": "Chinois traditionnel",
"menu.quick_actions.title": "🎯 Actions rapides",
"menu.actions.select_all": "Tout sélectionner",
"menu.actions.select_all.tooltip": "Sélectionner toutes vos unités (raccourci : Ctrl+A)",
"menu.actions.stop": "Arrêter sélection",
"menu.actions.stop.tooltip": "Arrêter les unités sélectionnées",
"menu.actions.attack_move": "Attaque en mouvement",
"menu.actions.attack_move.tooltip": "Attaquer les ennemis en se déplaçant (raccourci : A)",
"menu.actions.restart": "Recommencer",
"menu.control_groups.title": "🎮 Groupes de contrôle",
"menu.stats.title": "📈 Statistiques",
"menu.stats.player_units": "Unités joueur :",
"menu.stats.enemy_units": "Unités ennemies :",
"menu.stats.buildings": "Bâtiments :",
"status.connected": "Connecté",
"status.disconnected": "Déconnecté",
"game.header.title": "🎮 Commandant RTS",
"menu.build.title": "🏗️ Menu construction",
"menu.units.title": "⚔️ Entraîner unités",
"menu.selection.title": "📊 Sélection",
"menu.selection.none": "Aucune unité sélectionnée",
"menu.production_queue.title": "🏭 File de production",
"menu.production_queue.empty": "File vide",
"control_groups.hint": "Ctrl+[1-9] pour assigner, [1-9] pour sélectionner",
"hud.topbar.tick": "Tick :",
"hud.topbar.units": "Unités :",
"hud.nuke.charging": "Chargement :",
"hud.nuke.ready": "☢️ PRÊT (Appuyez sur N)",
"notification.moving_units": "Déplacement de {count} unités",
"notification.moving_units_distant": "Déplacement de {count} unités vers une position éloignée",
"notification.connection_error": "Erreur de connexion",
"notification.disconnected": "Déconnecté du serveur",
"hud.intel.requesting": "🤖 Demande d'analyse tactique...",
"notification.nuke_launched": "💥 BOMBE NUCLÉAIRE LANCÉE !",
"notification.nuke_cancelled": "Lancement nucléaire annulé",
"hud.nuke.select_target": "Sélectionnez la cible nucléaire (clic droit)",
"notification.group.empty": "Groupe {group} : Vide",
"notification.group.destroyed": "Groupe {group} : Toutes les unités détruites",
"notification.group.assigned": "Groupe {group} assigné : {count} unité(s)",
"notification.group.selected": "Groupe {group} sélectionné : {count} unité(s)",
"hud.intel.header.active": "🤖 Renseignement : Actif",
"hud.intel.header.error": "🤖 Renseignement : Erreur",
"hud.intel.status.failed": "Analyse échouée",
"hud.intel.source.llm": "Source : Modèle de langage (LLM)",
"hud.intel.source.heuristic": "Source : Analyse heuristique",
"menu.sound.enable": "Activer le son",
"menu.sound.disable": "Désactiver le son",
"notification.sound.enabled": "Son activé",
"notification.sound.disabled": "Son désactivé",
"game.draw.banner": "Match nul !",
"menu.camera.zoom_in": "Zoom avant",
"menu.camera.zoom_out": "Zoom arrière",
"menu.camera.reset": "Réinitialiser la vue",
"hud.intel.refresh.tooltip": "Rafraîchir le renseignement",
"hud.model.download.starting": "Téléchargement du modèle…",
"hud.model.download.progress": "Téléchargement : {percent}% ({note})",
"hud.model.download.retry": "Nouvelle tentative de téléchargement…",
"hud.model.download.done": "Modèle prêt",
"hud.model.download.error": "Échec du téléchargement du modèle",
},
"zh-TW": {
"game.window.title": "簡約即時戰略",
"game.language.display": "介面語言:{language}",
"game.win.banner": "{winner} 獲勝!",
"game.winner.player": "玩家",
"game.winner.enemy": "敵軍",
"hud.topbar.credits": "資源:{amount}",
"hud.topbar.power": "電力:{produced}/{consumed}",
"hud.topbar.intel.summary": "情報:{summary}",
"hud.topbar.intel.waiting": "情報:等待報告",
"hud.intel.header.offline": "情報:離線",
"hud.intel.header.refresh": "情報:{mode} 更新",
"hud.intel.mode.auto": "自動",
"hud.intel.mode.manual": "手動",
"hud.intel.status.model_missing": "模型不可用",
"hud.intel.status.updating": "狀態:更新中...",
"hud.intel.status.updated": "{seconds} 秒前更新",
"hud.intel.status.waiting": "等待首次報告",
"hud.intel.cycle": "週期:約 {seconds} 秒",
"hud.section.infantry": "步兵",
"hud.section.vehicles": "載具",
"hud.section.support": "支援",
"hud.section.structures": "建築",
"hud.button.cost": "{cost} cr",
"unit.infantry": "步兵",
"unit.tank": "坦克",
"unit.artillery": "火砲",
"unit.helicopter": "直升機",
"unit.harvester": "採礦車",
"building.hq": "總部",
"building.barracks": "兵營",
"building.war_factory": "戰爭工廠",
"building.refinery": "精煉廠",
"building.power_plant": "發電廠",
"building.turret": "防禦砲塔",
"faction.allies": "盟軍",
"faction.soviets": "蘇聯",
"intel.unavailable": "情報不可用",
"intel.error": "情報錯誤:{error}",
"notification.insufficient_credits": "⚠️ 資源不足!需要 {cost} cr,目前有 {current} cr",
"notification.low_power": "⚠️ 電力不足!建造更多發電廠,否則生產將變慢!",
"notification.no_power": "🚨 嚴重電力短缺!建築離線!",
"notification.building_requires": "⚠️ 無法建造 {building}:需要 {requirement}",
"notification.unit_requires": "⚠️ 無法訓練 {unit}:需要 {requirement}",
"notification.building_placed": "建造 {building}",
"notification.unit_training": "訓練 {unit}",
"notification.building_cancelled": "取消建造",
"notification.building_too_far_from_hq": "⚠️ 距離總部太遠:請在更靠近總部的地方建造",
"notification.building_limit_one": "⚠️ 只能建造一座{building}",
"notification.production_building_selected": "🏭 將由此建築物進行生產:{building}",
"hud.production.source.auto": "自動",
"hud.production.source.label": "使用:",
"hud.production.source.clear": "清除選取",
"notification.units_moving": "移動 {count} 個單位",
"notification.units_selected": "已選擇 {count} 個單位",
"notification.units_attacking": "🎯 攻擊敵方 {target}!",
"notification.click_to_place": "🏗️ 點擊放置 {building}",
"notification.language_changed": "語言已更改為 {language}",
"language.en": "英語",
"language.fr": "法語",
"language.zh-TW": "繁體中文",
"menu.quick_actions.title": "🎯 快速動作",
"menu.actions.select_all": "全選單位",
"menu.actions.select_all.tooltip": "選擇所有單位(快捷鍵:Ctrl+A)",
"menu.actions.stop": "停止選取",
"menu.actions.stop.tooltip": "停止選取的單位",
"menu.actions.attack_move": "攻擊移動",
"menu.actions.attack_move.tooltip": "移動時攻擊敵人(快捷鍵:A)",
"menu.actions.restart": "重新開始",
"menu.control_groups.title": "🎮 控制組",
"menu.stats.title": "📈 遊戲統計",
"menu.stats.player_units": "玩家單位:",
"menu.stats.enemy_units": "敵方單位:",
"menu.stats.buildings": "建築:",
"status.connected": "已連接",
"status.disconnected": "已斷線",
"game.header.title": "🎮 RTS 指揮官",
"menu.build.title": "🏗️ 建造選單",
"menu.units.title": "⚔️ 訓練單位",
"menu.selection.title": "📊 選取資訊",
"menu.selection.none": "未選取單位",
"menu.production_queue.title": "🏭 生產佇列",
"menu.production_queue.empty": "佇列為空",
"control_groups.hint": "Ctrl+[1-9] 指派,[1-9] 選取",
"hud.topbar.tick": "Tick:",
"hud.topbar.units": "單位:",
"hud.nuke.charging": "充能中:",
"hud.nuke.ready": "☢️ 就緒(按 N)",
"notification.moving_units": "移動 {count} 個單位",
"notification.moving_units_distant": "移動 {count} 個單位到遠處",
"notification.connection_error": "連線錯誤",
"notification.disconnected": "已從伺服器斷線",
"hud.intel.requesting": "🤖 請求戰術分析...",
"notification.nuke_launched": "💥 核彈發射!",
"notification.nuke_cancelled": "核彈發射已取消",
"hud.nuke.select_target": "選擇核彈目標(右鍵)",
"notification.group.empty": "群組 {group}:空",
"notification.group.destroyed": "群組 {group}:所有單位已被摧毀",
"notification.group.assigned": "群組 {group} 已指派:{count} 個單位",
"notification.group.selected": "群組 {group} 已選取:{count} 個單位",
"hud.intel.header.active": "🤖 情報:運作中",
"hud.intel.header.error": "🤖 情報:錯誤",
"hud.intel.status.failed": "分析失敗",
"hud.intel.source.llm": "來源:大型語言模型 (LLM)",
"hud.intel.source.heuristic": "來源:啟發式分析",
"menu.sound.enable": "開啟聲音",
"menu.sound.disable": "關閉聲音",
"notification.sound.enabled": "已開啟聲音",
"notification.sound.disabled": "已關閉聲音",
"menu.camera.zoom_in": "放大",
"menu.camera.zoom_out": "縮小",
"menu.camera.reset": "重置視角",
"hud.intel.refresh.tooltip": "重新整理情報",
"hud.model.download.starting": "正在下載模型…",
"hud.model.download.progress": "下載中:{percent}%({note})",
"hud.model.download.retry": "重試下載…",
"hud.model.download.done": "模型已就緒",
"hud.model.download.error": "模型下載失敗",
"game.draw.banner": "平手!",
},
}
LANGUAGE_METADATA: Dict[str, LanguageMetadata] = {
"en": LanguageMetadata(
display_name="English",
ai_language_name="English",
ai_example_summary="Allies hold a modest resource advantage and a forward infantry presence near the center.",
),
"fr": LanguageMetadata(
display_name="Français",
ai_language_name="French",
ai_example_summary="Les Alliés disposent d'un léger avantage économique et d'une infanterie avancée près du centre.",
),
"zh-TW": LanguageMetadata(
display_name="繁體中文",
ai_language_name="Traditional Chinese",
ai_example_summary="盟軍在資源上略占優勢,並在中央附近部署前進步兵。",
),
}
DEFAULT_LANGUAGE = "en"
SUPPORTED_LANGUAGES: Iterable[str] = tuple(TRANSLATIONS.keys())
class LocalizationManager:
"""Manages translations for multiple languages"""
def __init__(self) -> None:
self._translations = TRANSLATIONS
self._metadata = LANGUAGE_METADATA
self._order = list(SUPPORTED_LANGUAGES)
def translate(self, language_code: str, key: str, **kwargs) -> str:
"""Get translated string with variable substitution"""
lang_map = self._translations.get(language_code)
template = None if lang_map is None else lang_map.get(key)
if template is None:
template = self._translations[DEFAULT_LANGUAGE].get(key, key)
try:
return template.format(**kwargs)
except (KeyError, ValueError):
return template
def get_supported_languages(self) -> Iterable[str]:
"""Get list of supported language codes"""
return tuple(self._order)
def get_display_name(self, language: str) -> str:
"""Get display name for language"""
metadata = self._metadata.get(language)
if metadata:
return metadata.display_name
return self._metadata[DEFAULT_LANGUAGE].display_name
def get_ai_language_name(self, language: str) -> str:
"""Get AI language name for prompts"""
metadata = self._metadata.get(language)
if metadata:
return metadata.ai_language_name
return self._metadata[DEFAULT_LANGUAGE].ai_language_name
def get_ai_example_summary(self, language: str) -> str:
"""Get example AI summary for language"""
metadata = self._metadata.get(language)
if metadata:
return metadata.ai_example_summary
return self._metadata[DEFAULT_LANGUAGE].ai_example_summary
# Singleton instance
LOCALIZATION = LocalizationManager()
# Helper for Simplified → Traditional Chinese conversion using OpenCC
_opencc_converter = None
def convert_to_traditional(text: str) -> str:
"""Convert Simplified Chinese to Traditional Chinese"""
global _opencc_converter
if not text:
return text
if _opencc_converter is None:
try:
from opencc import OpenCC
_opencc_converter = OpenCC('s2t')
except ImportError:
print("Warning: OpenCC not available for Chinese character conversion")
_opencc_converter = False
except Exception as e:
print(f"Warning: OpenCC initialization failed: {e}")
_opencc_converter = False
if _opencc_converter and _opencc_converter is not False:
try:
convert_fn = getattr(_opencc_converter, 'convert', None)
if callable(convert_fn):
return str(convert_fn(text))
except Exception as e:
print(f"Warning: OpenCC conversion failed: {e}")
return text
return text