mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-28 23:57:58 +09:00
Comments, docstrings and a few strings cited plan numbers ("plan 29
#9", "plan 86 §B2") from a git-ignored folder, so they pointed at
nothing; one reached users in a bottleneck hint. Code and frontend
strings are unchanged apart from those citations (checked by AST and
token comparison). A vhost still carrying the old asset comment reads
as undrifted, and a guard test keeps new citations out.
60 lines
2.2 KiB
Python
60 lines
2.2 KiB
Python
"""Supported UI languages.
|
|
|
|
The **canonical** list is the frontend manifest,
|
|
``frontend/src/i18n/languages.json`` — that is what the browser actually loads
|
|
bundles for, and shipping a language means putting a JSON file next to it.
|
|
|
|
This module exists because the API has to validate what it stores: a user row
|
|
carrying a code the panel cannot render is a preference that silently does
|
|
nothing. Rather than have the backend fetch or parse the frontend at runtime,
|
|
the list is restated here and ``tests/test_user_language_preference.py`` fails
|
|
the build if the two ever disagree. Two lists with a parity test beats one list
|
|
the backend cannot see, and beats validating nothing at all.
|
|
"""
|
|
|
|
import re
|
|
|
|
# Keep in sync with frontend/src/i18n/languages.json (enforced by test).
|
|
SUPPORTED_LANGUAGES = (
|
|
'id', 'de', 'en', 'es', 'fr', 'pt', 'vi', 'tr', 'ru', 'ko', 'zh-Hans',
|
|
'zh-Hant', 'th', 'ar', 'it', 'pl', 'bn',
|
|
)
|
|
|
|
DEFAULT_LANGUAGE = 'en'
|
|
|
|
# Shape gate applied before the allowlist so a malformed value is rejected for
|
|
# being malformed, not for being unknown.
|
|
_LANGUAGE_TAG = re.compile(r'^[a-z]{2,3}(-[a-z0-9]{2,8})*$')
|
|
|
|
|
|
def normalize_language(value):
|
|
"""Return a supported language code for ``value``, or ``None``.
|
|
|
|
Most languages match language-only, so ``es-MX`` and ``ES`` both resolve
|
|
to ``es``. Chinese is script-aware: regions and explicit script subtags
|
|
resolve to ``zh-Hans`` or ``zh-Hant``.
|
|
"""
|
|
if not isinstance(value, str):
|
|
return None
|
|
# Case-fold first: browsers send `es-MX`, `ES`, and `es-mx` for the same
|
|
# thing, and a case-sensitive shape gate would reject two of the three.
|
|
tag = value.strip().replace('_', '-').lower()
|
|
if not tag or len(tag) > 10 or not _LANGUAGE_TAG.match(tag):
|
|
return None
|
|
canonical = {code.lower(): code for code in SUPPORTED_LANGUAGES}
|
|
if tag in canonical:
|
|
return canonical[tag]
|
|
|
|
parts = tag.split('-')
|
|
base = parts[0]
|
|
if base == 'zh':
|
|
traditional = (
|
|
'hant' in parts
|
|
or any(part in ('tw', 'hk', 'mo') for part in parts)
|
|
)
|
|
script = 'zh-hant' if traditional else 'zh-hans'
|
|
if script in canonical:
|
|
return canonical[script]
|
|
|
|
return canonical.get(base)
|