chore: import deployable mai-bot source tree
This commit is contained in:
66
pytests/i18n_test/test_i18n.py
Normal file
66
pytests/i18n_test/test_i18n.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from src.common.i18n.manager import I18nManager
|
||||
from src.common.i18n.loaders import DuplicateTranslationKeyError, load_locale_catalog
|
||||
|
||||
|
||||
def write_locale_file(locales_root: Path, locale: str, file_name: str, payload: dict[str, object]) -> None:
|
||||
locale_dir = locales_root / locale
|
||||
locale_dir.mkdir(parents=True, exist_ok=True)
|
||||
file_path = locale_dir / file_name
|
||||
file_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def test_t_falls_back_to_default_locale(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(locales_root, "zh-CN", "core.json", {"greeting": "你好,{name}"})
|
||||
write_locale_file(locales_root, "en-US", "core.json", {})
|
||||
|
||||
manager = I18nManager(locales_root=locales_root)
|
||||
|
||||
assert manager.t("greeting", locale="en-US", name="Mai") == "你好,Mai"
|
||||
|
||||
|
||||
def test_t_returns_key_when_missing_everywhere(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(locales_root, "zh-CN", "core.json", {})
|
||||
write_locale_file(locales_root, "en-US", "core.json", {})
|
||||
|
||||
manager = I18nManager(locales_root=locales_root)
|
||||
|
||||
assert manager.t("missing.key", locale="en-US") == "missing.key"
|
||||
|
||||
|
||||
def test_tn_uses_plural_rules(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(
|
||||
locales_root,
|
||||
"en-US",
|
||||
"core.json",
|
||||
{
|
||||
"tasks.cancelled": {
|
||||
"one": "Cancelled {count} task",
|
||||
"other": "Cancelled {count} tasks",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
manager = I18nManager(default_locale="en-US", locales_root=locales_root)
|
||||
|
||||
assert manager.tn("tasks.cancelled", 1) == "Cancelled 1 task"
|
||||
assert manager.tn("tasks.cancelled", 2) == "Cancelled 2 tasks"
|
||||
|
||||
|
||||
def test_load_locale_catalog_rejects_duplicate_keys(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(locales_root, "zh-CN", "a.json", {"duplicate.key": "A"})
|
||||
write_locale_file(locales_root, "zh-CN", "b.json", {"duplicate.key": "B"})
|
||||
|
||||
with pytest.raises(DuplicateTranslationKeyError):
|
||||
load_locale_catalog("zh-CN", locales_root)
|
||||
110
pytests/i18n_test/test_i18n_validate.py
Normal file
110
pytests/i18n_test/test_i18n_validate.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
|
||||
SCRIPT_PATH = Path(__file__).resolve().parents[2] / "scripts" / "i18n_validate.py"
|
||||
MODULE_SPEC = spec_from_file_location("i18n_validate_script", SCRIPT_PATH)
|
||||
assert MODULE_SPEC is not None
|
||||
assert MODULE_SPEC.loader is not None
|
||||
I18N_VALIDATE = module_from_spec(MODULE_SPEC)
|
||||
MODULE_SPEC.loader.exec_module(I18N_VALIDATE)
|
||||
|
||||
|
||||
def write_locale_file(locales_root: Path, locale: str, file_name: str, payload: dict[str, object]) -> None:
|
||||
locale_dir = locales_root / locale
|
||||
locale_dir.mkdir(parents=True, exist_ok=True)
|
||||
(locale_dir / file_name).write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def write_dashboard_locale_file(locales_root: Path, locale: str, payload: dict[str, object]) -> None:
|
||||
locales_root.mkdir(parents=True, exist_ok=True)
|
||||
(locales_root / f"{locale}.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def test_validate_json_locales_rejects_han_characters_in_english_locale(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(locales_root, "zh-CN", "core.json", {"consent.prompt": '输入"同意"继续'})
|
||||
write_locale_file(locales_root, "en-US", "core.json", {"consent.prompt": 'Type "confirmed" or "同意" to continue'})
|
||||
|
||||
errors = I18N_VALIDATE.validate_json_locales(locales_root)
|
||||
|
||||
assert any("consent.prompt" in error and "仍包含中文字符" in error for error in errors)
|
||||
|
||||
|
||||
def test_validate_json_locales_rejects_untranslated_han_source_in_other_target_locales(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(locales_root, "zh-CN", "core.json", {"greeting": "你好,世界"})
|
||||
write_locale_file(locales_root, "ja", "core.json", {"greeting": "你好,世界"})
|
||||
|
||||
errors = I18N_VALIDATE.validate_json_locales(locales_root)
|
||||
|
||||
assert any("greeting" in error and "直接保留了包含中文字符的 source 文案" in error for error in errors)
|
||||
|
||||
|
||||
def test_validate_json_locales_avoids_false_positive_when_plural_categories_do_not_align(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "locales"
|
||||
write_locale_file(
|
||||
locales_root,
|
||||
"zh-CN",
|
||||
"core.json",
|
||||
{
|
||||
"tasks.cancelled": {
|
||||
"one": "中文单数",
|
||||
"other": "中文复数",
|
||||
}
|
||||
},
|
||||
)
|
||||
write_locale_file(
|
||||
locales_root,
|
||||
"ja",
|
||||
"core.json",
|
||||
{
|
||||
"tasks.cancelled": {
|
||||
"many": "中文单数",
|
||||
"other": "已翻译",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
errors = I18N_VALIDATE.validate_json_locales(locales_root)
|
||||
|
||||
assert any("tasks.cancelled" in error and "plural category 不一致" in error for error in errors)
|
||||
assert not any("tasks.cancelled" in error and "直接保留了包含中文字符的 source 文案" in error for error in errors)
|
||||
|
||||
|
||||
def test_validate_dashboard_json_locales_rejects_han_characters_in_english_locale(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "dashboard-locales"
|
||||
write_dashboard_locale_file(locales_root, "zh", {"common": {"greeting": "你好,世界"}})
|
||||
write_dashboard_locale_file(locales_root, "en", {"common": {"greeting": "Hello 同意"}})
|
||||
|
||||
errors = I18N_VALIDATE.validate_dashboard_json_locales(locales_root)
|
||||
|
||||
assert any("dashboard:en" in error and "common.greeting" in error and "仍包含中文字符" in error for error in errors)
|
||||
|
||||
|
||||
def test_validate_dashboard_json_locales_rejects_untranslated_han_source_in_other_target_locales(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
locales_root = tmp_path / "dashboard-locales"
|
||||
write_dashboard_locale_file(locales_root, "zh", {"common": {"greeting": "你好,世界"}})
|
||||
write_dashboard_locale_file(locales_root, "ja", {"common": {"greeting": "你好,世界"}})
|
||||
|
||||
errors = I18N_VALIDATE.validate_dashboard_json_locales(locales_root)
|
||||
|
||||
assert any(
|
||||
"dashboard:ja" in error and "common.greeting" in error and "直接保留了包含中文字符的 source 文案" in error
|
||||
for error in errors
|
||||
)
|
||||
|
||||
|
||||
def test_validate_dashboard_json_locales_rejects_i18next_placeholder_drift(tmp_path: Path) -> None:
|
||||
locales_root = tmp_path / "dashboard-locales"
|
||||
write_dashboard_locale_file(locales_root, "zh", {"status": {"checkingDesc": "等待服务恢复... ({{current}}/{{max}})"}})
|
||||
write_dashboard_locale_file(locales_root, "ko", {"status": {"checkingDesc": "서비스 복구 대기 중... ({{current}}/{{limit}})"}})
|
||||
|
||||
errors = I18N_VALIDATE.validate_dashboard_json_locales(locales_root)
|
||||
|
||||
assert any("dashboard:ko" in error and "status.checkingDesc" in error and "占位符集合与 source 不一致" in error for error in errors)
|
||||
Reference in New Issue
Block a user