summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_i18n.py110
-rw-r--r--tests/test_i18n_rendering.py72
2 files changed, 182 insertions, 0 deletions
diff --git a/tests/test_i18n.py b/tests/test_i18n.py
new file mode 100644
index 0000000..48f8b9f
--- /dev/null
+++ b/tests/test_i18n.py
@@ -0,0 +1,110 @@
+"""Tests that i18n keys referenced in code and templates exist in JSON files."""
+
+from __future__ import annotations
+
+import json
+import re
+from pathlib import Path
+
+import pytest
+
+from ollama_web import i18n, prompts
+
+
+_SRC = Path(__file__).resolve().parent.parent / "src" / "ollama_web"
+
+
+def _json_keys(data: dict, prefix: str = "") -> set[str]:
+ """Return all dot-separated keys in a nested dict."""
+ keys: set[str] = set()
+ for key, value in data.items():
+ full = f"{prefix}.{key}" if prefix else key
+ keys.add(full)
+ if isinstance(value, dict):
+ keys.update(_json_keys(value, full))
+ return keys
+
+
+def _extract_template_keys(path: Path) -> set[str]:
+ """Find t('...') calls in Jinja2 templates."""
+ text = path.read_text(encoding="utf-8")
+ return set(re.findall(r"{{\s*t\(['\"]([^'\"]+)['\"]", text))
+
+
+def _extract_js_keys(path: Path) -> set[str]:
+ """Find t("...") / t('...') calls in JavaScript files."""
+ text = path.read_text(encoding="utf-8")
+ return set(re.findall(r"\bt\(['\"]([^'\"]+)['\"]", text))
+
+
+def _extract_python_t_keys(path: Path) -> set[str]:
+ """Find t(...) calls in Python source."""
+ text = path.read_text(encoding="utf-8")
+ return set(re.findall(r"\bt\(['\"]([^'\"]+)['\"]", text))
+
+
+def _extract_python_prompt_keys(path: Path) -> set[str]:
+ """Find get_prompt(...) calls in Python source."""
+ text = path.read_text(encoding="utf-8")
+ return set(re.findall(r"\bget_prompt\(['\"]([^'\"]+)['\"]", text))
+
+
+def _referenced_message_keys() -> set[str]:
+ """Collect every message key referenced from templates, JS and Python."""
+ referenced: set[str] = set()
+ for path in _SRC.rglob("*.html"):
+ referenced.update(_extract_template_keys(path))
+ for path in _SRC.rglob("*.js"):
+ referenced.update(_extract_js_keys(path))
+ for path in _SRC.rglob("*.py"):
+ referenced.update(_extract_python_t_keys(path))
+ return referenced
+
+
+def _referenced_prompt_keys() -> set[str]:
+ """Collect every prompt key referenced from Python."""
+ referenced: set[str] = set()
+ for path in _SRC.rglob("*.py"):
+ referenced.update(_extract_python_prompt_keys(path))
+ return referenced
+
+
+@pytest.mark.parametrize("lang", ["ja", "en"])
+def test_all_message_keys_exist(lang: str):
+ """Every referenced message key must exist for each supported language."""
+ messages = i18n.get_messages(lang)
+ available = _json_keys(messages)
+ missing = _referenced_message_keys() - available
+ assert not missing, f"Missing keys in i18n/messages/{lang}.json: {sorted(missing)}"
+
+
+@pytest.mark.parametrize("lang", ["ja", "en"])
+def test_all_prompt_keys_exist(lang: str):
+ """Every referenced prompt key must exist for each supported language."""
+ prompt_data = prompts.get_prompts(lang)
+ available = _json_keys(prompt_data)
+ missing = _referenced_prompt_keys() - available
+ assert not missing, f"Missing keys in prompts/{lang}.json: {sorted(missing)}"
+
+
+@pytest.mark.parametrize("lang", ["ja", "en"])
+def test_json_files_are_valid(lang: str):
+ """Both localization JSON files must be valid and parseable."""
+ assert isinstance(i18n.get_messages(lang), dict)
+ assert isinstance(prompts.get_prompts(lang), dict)
+
+
+def test_t_returns_expected_strings():
+ """Basic sanity checks for the t() helper."""
+ assert i18n.t("common.send") == "送信"
+ assert i18n.t("common.send", lang="en") == "Send"
+ assert i18n.t("missing.key", fallback="fallback") == "fallback"
+ assert i18n.t("missing.key") == "missing.key"
+
+
+def test_get_prompt_returns_expected_strings():
+ """Basic sanity checks for the get_prompt() helper."""
+ assert prompts.get_prompt("tool_system") == prompts.get_prompts("ja")["tool_system"]
+ assert prompts.get_prompt("tool_system", lang="en") == prompts.get_prompts("en")["tool_system"]
+ assert prompts.get_prompt("missing.prompt", fallback="fallback") == "fallback"
+ assert prompts.get_prompt("missing.prompt") == "missing.prompt"
diff --git a/tests/test_i18n_rendering.py b/tests/test_i18n_rendering.py
new file mode 100644
index 0000000..95015d5
--- /dev/null
+++ b/tests/test_i18n_rendering.py
@@ -0,0 +1,72 @@
+"""Tests that page rendering respects the configured language consistently."""
+
+from __future__ import annotations
+
+import pytest
+from starlette.testclient import TestClient
+
+from ollama_web.app import create_app
+from ollama_web.config import settings
+from ollama_web.i18n import get_messages, t
+
+
+@pytest.fixture
+def reset_language():
+ """Reset language to the initial value after each test."""
+ original = settings.language
+ yield
+ settings.language = original
+ if hasattr(get_messages, "cache_clear"):
+ get_messages.cache_clear()
+
+
+def _render_index() -> str:
+ """Render index.html and return the HTML body."""
+ app = create_app()
+ with TestClient(app) as client:
+ response = client.post("/api/auth/login", json={"pin": settings.pin})
+ assert response.status_code == 200
+ response = client.get("/")
+ return response.text
+
+
+def test_english_language_consistency(reset_language):
+ """When settings.language='en', all UI text including MCP must be English."""
+ settings.language = "en"
+ html = _render_index()
+ # HTML-side labels
+ assert 'lang="en"' in html
+ assert "MCP Settings" in html
+ assert "MCP Server Settings" in html
+ assert "+ Add server" in html
+ # Japanese labels must not appear anywhere
+ assert "MCP設定" not in html
+ assert "MCPサーバー設定" not in html
+ assert "+ サーバー追加" not in html
+ assert "セッション" not in html
+ assert "モデル:" not in html
+
+
+def test_japanese_language_consistency(reset_language):
+ """When settings.language='ja', all UI text including MCP must be Japanese."""
+ settings.language = "ja"
+ html = _render_index()
+ # HTML-side labels
+ assert 'lang="ja"' in html
+ assert "MCP設定" in html
+ assert "MCPサーバー設定" in html
+ assert "+ サーバー追加" in html
+ # English labels must not appear anywhere
+ assert "MCP Settings" not in html
+ assert "MCP Server Settings" not in html
+ assert "+ Add server" not in html
+ assert "Sessions" not in html
+ assert "Model:" not in html
+
+
+def test_t_helper_respects_language_override():
+ """The t() helper must return the requested language string."""
+ assert t("common.send", lang="en") == "Send"
+ assert t("common.send", lang="ja") == "送信"
+ assert t("mcp.server_settings", lang="en") == "MCP Server Settings"
+ assert t("mcp.server_settings", lang="ja") == "MCPサーバー設定"