feat(i18n): enhance localization with new actions and descriptions
- Added new action translations for "assassinate", "attack", "breakthrough", "cultivate", "escape", and "self-heal" in English, Simplified Chinese, and Traditional Chinese. - Included detailed descriptions and requirements for each action to improve gameplay clarity. - Updated the translation extraction and compilation process to utilize the polib library for better handling of PO files.
This commit is contained in:
@@ -74,20 +74,28 @@ class TestTranslationKeysUsage:
|
||||
msgids = set()
|
||||
|
||||
try:
|
||||
content = po_file.read_text(encoding='utf-8')
|
||||
pattern = r'msgid\s+"([^"]*)"'
|
||||
matches = re.findall(pattern, content)
|
||||
# 将 po 文件中的转义序列(如 \\n \\t等)转换为实际字符
|
||||
# 使用字符串替换而不是 decode,避免中文字符编码问题
|
||||
decoded_msgids = set()
|
||||
for m in matches:
|
||||
if m: # 排除空字符串
|
||||
# 替换常见的转义序列
|
||||
decoded = m.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r').replace('\\"', '"').replace("\\'", "'").replace('\\\\', '\\')
|
||||
decoded_msgids.add(decoded)
|
||||
msgids = decoded_msgids
|
||||
import polib
|
||||
po = polib.pofile(str(po_file))
|
||||
for entry in po:
|
||||
msgids.add(entry.msgid)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {po_file}: {e}")
|
||||
print(f"Warning: Could not read {po_file} with polib: {e}")
|
||||
# Fallback to regex
|
||||
try:
|
||||
content = po_file.read_text(encoding='utf-8')
|
||||
pattern = r'msgid\s+"([^"]*)"'
|
||||
matches = re.findall(pattern, content)
|
||||
# 将 po 文件中的转义序列(如 \\n \\t等)转换为实际字符
|
||||
# 使用字符串替换而不是 decode,避免中文字符编码问题
|
||||
decoded_msgids = set()
|
||||
for m in matches:
|
||||
if m: # 排除空字符串
|
||||
# 替换常见的转义序列
|
||||
decoded = m.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r').replace('\\"', '"').replace("\\'", "'").replace('\\\\', '\\')
|
||||
decoded_msgids.add(decoded)
|
||||
msgids = decoded_msgids
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {po_file}: {e}")
|
||||
|
||||
return msgids
|
||||
|
||||
@@ -263,25 +271,30 @@ class TestI18nConsistency:
|
||||
|
||||
def extract_msgid_msgstr_pairs(po_file):
|
||||
"""提取 msgid 和 msgstr 对"""
|
||||
content = po_file.read_text(encoding='utf-8')
|
||||
pairs = {}
|
||||
|
||||
lines = content.split('\n')
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if line.startswith('msgid "') and line != 'msgid ""':
|
||||
msgid = line[7:-1] # 提取引号内的内容
|
||||
|
||||
# 查找对应的 msgstr
|
||||
i += 1
|
||||
while i < len(lines) and not lines[i].strip().startswith('msgstr '):
|
||||
try:
|
||||
import polib
|
||||
po = polib.pofile(str(po_file))
|
||||
for entry in po:
|
||||
pairs[entry.msgid] = entry.msgstr
|
||||
except Exception:
|
||||
content = po_file.read_text(encoding='utf-8')
|
||||
lines = content.split('\n')
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if line.startswith('msgid "') and line != 'msgid ""':
|
||||
msgid = line[7:-1] # 提取引号内的内容
|
||||
|
||||
# 查找对应的 msgstr
|
||||
i += 1
|
||||
|
||||
if i < len(lines):
|
||||
msgstr = lines[i].strip()[8:-1] # 提取引号内的内容
|
||||
pairs[msgid] = msgstr
|
||||
i += 1
|
||||
while i < len(lines) and not lines[i].strip().startswith('msgstr '):
|
||||
i += 1
|
||||
|
||||
if i < len(lines):
|
||||
msgstr = lines[i].strip()[8:-1] # 提取引号内的内容
|
||||
pairs[msgid] = msgstr
|
||||
i += 1
|
||||
|
||||
return pairs
|
||||
|
||||
|
||||
@@ -18,19 +18,24 @@ except ImportError:
|
||||
PYTEST_AVAILABLE = False
|
||||
|
||||
|
||||
import polib
|
||||
|
||||
def extract_msgids(filepath: Path) -> list[str]:
|
||||
"""从 po 文件中提取所有 msgid"""
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 匹配 msgid "..." 模式
|
||||
pattern = r'msgid\s+"([^"]*)"'
|
||||
matches = re.findall(pattern, content)
|
||||
|
||||
# 过滤掉空字符串(文件头的 msgid "")
|
||||
msgids = [m for m in matches if m]
|
||||
|
||||
return msgids
|
||||
if not filepath.exists():
|
||||
return []
|
||||
|
||||
try:
|
||||
po = polib.pofile(str(filepath))
|
||||
return [entry.msgid for entry in po]
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {filepath} with polib: {e}")
|
||||
# Fallback to regex
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
pattern = r'msgid\s+"([^"]*)"'
|
||||
matches = re.findall(pattern, content)
|
||||
return [m for m in matches if m]
|
||||
|
||||
|
||||
def find_duplicates(msgids: list[str]) -> dict[str, int]:
|
||||
|
||||
87
tests/test_i18n_modules.py
Normal file
87
tests/test_i18n_modules.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
import polib
|
||||
from src.i18n import t, _get_locale_dir
|
||||
|
||||
def test_i18n_modules_structure():
|
||||
"""
|
||||
Test that the i18n modules structure is correct.
|
||||
"""
|
||||
locale_dir = _get_locale_dir()
|
||||
# Directory names use underscores (zh_CN), not hyphens (zh-CN)
|
||||
languages = ["zh_CN", "en_US", "zh_TW"]
|
||||
|
||||
for lang in languages:
|
||||
lang_dir = locale_dir / lang
|
||||
assert lang_dir.exists(), f"Language directory {lang} does not exist"
|
||||
|
||||
modules_dir = lang_dir / "modules"
|
||||
assert modules_dir.exists(), f"Modules directory for {lang} does not exist"
|
||||
assert modules_dir.is_dir()
|
||||
|
||||
# Check if there are .po files in modules
|
||||
po_files = list(modules_dir.glob("*.po"))
|
||||
assert len(po_files) > 0, f"No .po files found in {modules_dir}"
|
||||
|
||||
# Check specific expected modules
|
||||
expected_modules = ["battle.po", "action.po", "fortune.po"]
|
||||
for mod in expected_modules:
|
||||
assert (modules_dir / mod).exists(), f"Expected module {mod} missing in {lang}"
|
||||
|
||||
def test_merged_messages_po_integrity():
|
||||
"""
|
||||
Test that the merged messages.po file in LC_MESSAGES exists and contains entries from modules.
|
||||
"""
|
||||
locale_dir = _get_locale_dir()
|
||||
lang = "zh_CN" # Test with one language (underscore)
|
||||
|
||||
lc_messages_dir = locale_dir / lang / "LC_MESSAGES"
|
||||
messages_po_path = lc_messages_dir / "messages.po"
|
||||
|
||||
assert messages_po_path.exists(), "Merged messages.po does not exist"
|
||||
|
||||
merged_po = polib.pofile(str(messages_po_path))
|
||||
assert len(merged_po) > 0, "Merged messages.po is empty"
|
||||
|
||||
# Check a random key from a module (e.g., from battle.po)
|
||||
# We know "attack_action_name" should be in action.po or battle.po
|
||||
# Actually "attack_action_name" is in action.po
|
||||
|
||||
found_attack = False
|
||||
for entry in merged_po:
|
||||
if entry.msgid == "attack_action_name":
|
||||
found_attack = True
|
||||
break
|
||||
|
||||
assert found_attack, "Key 'attack_action_name' (from action.po) not found in merged messages.po"
|
||||
|
||||
def test_translation_loading():
|
||||
"""
|
||||
Test that translations are actually loaded and working.
|
||||
"""
|
||||
# This relies on the force_chinese_language fixture in conftest.py if run in full suite,
|
||||
# or we explicitly set it here.
|
||||
from src.classes.language import language_manager
|
||||
language_manager.set_language("zh-CN")
|
||||
|
||||
# Test a known key
|
||||
# "attack_action_name" -> "发起战斗"
|
||||
assert t("attack_action_name") == "发起战斗"
|
||||
|
||||
# Test a formatted string
|
||||
# "{winner} defeated {loser}" -> "{winner} 战胜了 {loser}"
|
||||
# Note: the translation might be different, let's check exact value from file or flexible match
|
||||
res = t("{winner} defeated {loser}", winner="A", loser="B")
|
||||
assert "战胜了" in res
|
||||
|
||||
def test_split_po_script_exists():
|
||||
"""
|
||||
Ensure the maintenance scripts exist.
|
||||
"""
|
||||
root = Path(__file__).parent.parent
|
||||
split_script = root / "tools" / "i18n" / "split_po.py"
|
||||
build_script = root / "tools" / "i18n" / "build_mo.py"
|
||||
|
||||
assert split_script.exists()
|
||||
assert build_script.exists()
|
||||
@@ -141,17 +141,26 @@ class TestTranslationKeysDefinition:
|
||||
|
||||
msgids = set()
|
||||
try:
|
||||
content = po_file.read_text(encoding='utf-8')
|
||||
pattern = r'msgid\s+"([^"]*)"'
|
||||
matches = re.findall(pattern, content)
|
||||
# 手动处理常见的转义序列
|
||||
for m in matches:
|
||||
if m: # 排除空字符串
|
||||
# 只替换常见的转义序列,保留Unicode字符
|
||||
decoded = m.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
|
||||
msgids.add(decoded)
|
||||
import polib
|
||||
po = polib.pofile(str(po_file))
|
||||
for entry in po:
|
||||
msgids.add(entry.msgid)
|
||||
except ImportError:
|
||||
print("Warning: polib not installed, falling back to regex (less accurate)")
|
||||
try:
|
||||
content = po_file.read_text(encoding='utf-8')
|
||||
pattern = r'msgid\s+"([^"]*)"'
|
||||
matches = re.findall(pattern, content)
|
||||
# 手动处理常见的转义序列
|
||||
for m in matches:
|
||||
if m: # 排除空字符串
|
||||
# 只替换常见的转义序列,保留Unicode字符
|
||||
decoded = m.replace('\\n', '\n').replace('\\t', '\t').replace('\\r', '\r')
|
||||
msgids.add(decoded)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {po_file}: {e}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {po_file}: {e}")
|
||||
print(f"Warning: Could not read {po_file} with polib: {e}")
|
||||
|
||||
return msgids
|
||||
|
||||
|
||||
Reference in New Issue
Block a user