重构i18n,现在game configs的配表,除了姓名这种外,都是统一的配表了。 对应的配表的名称和desc需要去i18n里取,但是其他配置不需要重复配置了。 这大大简化了之后新增i18n的心智负担。
178 lines
7.2 KiB
Python
178 lines
7.2 KiB
Python
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
from src.classes.language import language_manager
|
|
from src.utils.df import load_game_configs, game_configs, reload_game_configs
|
|
from src.classes.name import get_random_name, Gender
|
|
|
|
class TestCsvI18n:
|
|
"""
|
|
Test suite for the new Single Source of Truth CSV I18n architecture.
|
|
"""
|
|
|
|
def test_load_csv_injection(self, tmp_path):
|
|
"""
|
|
Unit test: Verify that load_csv correctly injects translations using t().
|
|
"""
|
|
# 1. Setup Mock CSV
|
|
config_dir = tmp_path / "game_configs"
|
|
config_dir.mkdir(parents=True)
|
|
|
|
csv_file = config_dir / "test_item.csv"
|
|
csv_file.write_text(
|
|
"id,name_id,name,desc_id,desc\n"
|
|
"名称ID,名称,描述ID,描述\n"
|
|
"1,TEST_NAME_ID,OriginalName,TEST_DESC_ID,OriginalDesc",
|
|
encoding="utf-8"
|
|
)
|
|
|
|
# 2. Patch CONFIG to point to temp dir
|
|
from src.utils.config import CONFIG
|
|
original_shared_path = CONFIG.paths.shared_game_configs
|
|
original_localized_path = getattr(CONFIG.paths, "localized_game_configs", None)
|
|
|
|
# Point shared config to our temp dir
|
|
CONFIG.paths.shared_game_configs = config_dir
|
|
# Disable localized config for this test to avoid interference
|
|
CONFIG.paths.localized_game_configs = tmp_path / "non_existent"
|
|
|
|
try:
|
|
# 3. Patch t() to return mock translations
|
|
with patch('src.utils.df.t') as mock_t:
|
|
# Mock translation logic
|
|
def side_effect(key, **kwargs):
|
|
if key == "TEST_NAME_ID": return "TranslatedName"
|
|
if key == "TEST_DESC_ID": return "TranslatedDesc"
|
|
return key
|
|
|
|
mock_t.side_effect = side_effect
|
|
|
|
# 4. Load
|
|
loaded = load_game_configs()
|
|
|
|
# 5. Verify
|
|
assert "test_item" in loaded
|
|
item = loaded["test_item"][0]
|
|
|
|
# Verify translation was applied
|
|
assert item["name"] == "TranslatedName"
|
|
assert item["desc"] == "TranslatedDesc"
|
|
|
|
# Verify t() was called with correct IDs
|
|
mock_t.assert_any_call("TEST_NAME_ID")
|
|
mock_t.assert_any_call("TEST_DESC_ID")
|
|
|
|
finally:
|
|
CONFIG.paths.shared_game_configs = original_shared_path
|
|
if original_localized_path:
|
|
CONFIG.paths.localized_game_configs = original_localized_path
|
|
|
|
def test_load_csv_fallback(self, tmp_path):
|
|
"""
|
|
Unit test: Verify fallback to CSV values when translation is missing.
|
|
"""
|
|
config_dir = tmp_path / "game_configs"
|
|
config_dir.mkdir(parents=True)
|
|
|
|
# Note: load_csv skips row 2 if it exists (assuming it's a comment row)
|
|
# So we provide: Header, Comment, Data
|
|
csv_file = config_dir / "test_fallback.csv"
|
|
csv_file.write_text(
|
|
"id,name_id,name\n"
|
|
"ID,ID_KEY,NAME_KEY\n"
|
|
"1,MISSING_ID,OriginalName",
|
|
encoding="utf-8"
|
|
)
|
|
|
|
from src.utils.config import CONFIG
|
|
original_shared_path = CONFIG.paths.shared_game_configs
|
|
original_localized_path = getattr(CONFIG.paths, "localized_game_configs", None)
|
|
|
|
CONFIG.paths.shared_game_configs = config_dir
|
|
CONFIG.paths.localized_game_configs = tmp_path / "non_existent"
|
|
|
|
try:
|
|
with patch('src.utils.df.t') as mock_t:
|
|
# Mock t() returning the key itself (standard gettext behavior for missing keys)
|
|
mock_t.side_effect = lambda k, **kw: k
|
|
|
|
loaded = load_game_configs()
|
|
item = loaded["test_fallback"][0]
|
|
|
|
# Should fallback to "OriginalName" because t("MISSING_ID") == "MISSING_ID"
|
|
assert item["name"] == "OriginalName"
|
|
|
|
finally:
|
|
CONFIG.paths.shared_game_configs = original_shared_path
|
|
if original_localized_path:
|
|
CONFIG.paths.localized_game_configs = original_localized_path
|
|
|
|
def test_integration_switch_language(self):
|
|
"""
|
|
Integration test: Verify real config loading and language switching.
|
|
Requires actual static/game_configs files and compiled .mo files.
|
|
"""
|
|
# Ensure we are using the real paths
|
|
try:
|
|
# 1. Switch to zh-CN
|
|
language_manager.set_language("zh-CN")
|
|
# Ensure reload happens
|
|
reload_game_configs()
|
|
|
|
# Check a known item (e.g. hidden_domain)
|
|
domains = game_configs.get("hidden_domain")
|
|
if not domains:
|
|
pytest.skip("hidden_domain.csv not found or empty")
|
|
|
|
first_domain_zh = domains[0]
|
|
# zh-CN name should be Chinese
|
|
# Heuristic: Check for Chinese char range
|
|
assert any("\u4e00" <= c <= "\u9fff" for c in first_domain_zh["name"]), f"Expected Chinese name, got {first_domain_zh['name']}"
|
|
|
|
# 2. Switch to en-US
|
|
language_manager.set_language("en-US")
|
|
reload_game_configs()
|
|
|
|
domains_en = game_configs.get("hidden_domain")
|
|
first_domain_en = domains_en[0]
|
|
|
|
# en-US name should NOT be Chinese
|
|
assert not any("\u4e00" <= c <= "\u9fff" for c in first_domain_en["name"]), f"Expected English name, got {first_domain_en['name']}"
|
|
assert first_domain_en["name"] != first_domain_zh["name"]
|
|
|
|
finally:
|
|
# Reset to zh-CN
|
|
language_manager.set_language("zh-CN")
|
|
reload_game_configs()
|
|
|
|
def test_name_manager_i18n(self):
|
|
"""
|
|
Integration test: Verify NameManager loads correct files based on language.
|
|
"""
|
|
try:
|
|
# 1. zh-CN
|
|
language_manager.set_language("zh-CN")
|
|
# NameManager auto-reloads on init, but we need to force reload since it's a singleton
|
|
from src.classes.name import reload as reload_names, _name_manager
|
|
reload_names()
|
|
|
|
# Check internal lists
|
|
# common_last_names should contain "李", "王" etc.
|
|
assert len(_name_manager.common_last_names) > 0
|
|
assert "李" in _name_manager.common_last_names or "王" in _name_manager.common_last_names
|
|
|
|
# 2. en-US
|
|
language_manager.set_language("en-US")
|
|
reload_names()
|
|
|
|
# common_last_names should contain "Li", "Wang" etc. (from last_name_en.csv)
|
|
assert len(_name_manager.common_last_names) > 0
|
|
# Check that it's NOT Chinese chars
|
|
first_name = _name_manager.common_last_names[0]
|
|
assert not any("\u4e00" <= c <= "\u9fff" for c in first_name)
|
|
finally:
|
|
# Reset to zh-CN
|
|
language_manager.set_language("zh-CN")
|
|
from src.classes.name import reload as reload_names
|
|
reload_names()
|