"""Language policy shared by API locale negotiation and background jobs.""" from __future__ import annotations from typing import Iterable SUPPORTED_LOCALES = ("th", "en") DEFAULT_LOCALE = "th" # Legacy values are intentionally mapped to the default locale so an old # browser preference cannot re-enable an unsupported product language. _LEGACY_LOCALE_ALIASES = { "zh": DEFAULT_LOCALE, "zh-cn": DEFAULT_LOCALE, "zh-tw": DEFAULT_LOCALE, } def normalize_locale(value: object, default: str = DEFAULT_LOCALE) -> str: """Return one supported base locale, failing closed to ``default``.""" safe_default = default if default in SUPPORTED_LOCALES else DEFAULT_LOCALE if not isinstance(value, str): return safe_default normalized = value.strip().replace("_", "-").lower() if not normalized: return safe_default if normalized in _LEGACY_LOCALE_ALIASES: return _LEGACY_LOCALE_ALIASES[normalized] if normalized in SUPPORTED_LOCALES: return normalized base_locale = normalized.split("-", 1)[0] if base_locale in SUPPORTED_LOCALES: return base_locale if base_locale in _LEGACY_LOCALE_ALIASES: return _LEGACY_LOCALE_ALIASES[base_locale] return safe_default def locale_from_accept_language(header: object) -> str: """Choose the best supported locale from an HTTP Accept-Language value.""" if not isinstance(header, str) or not header.strip(): return DEFAULT_LOCALE candidates: list[tuple[float, int, str]] = [] for position, raw_item in enumerate(header.split(",")): parts = [part.strip() for part in raw_item.split(";")] language = parts[0] quality = 1.0 for parameter in parts[1:]: key, separator, value = parameter.partition("=") if key.strip().lower() != "q" or not separator: continue try: quality = float(value.strip()) except ValueError: quality = 0.0 break if quality <= 0: continue normalized_language = language.strip().replace("_", "-").lower() base_locale = normalized_language.split("-", 1)[0] if base_locale in SUPPORTED_LOCALES: # Higher q wins; original order breaks ties. candidates.append((quality, -position, base_locale)) if not candidates: return DEFAULT_LOCALE candidates.sort(reverse=True) return candidates[0][2] def is_supported_locale(value: object) -> bool: """Return whether ``value`` is already a canonical supported locale.""" return isinstance(value, str) and value in SUPPORTED_LOCALES def supported_locales() -> Iterable[str]: """Expose supported locales without allowing callers to mutate the tuple.""" return SUPPORTED_LOCALES