"""Language selection. French is the primary language of the site; English remains available. Source strings stay in English and act as gettext message ids, with the French wording supplied by translations/fr/LC_MESSAGES/messages.po. That keeps the codebase in one language — the same one as its comments and docstrings — while what a member actually sees defaults to French. Consequence worth knowing: an English page is what you get when a string has no French translation yet. A missing entry degrades to English rather than to a raw identifier, which is why the migration can proceed template by template without ever leaving the site in a broken state. """ from flask import request, session #: Locales the site is served in, in order of preference. SUPPORTED_LOCALES = ('fr', 'en') #: Language names as written in their own language, for the switcher. LOCALE_NAMES = { 'fr': 'Français', 'en': 'English', } #: Session key holding an explicit user choice. LOCALE_SESSION_KEY = 'locale' DEFAULT_LOCALE = 'fr' def select_locale(): """Pick the locale for the current request. Order of precedence: 1. an explicit choice the user made through the language switcher, kept in the session; 2. the browser's Accept-Language header, restricted to what we serve; 3. French. Note that step 2 only ever selects English for someone whose browser actually asks for it. Everyone else gets French, including browsers sending no header at all. Returns: str: A locale code from SUPPORTED_LOCALES. """ chosen = session.get(LOCALE_SESSION_KEY) if chosen in SUPPORTED_LOCALES: return chosen # best_match returns None when nothing overlaps. if request: negotiated = request.accept_languages.best_match(SUPPORTED_LOCALES) if negotiated: return negotiated return DEFAULT_LOCALE def set_locale(locale): """Record an explicit language choice for this session. Args: locale: Requested locale code. Returns: bool: True if it was accepted, False if unsupported. """ if locale not in SUPPORTED_LOCALES: return False session[LOCALE_SESSION_KEY] = locale return True