from __future__ import annotations import json import re from functools import lru_cache from pathlib import Path from typing import Any, Mapping I18N_ROOT = Path(__file__).resolve().parents[2] / "shared" / "i18n" DEFAULT_LOCALE = "en" SUPPORTED_LOCALES = ("en", "zh-CN") _PLACEHOLDER = re.compile(r"{{\s*([A-Za-z0-9_.-]+)\s*}}") def normalize_locale(value: str | None) -> str | None: if not value: return None normalized = value.strip().replace("_", "-").lower() if normalized == "*": return DEFAULT_LOCALE if normalized == "en" or normalized.startswith("en-"): return "en" if normalized in {"zh", "zh-cn", "zh-hans"} or normalized.startswith("zh-hans-"): return "zh-CN" return None def negotiate_locale(accept_language: str | None) -> str: candidates: list[tuple[float, int, str]] = [] for index, part in enumerate((accept_language or "").split(",")): token, *parameters = part.strip().split(";") quality = 1.0 for parameter in parameters: name, separator, value = parameter.strip().partition("=") if separator and name.lower() == "q": try: quality = float(value) except ValueError: quality = 0.0 locale = normalize_locale(token) if locale and quality > 0: candidates.append((quality, -index, locale)) return max(candidates, default=(1.0, 0, DEFAULT_LOCALE))[2] @lru_cache(maxsize=None) def _catalog(locale: str, namespace: str) -> Mapping[str, Any]: path = I18N_ROOT / "locales" / locale / f"{namespace}.json" with path.open(encoding="utf-8") as handle: return json.load(handle) def _lookup(catalog: Mapping[str, Any], path: str) -> str | None: current: Any = catalog for segment in path.split("."): if not isinstance(current, Mapping) or segment not in current: return None current = current[segment] return current if isinstance(current, str) else None def translate( key: str, locale: str, params: Mapping[str, Any] | None = None, default: str | None = None, ) -> str: namespace, separator, path = key.partition(":") if not separator: namespace, path = "common", namespace template = _lookup(_catalog(locale, namespace), path) if template is None and locale != DEFAULT_LOCALE: template = _lookup(_catalog(DEFAULT_LOCALE, namespace), path) if template is None: template = default or key values = params or {} return _PLACEHOLDER.sub(lambda match: str(values.get(match.group(1), match.group(0))), template) def localized_message( key: str, locale: str, params: Mapping[str, Any] | None = None, default: str | None = None, ) -> dict[str, Any]: message: dict[str, Any] = { "key": key, "text": translate(key, locale, params=params, default=default), } if params: message["params"] = dict(params) return message def localize_error(payload: Mapping[str, Any], locale: str) -> dict[str, Any]: code = str(payload.get("error") or "unknown") key = f"api:errors.{code}" if _lookup(_catalog(locale, "api"), f"errors.{code}") is None: key = "api:errors.unknown" details = { name: value for name, value in payload.items() if name not in {"error", "message", "message_params"} } error: dict[str, Any] = { "code": code, "message": localized_message( key, locale, params=payload.get("message_params") if isinstance(payload.get("message_params"), Mapping) else None, ), } if details: error["details"] = details return {"error": error}