|  | @@ -1,12 +1,36 @@
 | 
											
												
													
														|  |  # -*- coding: utf-8 -*-
 |  |  # -*- coding: utf-8 -*-
 | 
											
												
													
														|  |  # SPDX-License-Identifier: AGPL-3.0-or-later
 |  |  # SPDX-License-Identifier: AGPL-3.0-or-later
 | 
											
												
													
														|  |  # lint: pylint
 |  |  # lint: pylint
 | 
											
												
													
														|  | -"""Initialize :py:obj:`LOCALE_NAMES`, :py:obj:`RTL_LOCALES`.
 |  | 
 | 
											
												
													
														|  |  """
 |  |  """
 | 
											
												
													
														|  | 
 |  | +SearXNG’s locale data
 | 
											
												
													
														|  | 
 |  | +=====================
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -from typing import Set, Optional, List
 |  | 
 | 
											
												
													
														|  | -import os
 |  | 
 | 
											
												
													
														|  | -import pathlib
 |  | 
 | 
											
												
													
														|  | 
 |  | +The variables :py:obj:`RTL_LOCALES` and :py:obj:`LOCALE_NAMES` are loaded from
 | 
											
												
													
														|  | 
 |  | +:origin:`searx/data/locales.json` / see :py:obj:`locales_initialize` and
 | 
											
												
													
														|  | 
 |  | +:ref:`update_locales.py`.
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +.. hint::
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +   Whenever the value of :py:obj:`ADDITIONAL_TRANSLATIONS` or
 | 
											
												
													
														|  | 
 |  | +   :py:obj:`LOCALE_BEST_MATCH` is modified, the
 | 
											
												
													
														|  | 
 |  | +   :origin:`searx/data/locales.json` needs to be rebuild::
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +     ./manage data.locales
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +SearXNG's locale codes
 | 
											
												
													
														|  | 
 |  | +======================
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +.. automodule:: searx.sxng_locales
 | 
											
												
													
														|  | 
 |  | +   :members:
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +SearXNG’s locale implementations
 | 
											
												
													
														|  | 
 |  | +================================
 | 
											
												
													
														|  | 
 |  | +"""
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +from __future__ import annotations
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +from pathlib import Path
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  import babel
 |  |  import babel
 | 
											
												
													
														|  |  from babel.support import Translations
 |  |  from babel.support import Translations
 | 
											
										
											
												
													
														|  | @@ -15,7 +39,11 @@ import babel.core
 | 
											
												
													
														|  |  import flask_babel
 |  |  import flask_babel
 | 
											
												
													
														|  |  import flask
 |  |  import flask
 | 
											
												
													
														|  |  from flask.ctx import has_request_context
 |  |  from flask.ctx import has_request_context
 | 
											
												
													
														|  | -from searx import logger
 |  | 
 | 
											
												
													
														|  | 
 |  | +from searx import (
 | 
											
												
													
														|  | 
 |  | +    data,
 | 
											
												
													
														|  | 
 |  | +    logger,
 | 
											
												
													
														|  | 
 |  | +    searx_dir,
 | 
											
												
													
														|  | 
 |  | +)
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  logger = logger.getChild('locales')
 |  |  logger = logger.getChild('locales')
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -30,7 +58,7 @@ LOCALE_NAMES = {}
 | 
											
												
													
														|  |  :meta hide-value:
 |  |  :meta hide-value:
 | 
											
												
													
														|  |  """
 |  |  """
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -RTL_LOCALES: Set[str] = set()
 |  | 
 | 
											
												
													
														|  | 
 |  | +RTL_LOCALES: set[str] = set()
 | 
											
												
													
														|  |  """List of *Right-To-Left* locales e.g. 'he' or 'fa-IR' (see
 |  |  """List of *Right-To-Left* locales e.g. 'he' or 'fa-IR' (see
 | 
											
												
													
														|  |  :py:obj:`locales_initialize`)."""
 |  |  :py:obj:`locales_initialize`)."""
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -52,7 +80,7 @@ LOCALE_BEST_MATCH = {
 | 
											
												
													
														|  |      "pap": "pt-BR",
 |  |      "pap": "pt-BR",
 | 
											
												
													
														|  |  }
 |  |  }
 | 
											
												
													
														|  |  """Map a locale we do not have a translations for to a locale we have a
 |  |  """Map a locale we do not have a translations for to a locale we have a
 | 
											
												
													
														|  | -translation for. By example: use Taiwan version of the translation for Hong
 |  | 
 | 
											
												
													
														|  | 
 |  | +translation for.  By example: use Taiwan version of the translation for Hong
 | 
											
												
													
														|  |  Kong."""
 |  |  Kong."""
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -90,74 +118,37 @@ def get_translations():
 | 
											
												
													
														|  |      return _flask_babel_get_translations()
 |  |      return _flask_babel_get_translations()
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -def get_locale_descr(locale, locale_name):
 |  | 
 | 
											
												
													
														|  | -    """Get locale name e.g. 'Français - fr' or 'Português (Brasil) - pt-BR'
 |  | 
 | 
											
												
													
														|  | 
 |  | +_TR_LOCALES: list[str] = []
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    :param locale: instance of :py:class:`Locale`
 |  | 
 | 
											
												
													
														|  | -    :param locale_name: name e.g. 'fr'  or 'pt_BR' (delimiter is *underscore*)
 |  | 
 | 
											
												
													
														|  | -    """
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    native_language, native_territory = _get_locale_descr(locale, locale_name)
 |  | 
 | 
											
												
													
														|  | -    english_language, english_territory = _get_locale_descr(locale, 'en')
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    if native_territory == english_territory:
 |  | 
 | 
											
												
													
														|  | -        english_territory = None
 |  | 
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    if not native_territory and not english_territory:
 |  | 
 | 
											
												
													
														|  | -        if native_language == english_language:
 |  | 
 | 
											
												
													
														|  | -            return native_language
 |  | 
 | 
											
												
													
														|  | -        return native_language + ' (' + english_language + ')'
 |  | 
 | 
											
												
													
														|  | 
 |  | +def get_translation_locales() -> list[str]:
 | 
											
												
													
														|  | 
 |  | +    """Returns the list of transaltion locales (*underscore*).  The list is
 | 
											
												
													
														|  | 
 |  | +    generated from the translation folders in :origin:`searx/translations`"""
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -    result = native_language + ', ' + native_territory + ' (' + english_language
 |  | 
 | 
											
												
													
														|  | -    if english_territory:
 |  | 
 | 
											
												
													
														|  | -        return result + ', ' + english_territory + ')'
 |  | 
 | 
											
												
													
														|  | -    return result + ')'
 |  | 
 | 
											
												
													
														|  | 
 |  | +    global _TR_LOCALES  # pylint:disable=global-statement
 | 
											
												
													
														|  | 
 |  | +    if _TR_LOCALES:
 | 
											
												
													
														|  | 
 |  | +        return _TR_LOCALES
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -def _get_locale_descr(locale, language_code):
 |  | 
 | 
											
												
													
														|  | -    language_name = locale.get_language_name(language_code).capitalize()
 |  | 
 | 
											
												
													
														|  | -    if language_name and ('a' <= language_name[0] <= 'z'):
 |  | 
 | 
											
												
													
														|  | -        language_name = language_name.capitalize()
 |  | 
 | 
											
												
													
														|  | -    territory_name = locale.get_territory_name(language_code)
 |  | 
 | 
											
												
													
														|  | -    return language_name, territory_name
 |  | 
 | 
											
												
													
														|  | 
 |  | +    tr_locales = []
 | 
											
												
													
														|  | 
 |  | +    for folder in (Path(searx_dir) / 'translations').iterdir():
 | 
											
												
													
														|  | 
 |  | +        if not folder.is_dir():
 | 
											
												
													
														|  | 
 |  | +            continue
 | 
											
												
													
														|  | 
 |  | +        if not (folder / 'LC_MESSAGES').is_dir():
 | 
											
												
													
														|  | 
 |  | +            continue
 | 
											
												
													
														|  | 
 |  | +        tr_locales.append(folder.name)
 | 
											
												
													
														|  | 
 |  | +    _TR_LOCALES = sorted(tr_locales)
 | 
											
												
													
														|  | 
 |  | +    return _TR_LOCALES
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -def locales_initialize(directory=None):
 |  | 
 | 
											
												
													
														|  | 
 |  | +def locales_initialize():
 | 
											
												
													
														|  |      """Initialize locales environment of the SearXNG session.
 |  |      """Initialize locales environment of the SearXNG session.
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      - monkey patch :py:obj:`flask_babel.get_translations` by :py:obj:`get_translations`
 |  |      - monkey patch :py:obj:`flask_babel.get_translations` by :py:obj:`get_translations`
 | 
											
												
													
														|  |      - init global names :py:obj:`LOCALE_NAMES`, :py:obj:`RTL_LOCALES`
 |  |      - init global names :py:obj:`LOCALE_NAMES`, :py:obj:`RTL_LOCALES`
 | 
											
												
													
														|  |      """
 |  |      """
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    directory = directory or pathlib.Path(__file__).parent / 'translations'
 |  | 
 | 
											
												
													
														|  | -    logger.debug("locales_initialize: %s", directory)
 |  | 
 | 
											
												
													
														|  |      flask_babel.get_translations = get_translations
 |  |      flask_babel.get_translations = get_translations
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    for tag, descr in ADDITIONAL_TRANSLATIONS.items():
 |  | 
 | 
											
												
													
														|  | -        locale = babel.Locale.parse(LOCALE_BEST_MATCH[tag], sep='-')
 |  | 
 | 
											
												
													
														|  | -        LOCALE_NAMES[tag] = descr
 |  | 
 | 
											
												
													
														|  | -        if locale.text_direction == 'rtl':
 |  | 
 | 
											
												
													
														|  | -            RTL_LOCALES.add(tag)
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    for tag in LOCALE_BEST_MATCH:
 |  | 
 | 
											
												
													
														|  | -        descr = LOCALE_NAMES.get(tag)
 |  | 
 | 
											
												
													
														|  | -        if not descr:
 |  | 
 | 
											
												
													
														|  | -            locale = babel.Locale.parse(tag, sep='-')
 |  | 
 | 
											
												
													
														|  | -            LOCALE_NAMES[tag] = get_locale_descr(locale, tag.replace('-', '_'))
 |  | 
 | 
											
												
													
														|  | -            if locale.text_direction == 'rtl':
 |  | 
 | 
											
												
													
														|  | -                RTL_LOCALES.add(tag)
 |  | 
 | 
											
												
													
														|  | -
 |  | 
 | 
											
												
													
														|  | -    for dirname in sorted(os.listdir(directory)):
 |  | 
 | 
											
												
													
														|  | -        # Based on https://flask-babel.tkte.ch/_modules/flask_babel.html#Babel.list_translations
 |  | 
 | 
											
												
													
														|  | -        if not os.path.isdir(os.path.join(directory, dirname, 'LC_MESSAGES')):
 |  | 
 | 
											
												
													
														|  | -            continue
 |  | 
 | 
											
												
													
														|  | -        tag = dirname.replace('_', '-')
 |  | 
 | 
											
												
													
														|  | -        descr = LOCALE_NAMES.get(tag)
 |  | 
 | 
											
												
													
														|  | -        if not descr:
 |  | 
 | 
											
												
													
														|  | -            locale = babel.Locale.parse(dirname)
 |  | 
 | 
											
												
													
														|  | -            LOCALE_NAMES[tag] = get_locale_descr(locale, dirname)
 |  | 
 | 
											
												
													
														|  | -            if locale.text_direction == 'rtl':
 |  | 
 | 
											
												
													
														|  | -                RTL_LOCALES.add(tag)
 |  | 
 | 
											
												
													
														|  | 
 |  | +    LOCALE_NAMES.update(data.LOCALES["LOCALE_NAMES"])
 | 
											
												
													
														|  | 
 |  | +    RTL_LOCALES.update(data.LOCALES["RTL_LOCALES"])
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  def region_tag(locale: babel.Locale) -> str:
 |  |  def region_tag(locale: babel.Locale) -> str:
 | 
											
										
											
												
													
														|  | @@ -177,7 +168,7 @@ def language_tag(locale: babel.Locale) -> str:
 | 
											
												
													
														|  |      return sxng_lang
 |  |      return sxng_lang
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -def get_locale(locale_tag: str) -> Optional[babel.Locale]:
 |  | 
 | 
											
												
													
														|  | 
 |  | +def get_locale(locale_tag: str) -> babel.Locale | None:
 | 
											
												
													
														|  |      """Returns a :py:obj:`babel.Locale` object parsed from argument
 |  |      """Returns a :py:obj:`babel.Locale` object parsed from argument
 | 
											
												
													
														|  |      ``locale_tag``"""
 |  |      ``locale_tag``"""
 | 
											
												
													
														|  |      try:
 |  |      try:
 | 
											
										
											
												
													
														|  | @@ -190,7 +181,7 @@ def get_locale(locale_tag: str) -> Optional[babel.Locale]:
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  def get_official_locales(
 |  |  def get_official_locales(
 | 
											
												
													
														|  |      territory: str, languages=None, regional: bool = False, de_facto: bool = True
 |  |      territory: str, languages=None, regional: bool = False, de_facto: bool = True
 | 
											
												
													
														|  | -) -> Set[babel.Locale]:
 |  | 
 | 
											
												
													
														|  | 
 |  | +) -> set[babel.Locale]:
 | 
											
												
													
														|  |      """Returns a list of :py:obj:`babel.Locale` with languages from
 |  |      """Returns a list of :py:obj:`babel.Locale` with languages from
 | 
											
												
													
														|  |      :py:obj:`babel.languages.get_official_languages`.
 |  |      :py:obj:`babel.languages.get_official_languages`.
 | 
											
												
													
														|  |  
 |  |  
 | 
											
										
											
												
													
														|  | @@ -376,7 +367,7 @@ def get_engine_locale(searxng_locale, engine_locales, default=None):
 | 
											
												
													
														|  |      return default
 |  |      return default
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -def match_locale(searxng_locale: str, locale_tag_list: List[str], fallback: Optional[str] = None) -> Optional[str]:
 |  | 
 | 
											
												
													
														|  | 
 |  | +def match_locale(searxng_locale: str, locale_tag_list: list[str], fallback: str | None = None) -> str | None:
 | 
											
												
													
														|  |      """Return tag from ``locale_tag_list`` that best fits to ``searxng_locale``.
 |  |      """Return tag from ``locale_tag_list`` that best fits to ``searxng_locale``.
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |      :param str searxng_locale: SearXNG's internal representation of locale (de,
 |  |      :param str searxng_locale: SearXNG's internal representation of locale (de,
 | 
											
										
											
												
													
														|  | @@ -425,7 +416,7 @@ def match_locale(searxng_locale: str, locale_tag_list: List[str], fallback: Opti
 | 
											
												
													
														|  |      return get_engine_locale(searxng_locale, engine_locales, default=fallback)
 |  |      return get_engine_locale(searxng_locale, engine_locales, default=fallback)
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  |  
 |  |  
 | 
											
												
													
														|  | -def build_engine_locales(tag_list: List[str]):
 |  | 
 | 
											
												
													
														|  | 
 |  | +def build_engine_locales(tag_list: list[str]):
 | 
											
												
													
														|  |      """From a list of locale tags a dictionary is build that can be passed by
 |  |      """From a list of locale tags a dictionary is build that can be passed by
 | 
											
												
													
														|  |      argument ``engine_locales`` to :py:obj:`get_engine_locale`.  This function
 |  |      argument ``engine_locales`` to :py:obj:`get_engine_locale`.  This function
 | 
											
												
													
														|  |      is mainly used by :py:obj:`match_locale` and is similar to what the
 |  |      is mainly used by :py:obj:`match_locale` and is similar to what the
 |