Files
weirsoe-party-protocol/lobby/i18n.py
Asger Geel Weirsoee c34a52e83e
All checks were successful
CI / test-and-quality (push) Successful in 3m35s
CI / test-and-quality (pull_request) Successful in 3m38s
Fix Accept-Language q parsing in locale resolver
2026-03-02 00:38:34 +00:00

94 lines
3.1 KiB
Python

import json
import logging
from functools import lru_cache
from pathlib import Path
from django.http import HttpRequest, JsonResponse
from django.utils.translation import get_language_from_request
LOGGER = logging.getLogger(__name__)
@lru_cache(maxsize=1)
def lobby_i18n_catalog() -> dict:
catalog_path = Path(__file__).resolve().parents[1] / "shared" / "i18n" / "lobby.json"
with catalog_path.open(encoding="utf-8") as handle:
return json.load(handle)
@lru_cache(maxsize=1)
def i18n_locale_config() -> tuple[str, tuple[str, ...]]:
locales = lobby_i18n_catalog().get("locales", {})
default_locale = str(locales.get("default", "en")).strip().lower() or "en"
supported_locales = tuple(
locale.strip().lower() for locale in locales.get("supported", ["en", "da"]) if str(locale).strip()
) or ("en", "da")
return default_locale, supported_locales
def lobby_i18n_errors() -> dict:
return lobby_i18n_catalog().get("backend", {}).get("error_codes", {})
def lobby_i18n_error_messages() -> dict:
return lobby_i18n_catalog().get("backend", {}).get("errors", {})
def _quality_value(language_candidate: str) -> float | None:
for parameter in language_candidate.split(";")[1:]:
key, separator, value = parameter.partition("=")
if separator and key.strip().lower() == "q":
try:
return float(value.strip())
except ValueError:
return None
return None
def resolve_locale(request: HttpRequest) -> str:
default_locale, supported_locales = i18n_locale_config()
accept_language = request.META.get("HTTP_ACCEPT_LANGUAGE") or ""
for candidate in accept_language.split(","):
quality = _quality_value(candidate)
if quality is not None and quality <= 0:
continue
tag = candidate.split(";", 1)[0]
normalized = tag.strip().replace("_", "-").split("-", 1)[0].lower()
if normalized in supported_locales:
return normalized
requested = (get_language_from_request(request) or "").replace("_", "-").split("-", 1)[0].lower()
if requested in supported_locales:
return requested
return default_locale
def resolve_error_message(*, key: str, locale: str) -> str:
default_locale, _supported_locales = i18n_locale_config()
translations = lobby_i18n_error_messages().get(key)
if not isinstance(translations, dict):
LOGGER.warning("i18n key missing in shared catalog", extra={"key": key, "locale": locale})
return key
if locale in translations and translations[locale]:
return translations[locale]
if default_locale in translations and translations[default_locale]:
return translations[default_locale]
LOGGER.warning("i18n translation missing for key", extra={"key": key, "locale": locale})
return key
def api_error(request: HttpRequest, *, key: str, status: int) -> JsonResponse:
locale = resolve_locale(request)
return JsonResponse(
{
"error": resolve_error_message(key=key, locale=locale),
"error_code": key,
"locale": locale,
},
status=status,
)