Refactor/i18n (#115)
重构i18n,现在game configs的配表,除了姓名这种外,都是统一的配表了。 对应的配表的名称和desc需要去i18n里取,但是其他配置不需要重复配置了。 这大大简化了之后新增i18n的心智负担。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
|
||||
from src.classes.action.registry import ActionRegistry
|
||||
# 确保在收集注册表前加载所有动作模块(含 mutual actions)
|
||||
@@ -26,10 +27,22 @@ def _build_action_info(action):
|
||||
info["cd_months"] = cd
|
||||
return info
|
||||
|
||||
ACTION_INFOS = {
|
||||
action.__name__: _build_action_info(action)
|
||||
for action in ALL_ACTUAL_ACTION_CLASSES
|
||||
}
|
||||
def get_action_infos() -> Dict[str, Any]:
|
||||
"""
|
||||
动态获取当前语言环境下的动作描述信息
|
||||
"""
|
||||
return {
|
||||
action.__name__: _build_action_info(action)
|
||||
for action in ALL_ACTUAL_ACTION_CLASSES
|
||||
}
|
||||
|
||||
def get_action_infos_str() -> str:
|
||||
"""
|
||||
获取JSON格式的动作描述字符串
|
||||
"""
|
||||
return json.dumps(get_action_infos(), ensure_ascii=False, indent=2)
|
||||
|
||||
# 为了兼容性保留 ACTION_INFOS_STR,但请注意这可能是旧的(导入时的快照),不会随语言切换更新
|
||||
# 建议使用 get_action_infos_str() 获取最新语言的描述
|
||||
ACTION_INFOS = get_action_infos()
|
||||
ACTION_INFOS_STR = json.dumps(ACTION_INFOS, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ class HiddenDomain(Gathering):
|
||||
|
||||
# 3. 角色信息 (可选,增加故事细节)
|
||||
details_list = []
|
||||
details_list.append(t("【Related Avatars Information】"))
|
||||
details_list.append(t("\n【Related Avatars Information】"))
|
||||
for av in related_avatars:
|
||||
info = av.get_info(detailed=True)
|
||||
details_list.append(f"- {av.name}: {info}")
|
||||
|
||||
@@ -34,6 +34,12 @@ class LanguageManager:
|
||||
except ImportError:
|
||||
# Prevent circular import crash during initialization
|
||||
pass
|
||||
|
||||
try:
|
||||
from src.classes.name import reload as reload_names
|
||||
reload_names()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -14,7 +14,7 @@ from src.classes.event import Event
|
||||
from src.utils.config import CONFIG
|
||||
from src.utils.llm import call_llm_with_task_name
|
||||
from src.run.log import get_logger
|
||||
from src.classes.actions import ACTION_INFOS_STR
|
||||
from src.classes.actions import get_action_infos_str
|
||||
from src.i18n import t
|
||||
|
||||
logger = get_logger().logger
|
||||
@@ -90,7 +90,7 @@ async def generate_long_term_objective(avatar: "Avatar") -> Optional[LongTermObj
|
||||
infos = {
|
||||
"world_info": world_info,
|
||||
"avatar_info": expanded_info,
|
||||
"general_action_infos": ACTION_INFOS_STR,
|
||||
"general_action_infos": get_action_infos_str(),
|
||||
}
|
||||
|
||||
# 调用LLM并自动解析JSON(使用配置的模型模式)
|
||||
|
||||
@@ -47,10 +47,11 @@ class NameManager:
|
||||
for g_list in self.common_given_names.values():
|
||||
g_list.clear()
|
||||
self.sect_given_names.clear()
|
||||
|
||||
# 加载姓氏
|
||||
if "last_name" in game_configs:
|
||||
last_name_df = game_configs["last_name"]
|
||||
|
||||
# 加载姓氏 (不再区分 _en 后缀,因为 config/df 已经处理了加载逻辑)
|
||||
last_name_df = game_configs.get("last_name", [])
|
||||
|
||||
if last_name_df:
|
||||
for row in last_name_df:
|
||||
name = get_str(row, "last_name")
|
||||
sect_id = get_int(row, "sect_id")
|
||||
@@ -63,8 +64,9 @@ class NameManager:
|
||||
self.common_last_names.append(name)
|
||||
|
||||
# 加载名字
|
||||
if "given_name" in game_configs:
|
||||
given_name_df = game_configs["given_name"]
|
||||
given_name_df = game_configs.get("given_name", [])
|
||||
|
||||
if given_name_df:
|
||||
for row in given_name_df:
|
||||
name = get_str(row, "given_name")
|
||||
gender_val = get_int(row, "gender") # 0 or 1
|
||||
|
||||
@@ -68,10 +68,23 @@ def _get_translation() -> Optional[gettext.GNUTranslations]:
|
||||
localedir=str(locale_dir),
|
||||
languages=[locale_name]
|
||||
)
|
||||
_translations[lang] = trans
|
||||
except FileNotFoundError:
|
||||
# No translation file found, will use message as-is.
|
||||
_translations[lang] = None
|
||||
trans = None
|
||||
|
||||
try:
|
||||
config_trans = gettext.translation(
|
||||
"game_configs",
|
||||
localedir=str(locale_dir),
|
||||
languages=[locale_name]
|
||||
)
|
||||
if trans:
|
||||
trans.add_fallback(config_trans)
|
||||
else:
|
||||
trans = config_trans
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
_translations[lang] = trans
|
||||
|
||||
return _translations.get(lang)
|
||||
|
||||
|
||||
BIN
src/i18n/locales/en_US/LC_MESSAGES/game_configs.mo
Normal file
BIN
src/i18n/locales/en_US/LC_MESSAGES/game_configs.mo
Normal file
Binary file not shown.
3020
src/i18n/locales/en_US/LC_MESSAGES/game_configs.po
Normal file
3020
src/i18n/locales/en_US/LC_MESSAGES/game_configs.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -2089,11 +2089,9 @@ msgstr "【Auction Items Information】"
|
||||
|
||||
|
||||
msgid "\n【Related Avatars Information】"
|
||||
|
||||
msgstr "\n【Related Avatars Information】"
|
||||
|
||||
|
||||
|
||||
# LLM Prompt
|
||||
|
||||
msgid "auction_story_prompt"
|
||||
@@ -3420,5 +3418,11 @@ msgstr "Hidden Domain {name} opened! Entry restricted to {realm} and below. Entr
|
||||
msgid "Hidden Domain {name} opened! Entry restricted to {realm} and below. No one entered."
|
||||
msgstr "Hidden Domain {name} opened! Entry restricted to {realm} and below. No one entered."
|
||||
|
||||
msgid "【Related Avatars Information】"
|
||||
msgstr "【Related Avatars Information】"
|
||||
msgid "{}: {}"
|
||||
msgstr "{}: {}"
|
||||
|
||||
msgid "found a treasure"
|
||||
msgstr "found a treasure"
|
||||
|
||||
msgid "perished"
|
||||
msgstr "perished"
|
||||
|
||||
2969
src/i18n/locales/templates/game_configs.pot
Normal file
2969
src/i18n/locales/templates/game_configs.pot
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/i18n/locales/zh_CN/LC_MESSAGES/game_configs.mo
Normal file
BIN
src/i18n/locales/zh_CN/LC_MESSAGES/game_configs.mo
Normal file
Binary file not shown.
2972
src/i18n/locales/zh_CN/LC_MESSAGES/game_configs.po
Normal file
2972
src/i18n/locales/zh_CN/LC_MESSAGES/game_configs.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -2261,5 +2261,13 @@ msgstr "秘境【{name}】现世!限制{realm}及以下修士进入。进入
|
||||
msgid "Hidden Domain {name} opened! Entry restricted to {realm} and below. No one entered."
|
||||
msgstr "秘境【{name}】现世!限制{realm}及以下修士进入。无修士进入。"
|
||||
|
||||
msgid "【Related Avatars Information】"
|
||||
msgstr "【相关角色信息】"
|
||||
|
||||
msgid "{}: {}"
|
||||
msgstr "{}:{}"
|
||||
|
||||
msgid "found a treasure"
|
||||
msgstr "觅得宝物"
|
||||
|
||||
msgid "perished"
|
||||
msgstr "葬身于"
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ from src.classes.sect import sects_by_id # 直接导入已加载的宗门数据
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
# 静态配置路径
|
||||
# 使用共享配置目录,因为 tile_map 和 region_map 是语言无关的
|
||||
CONFIG_DIR = CONFIG.paths.shared_game_configs
|
||||
CONFIG_DIR = CONFIG.paths.game_configs
|
||||
|
||||
def load_cultivation_world_map() -> Map:
|
||||
"""
|
||||
|
||||
@@ -79,14 +79,19 @@ def update_paths_for_language(lang_code: str = None):
|
||||
# 语言无关的配置目录
|
||||
CONFIG.paths.shared_game_configs = Path("static/game_configs")
|
||||
# 语言相关的配置目录
|
||||
CONFIG.paths.game_configs = target_dir / "game_configs"
|
||||
CONFIG.paths.localized_game_configs = target_dir / "game_configs"
|
||||
|
||||
# CONFIG.paths.game_configs 指向统一的数据源,不再区分语言目录
|
||||
# 这里我们保留 CONFIG.paths.game_configs 作为"逻辑概念上的"配置集合根目录(虽然物理上分开了)
|
||||
# 但实际上加载逻辑会在 df.py 中处理合并
|
||||
CONFIG.paths.game_configs = Path("static/game_configs")
|
||||
CONFIG.paths.templates = target_dir / "templates"
|
||||
|
||||
# 简单的存在性检查日志
|
||||
if not CONFIG.paths.game_configs.exists():
|
||||
print(f"[Config] Warning: Game configs dir not found at {CONFIG.paths.game_configs}")
|
||||
else:
|
||||
print(f"[Config] Switched paths to {lang_code}")
|
||||
print(f"[Config] Switched language context to {lang_code} (Configs using Single Source)")
|
||||
|
||||
# 模块加载时自动初始化默认路径,确保 CONFIG.paths.game_configs 存在,避免 import 时 KeyError
|
||||
update_paths_for_language()
|
||||
|
||||
@@ -3,6 +3,7 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from src.utils.config import CONFIG
|
||||
from src.i18n import t
|
||||
|
||||
|
||||
def load_csv(path: Path) -> List[Dict[str, Any]]:
|
||||
@@ -59,6 +60,32 @@ def load_csv(path: Path) -> List[Dict[str, Any]]:
|
||||
else:
|
||||
row_dict[header] = None
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# I18N Translation Injection
|
||||
# -----------------------------------------------------------
|
||||
# Try to translate name, desc and title using their IDs.
|
||||
# If translation exists (and is not just the key itself), overwrite the value.
|
||||
# Fallback is keeping the original value from CSV (usually Chinese reference).
|
||||
|
||||
title_id = row_dict.get("title_id")
|
||||
if title_id and isinstance(title_id, str):
|
||||
trans = t(title_id)
|
||||
if trans != title_id and trans:
|
||||
row_dict["title"] = trans
|
||||
|
||||
name_id = row_dict.get("name_id")
|
||||
if name_id and isinstance(name_id, str):
|
||||
trans = t(name_id)
|
||||
if trans != name_id and trans:
|
||||
row_dict["name"] = trans
|
||||
|
||||
desc_id = row_dict.get("desc_id")
|
||||
if desc_id and isinstance(desc_id, str):
|
||||
trans = t(desc_id)
|
||||
if trans != desc_id and trans:
|
||||
row_dict["desc"] = trans
|
||||
# -----------------------------------------------------------
|
||||
|
||||
data.append(row_dict)
|
||||
|
||||
return data
|
||||
@@ -66,17 +93,17 @@ def load_csv(path: Path) -> List[Dict[str, Any]]:
|
||||
def load_game_configs() -> dict[str, List[Dict[str, Any]]]:
|
||||
game_configs = {}
|
||||
|
||||
# 1. 加载共享配置 (Shared)
|
||||
# 1. 加载共享配置 (static/game_configs/*.csv)
|
||||
if hasattr(CONFIG.paths, "shared_game_configs") and CONFIG.paths.shared_game_configs.exists():
|
||||
for path in CONFIG.paths.shared_game_configs.glob("*.csv"):
|
||||
data = load_csv(path)
|
||||
game_configs[path.stem] = data
|
||||
|
||||
# 2. 加载本地化配置 (Localized) - 会覆盖同名文件
|
||||
if hasattr(CONFIG.paths, "game_configs") and CONFIG.paths.game_configs.exists():
|
||||
for path in CONFIG.paths.game_configs.glob("*.csv"):
|
||||
|
||||
# 2. 加载本地化配置 (static/locales/{lang}/game_configs/*.csv)
|
||||
# 如果文件名相同,覆盖共享配置
|
||||
if hasattr(CONFIG.paths, "localized_game_configs") and CONFIG.paths.localized_game_configs.exists():
|
||||
for path in CONFIG.paths.localized_game_configs.glob("*.csv"):
|
||||
data = load_csv(path)
|
||||
# 如果存在同名配置,这里会进行覆盖
|
||||
game_configs[path.stem] = data
|
||||
|
||||
return game_configs
|
||||
|
||||
Reference in New Issue
Block a user