.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
searx.locales Namespace Reference

Functions

 localeselector ()
 
 get_translations ()
 
list[str] get_translation_locales ()
 
 locales_initialize ()
 
str region_tag (babel.Locale locale)
 
str language_tag (babel.Locale locale)
 
babel.Locale|None get_locale (str locale_tag)
 
set[babel.Locale] get_official_locales (str territory, languages=None, bool regional=False, bool de_facto=True)
 
 get_engine_locale (searxng_locale, engine_locales, default=None)
 
str|None match_locale (str searxng_locale, list[str] locale_tag_list, str|None fallback=None)
 
 build_engine_locales (list[str] tag_list)
 

Variables

 logger = logger.getChild('locales')
 
 _flask_babel_get_translations = flask_babel.get_translations
 
dict LOCALE_NAMES = {}
 
set RTL_LOCALES = set()
 
dict ADDITIONAL_TRANSLATIONS
 
dict LOCALE_BEST_MATCH
 
list _TR_LOCALES = []
 

Detailed Description

SearXNG’s locale data
=====================

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
================================

Function Documentation

◆ build_engine_locales()

searx.locales.build_engine_locales ( list[str] tag_list)
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
is mainly used by :py:obj:`match_locale` and is similar to what the
``fetch_traits(..)`` function of engines do.

If there are territory codes in the ``tag_list`` that have a *script code*
additional keys are added to the returned dictionary.

.. code:: python

   >>> import locales
   >>> engine_locales = locales.build_engine_locales(['en', 'en-US', 'zh', 'zh-CN', 'zh-TW'])
   >>> engine_locales
   {
       'en': 'en', 'en-US': 'en-US',
       'zh': 'zh', 'zh-CN': 'zh-CN', 'zh_Hans': 'zh-CN',
       'zh-TW': 'zh-TW', 'zh_Hant': 'zh-TW'
   }
   >>> get_engine_locale('zh-Hans', engine_locales)
   'zh-CN'

This function is a good example to understand the language/region model
of SearXNG:

  SearXNG only distinguishes between **search languages** and **search
  regions**, by adding the *script-tags*, languages with *script-tags* can
  be assigned to the **regions** that SearXNG supports.

Definition at line 418 of file locales.py.

418def build_engine_locales(tag_list: list[str]):
419 """From a list of locale tags a dictionary is build that can be passed by
420 argument ``engine_locales`` to :py:obj:`get_engine_locale`. This function
421 is mainly used by :py:obj:`match_locale` and is similar to what the
422 ``fetch_traits(..)`` function of engines do.
423
424 If there are territory codes in the ``tag_list`` that have a *script code*
425 additional keys are added to the returned dictionary.
426
427 .. code:: python
428
429 >>> import locales
430 >>> engine_locales = locales.build_engine_locales(['en', 'en-US', 'zh', 'zh-CN', 'zh-TW'])
431 >>> engine_locales
432 {
433 'en': 'en', 'en-US': 'en-US',
434 'zh': 'zh', 'zh-CN': 'zh-CN', 'zh_Hans': 'zh-CN',
435 'zh-TW': 'zh-TW', 'zh_Hant': 'zh-TW'
436 }
437 >>> get_engine_locale('zh-Hans', engine_locales)
438 'zh-CN'
439
440 This function is a good example to understand the language/region model
441 of SearXNG:
442
443 SearXNG only distinguishes between **search languages** and **search
444 regions**, by adding the *script-tags*, languages with *script-tags* can
445 be assigned to the **regions** that SearXNG supports.
446
447 """
448 engine_locales = {}
449
450 for tag in tag_list:
451 locale = get_locale(tag)
452 if locale is None:
453 logger.warning("build_engine_locales: skip locale tag %s / unknown by babel", tag)
454 continue
455 if locale.territory:
456 engine_locales[region_tag(locale)] = tag
457 if locale.script:
458 engine_locales[language_tag(locale)] = tag
459 else:
460 engine_locales[language_tag(locale)] = tag
461 return engine_locales

References get_locale(), language_tag(), and region_tag().

Referenced by match_locale().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_engine_locale()

searx.locales.get_engine_locale ( searxng_locale,
engine_locales,
default = None )
Return engine's language (aka locale) string that best fits to argument
``searxng_locale``.

Argument ``engine_locales`` is a python dict that maps *SearXNG locales* to
corresponding *engine locales*::

  <engine>: {
      # SearXNG string : engine-string
      'ca-ES'          : 'ca_ES',
      'fr-BE'          : 'fr_BE',
      'fr-CA'          : 'fr_CA',
      'fr-CH'          : 'fr_CH',
      'fr'             : 'fr_FR',
      ...
      'pl-PL'          : 'pl_PL',
      'pt-PT'          : 'pt_PT'
      ..
      'zh'             : 'zh'
      'zh_Hans'        : 'zh'
      'zh_Hant'        : 'zh_TW'
  }

.. hint::

   The *SearXNG locale* string has to be known by babel!

If there is no direct 1:1 mapping, this functions tries to narrow down
engine's language (locale).  If no value can be determined by these
approximation attempts the ``default`` value is returned.

Assumptions:

A. When user select a language the results should be optimized according to
   the selected language.

B. When user select a language and a territory the results should be
   optimized with first priority on territory and second on language.

First approximation rule (*by territory*):

  When the user selects a locale with territory (and a language), the
  territory has priority over the language.  If any of the official languages
  in the territory is supported by the engine (``engine_locales``) it will
  be used.

Second approximation rule (*by language*):

  If "First approximation rule" brings no result or the user selects only a
  language without a territory.  Check in which territories the language
  has an official status and if one of these territories is supported by the
  engine.

Definition at line 218 of file locales.py.

218def get_engine_locale(searxng_locale, engine_locales, default=None):
219 """Return engine's language (aka locale) string that best fits to argument
220 ``searxng_locale``.
221
222 Argument ``engine_locales`` is a python dict that maps *SearXNG locales* to
223 corresponding *engine locales*::
224
225 <engine>: {
226 # SearXNG string : engine-string
227 'ca-ES' : 'ca_ES',
228 'fr-BE' : 'fr_BE',
229 'fr-CA' : 'fr_CA',
230 'fr-CH' : 'fr_CH',
231 'fr' : 'fr_FR',
232 ...
233 'pl-PL' : 'pl_PL',
234 'pt-PT' : 'pt_PT'
235 ..
236 'zh' : 'zh'
237 'zh_Hans' : 'zh'
238 'zh_Hant' : 'zh_TW'
239 }
240
241 .. hint::
242
243 The *SearXNG locale* string has to be known by babel!
244
245 If there is no direct 1:1 mapping, this functions tries to narrow down
246 engine's language (locale). If no value can be determined by these
247 approximation attempts the ``default`` value is returned.
248
249 Assumptions:
250
251 A. When user select a language the results should be optimized according to
252 the selected language.
253
254 B. When user select a language and a territory the results should be
255 optimized with first priority on territory and second on language.
256
257 First approximation rule (*by territory*):
258
259 When the user selects a locale with territory (and a language), the
260 territory has priority over the language. If any of the official languages
261 in the territory is supported by the engine (``engine_locales``) it will
262 be used.
263
264 Second approximation rule (*by language*):
265
266 If "First approximation rule" brings no result or the user selects only a
267 language without a territory. Check in which territories the language
268 has an official status and if one of these territories is supported by the
269 engine.
270
271 """
272 # pylint: disable=too-many-branches, too-many-return-statements
273
274 engine_locale = engine_locales.get(searxng_locale)
275
276 if engine_locale is not None:
277 # There was a 1:1 mapping (e.g. a region "fr-BE --> fr_BE" or a language
278 # "zh --> zh"), no need to narrow language-script nor territory.
279 return engine_locale
280
281 try:
282 locale = babel.Locale.parse(searxng_locale, sep='-')
283 except babel.core.UnknownLocaleError:
284 try:
285 locale = babel.Locale.parse(searxng_locale.split('-')[0])
286 except babel.core.UnknownLocaleError:
287 return default
288
289 searxng_lang = language_tag(locale)
290 engine_locale = engine_locales.get(searxng_lang)
291 if engine_locale is not None:
292 # There was a 1:1 mapping (e.g. "zh-HK --> zh_Hant" or "zh-CN --> zh_Hans")
293 return engine_locale
294
295 # SearXNG's selected locale is not supported by the engine ..
296
297 if locale.territory:
298 # Try to narrow by *official* languages in the territory (??-XX).
299
300 for official_language in babel.languages.get_official_languages(locale.territory, de_facto=True):
301 searxng_locale = official_language + '-' + locale.territory
302 engine_locale = engine_locales.get(searxng_locale)
303 if engine_locale is not None:
304 return engine_locale
305
306 # Engine does not support one of the official languages in the territory or
307 # there is only a language selected without a territory.
308
309 # Now lets have a look if the searxng_lang (the language selected by the
310 # user) is a official language in other territories. If so, check if
311 # engine does support the searxng_lang in this other territory.
312
313 if locale.language:
314
315 terr_lang_dict = {}
316 for territory, langs in babel.core.get_global("territory_languages").items():
317 if not langs.get(searxng_lang, {}).get('official_status'):
318 continue
319 terr_lang_dict[territory] = langs.get(searxng_lang)
320
321 # first: check fr-FR, de-DE .. is supported by the engine
322 # exception: 'en' --> 'en-US'
323
324 territory = locale.language.upper()
325 if territory == 'EN':
326 territory = 'US'
327
328 if terr_lang_dict.get(territory):
329 searxng_locale = locale.language + '-' + territory
330 engine_locale = engine_locales.get(searxng_locale)
331 if engine_locale is not None:
332 return engine_locale
333
334 # second: sort by population_percent and take first match
335
336 # drawback of "population percent": if there is a territory with a
337 # small number of people (e.g 100) but the majority speaks the
338 # language, then the percentage might be 100% (--> 100 people) but in
339 # a different territory with more people (e.g. 10.000) where only 10%
340 # speak the language the total amount of speaker is higher (--> 200
341 # people).
342 #
343 # By example: The population of Saint-Martin is 33.000, of which 100%
344 # speak French, but this is less than the 30% of the approximately 2.5
345 # million Belgian citizens
346 #
347 # - 'fr-MF', 'population_percent': 100.0, 'official_status': 'official'
348 # - 'fr-BE', 'population_percent': 38.0, 'official_status': 'official'
349
350 terr_lang_list = []
351 for k, v in terr_lang_dict.items():
352 terr_lang_list.append((k, v))
353
354 for territory, _lang in sorted(terr_lang_list, key=lambda item: item[1]['population_percent'], reverse=True):
355 searxng_locale = locale.language + '-' + territory
356 engine_locale = engine_locales.get(searxng_locale)
357 if engine_locale is not None:
358 return engine_locale
359
360 # No luck: narrow by "language from territory" and "territory from language"
361 # does not fit to a locale supported by the engine.
362
363 if engine_locale is None:
364 engine_locale = default
365
366 return default
367
368

References language_tag().

Referenced by match_locale().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_locale()

babel.Locale | None searx.locales.get_locale ( str locale_tag)
Returns a :py:obj:`babel.Locale` object parsed from argument
``locale_tag``

Definition at line 170 of file locales.py.

170def get_locale(locale_tag: str) -> babel.Locale | None:
171 """Returns a :py:obj:`babel.Locale` object parsed from argument
172 ``locale_tag``"""
173 try:
174 locale = babel.Locale.parse(locale_tag, sep='-')
175 return locale
176
177 except babel.core.UnknownLocaleError:
178 return None
179
180

Referenced by build_engine_locales(), and match_locale().

+ Here is the caller graph for this function:

◆ get_official_locales()

set[babel.Locale] searx.locales.get_official_locales ( str territory,
languages = None,
bool regional = False,
bool de_facto = True )
Returns a list of :py:obj:`babel.Locale` with languages from
:py:obj:`babel.languages.get_official_languages`.

:param territory: The territory (country or region) code.

:param languages: A list of language codes the languages from
  :py:obj:`babel.languages.get_official_languages` should be in
  (intersection).  If this argument is ``None``, all official languages in
  this territory are used.

:param regional: If the regional flag is set, then languages which are
  regionally official are also returned.

:param de_facto: If the de_facto flag is set to `False`, then languages
  which are “de facto” official are not returned.

Definition at line 181 of file locales.py.

183) -> set[babel.Locale]:
184 """Returns a list of :py:obj:`babel.Locale` with languages from
185 :py:obj:`babel.languages.get_official_languages`.
186
187 :param territory: The territory (country or region) code.
188
189 :param languages: A list of language codes the languages from
190 :py:obj:`babel.languages.get_official_languages` should be in
191 (intersection). If this argument is ``None``, all official languages in
192 this territory are used.
193
194 :param regional: If the regional flag is set, then languages which are
195 regionally official are also returned.
196
197 :param de_facto: If the de_facto flag is set to `False`, then languages
198 which are “de facto” official are not returned.
199
200 """
201 ret_val = set()
202 o_languages = babel.languages.get_official_languages(territory, regional=regional, de_facto=de_facto)
203
204 if languages:
205 languages = [l.lower() for l in languages]
206 o_languages = set(l for l in o_languages if l.lower() in languages)
207
208 for lang in o_languages:
209 try:
210 locale = babel.Locale.parse(lang + '_' + territory)
211 ret_val.add(locale)
212 except babel.UnknownLocaleError:
213 continue
214
215 return ret_val
216
217

◆ get_translation_locales()

list[str] searx.locales.get_translation_locales ( )
Returns the list of translation locales (*underscore*).  The list is
generated from the translation folders in :origin:`searx/translations`

Definition at line 123 of file locales.py.

123def get_translation_locales() -> list[str]:
124 """Returns the list of translation locales (*underscore*). The list is
125 generated from the translation folders in :origin:`searx/translations`"""
126
127 global _TR_LOCALES # pylint:disable=global-statement
128 if _TR_LOCALES:
129 return _TR_LOCALES
130
131 tr_locales = []
132 for folder in (Path(searx_dir) / 'translations').iterdir():
133 if not folder.is_dir():
134 continue
135 if not (folder / 'LC_MESSAGES').is_dir():
136 continue
137 tr_locales.append(folder.name)
138 _TR_LOCALES = sorted(tr_locales)
139 return _TR_LOCALES
140
141

◆ get_translations()

searx.locales.get_translations ( )
Monkey patch of :py:obj:`flask_babel.get_translations`

Definition at line 110 of file locales.py.

110def get_translations():
111 """Monkey patch of :py:obj:`flask_babel.get_translations`"""
112 if has_request_context():
113 use_translation = sxng_request.form.get('use-translation')
114 if use_translation in ADDITIONAL_TRANSLATIONS:
115 babel_ext = flask_babel.current_app.extensions['babel']
116 return Translations.load(babel_ext.translation_directories[0], use_translation)
117 return _flask_babel_get_translations()
118
119

References _flask_babel_get_translations.

◆ language_tag()

str searx.locales.language_tag ( babel.Locale locale)
Returns SearXNG's language tag from the locale and if exits, the tag
includes the script name (e.g. en, zh_Hant).

Definition at line 160 of file locales.py.

160def language_tag(locale: babel.Locale) -> str:
161 """Returns SearXNG's language tag from the locale and if exits, the tag
162 includes the script name (e.g. en, zh_Hant).
163 """
164 sxng_lang = locale.language
165 if locale.script:
166 sxng_lang += '_' + locale.script
167 return sxng_lang
168
169

Referenced by build_engine_locales(), get_engine_locale(), and match_locale().

+ Here is the caller graph for this function:

◆ locales_initialize()

searx.locales.locales_initialize ( )
Initialize locales environment of the SearXNG session.

- monkey patch :py:obj:`flask_babel.get_translations` by :py:obj:`get_translations`
- init global names :py:obj:`LOCALE_NAMES`, :py:obj:`RTL_LOCALES`

Definition at line 142 of file locales.py.

142def locales_initialize():
143 """Initialize locales environment of the SearXNG session.
144
145 - monkey patch :py:obj:`flask_babel.get_translations` by :py:obj:`get_translations`
146 - init global names :py:obj:`LOCALE_NAMES`, :py:obj:`RTL_LOCALES`
147 """
148 flask_babel.get_translations = get_translations
149 LOCALE_NAMES.update(data.LOCALES["LOCALE_NAMES"])
150 RTL_LOCALES.update(data.LOCALES["RTL_LOCALES"])
151
152

◆ localeselector()

searx.locales.localeselector ( )

Definition at line 86 of file locales.py.

86def localeselector():
87 locale = 'en'
88 if has_request_context():
89 value = sxng_request.preferences.get_value('locale')
90 if value:
91 locale = value
92
93 # first, set the language that is not supported by babel
94 if locale in ADDITIONAL_TRANSLATIONS:
95 sxng_request.form['use-translation'] = locale
96
97 # second, map locale to a value python-babel supports
98 locale = LOCALE_BEST_MATCH.get(locale, locale)
99
100 if locale == '':
101 # if there is an error loading the preferences
102 # the locale is going to be ''
103 locale = 'en'
104
105 # babel uses underscore instead of hyphen.
106 locale = locale.replace('-', '_')
107 return locale
108
109

◆ match_locale()

str | None searx.locales.match_locale ( str searxng_locale,
list[str] locale_tag_list,
str | None fallback = None )
Return tag from ``locale_tag_list`` that best fits to ``searxng_locale``.

:param str searxng_locale: SearXNG's internal representation of locale (de,
    de-DE, fr-BE, zh, zh-CN, zh-TW ..).

:param list locale_tag_list: The list of locale tags to select from

:param str fallback: fallback locale tag (if unset --> ``None``)

The rules to find a match are implemented in :py:obj:`get_engine_locale`,
the ``engine_locales`` is build up by :py:obj:`build_engine_locales`.

.. hint::

   The *SearXNG locale* string and the members of ``locale_tag_list`` has to
   be known by babel!  The :py:obj:`ADDITIONAL_TRANSLATIONS` are used in the
   UI and are not known by babel --> will be ignored.

Definition at line 369 of file locales.py.

369def match_locale(searxng_locale: str, locale_tag_list: list[str], fallback: str | None = None) -> str | None:
370 """Return tag from ``locale_tag_list`` that best fits to ``searxng_locale``.
371
372 :param str searxng_locale: SearXNG's internal representation of locale (de,
373 de-DE, fr-BE, zh, zh-CN, zh-TW ..).
374
375 :param list locale_tag_list: The list of locale tags to select from
376
377 :param str fallback: fallback locale tag (if unset --> ``None``)
378
379 The rules to find a match are implemented in :py:obj:`get_engine_locale`,
380 the ``engine_locales`` is build up by :py:obj:`build_engine_locales`.
381
382 .. hint::
383
384 The *SearXNG locale* string and the members of ``locale_tag_list`` has to
385 be known by babel! The :py:obj:`ADDITIONAL_TRANSLATIONS` are used in the
386 UI and are not known by babel --> will be ignored.
387 """
388
389 # searxng_locale = 'es'
390 # locale_tag_list = ['es-AR', 'es-ES', 'es-MX']
391
392 if not searxng_locale:
393 return fallback
394
395 locale = get_locale(searxng_locale)
396 if locale is None:
397 return fallback
398
399 # normalize to a SearXNG locale that can be passed to get_engine_locale
400
401 searxng_locale = language_tag(locale)
402 if locale.territory:
403 searxng_locale = region_tag(locale)
404
405 # clean up locale_tag_list
406
407 tag_list = []
408 for tag in locale_tag_list:
409 if tag in ('all', 'auto') or tag in ADDITIONAL_TRANSLATIONS:
410 continue
411 tag_list.append(tag)
412
413 # emulate fetch_traits
414 engine_locales = build_engine_locales(tag_list)
415 return get_engine_locale(searxng_locale, engine_locales, default=fallback)
416
417

References build_engine_locales(), get_engine_locale(), get_locale(), language_tag(), and region_tag().

+ Here is the call graph for this function:

◆ region_tag()

str searx.locales.region_tag ( babel.Locale locale)
Returns SearXNG's region tag from the locale (e.g. zh-TW , en-US).

Definition at line 153 of file locales.py.

153def region_tag(locale: babel.Locale) -> str:
154 """Returns SearXNG's region tag from the locale (e.g. zh-TW , en-US)."""
155 if not locale.territory:
156 raise ValueError('babel.Locale %s: missed a territory' % locale)
157 return locale.language + '-' + locale.territory
158
159

Referenced by build_engine_locales(), and match_locale().

+ Here is the caller graph for this function:

Variable Documentation

◆ _flask_babel_get_translations

searx.locales._flask_babel_get_translations = flask_babel.get_translations
protected

Definition at line 51 of file locales.py.

Referenced by get_translations().

◆ _TR_LOCALES

list searx.locales._TR_LOCALES = []
protected

Definition at line 120 of file locales.py.

◆ ADDITIONAL_TRANSLATIONS

dict searx.locales.ADDITIONAL_TRANSLATIONS
Initial value:
1= {
2 "dv": "ދިވެހި (Dhivehi)",
3 "oc": "Occitan",
4 "szl": "Ślōnski (Silesian)",
5 "pap": "Papiamento",
6}

Definition at line 64 of file locales.py.

◆ LOCALE_BEST_MATCH

dict searx.locales.LOCALE_BEST_MATCH
Initial value:
1= {
2 "dv": "si",
3 "oc": 'fr-FR',
4 "szl": "pl",
5 "nl-BE": "nl",
6 "zh-HK": "zh-Hant-TW",
7 "pap": "pt-BR",
8}

Definition at line 73 of file locales.py.

◆ LOCALE_NAMES

dict searx.locales.LOCALE_NAMES = {}

Definition at line 53 of file locales.py.

◆ logger

searx.locales.logger = logger.getChild('locales')

Definition at line 47 of file locales.py.

◆ RTL_LOCALES

set searx.locales.RTL_LOCALES = set()

Definition at line 60 of file locales.py.