1 # SPDX-License-Identifier: GPL-3.0-or-later
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2024 by the Nominatim developer community.
 
   6 # For a full list of authors see the git log.
 
   8 Helper functions for localizing names of results.
 
  10 from typing import Mapping, List, Optional
 
  11 from .config import Configuration
 
  12 from .results import AddressLines, BaseResultT
 
  18     """ Helper class for localization of names.
 
  20         It takes a list of language prefixes in their order of preferred
 
  24     def __init__(self, langs: Optional[List[str]] = None):
 
  25         self.config = Configuration(None)
 
  26         self.languages = langs or []
 
  27         self.name_tags: List[str] = []
 
  29         parts = self.config.OUTPUT_NAMES.split(',')
 
  33             if part.endswith(":XX"):
 
  34                 self._add_lang_tags(part[:-3])
 
  38     def __bool__(self) -> bool:
 
  39         return len(self.languages) > 0
 
  41     def _add_tags(self, *tags: str) -> None:
 
  43             self.name_tags.append(tag)
 
  44             self.name_tags.append(f"_place_{tag}")
 
  46     def _add_lang_tags(self, *tags: str) -> None:
 
  48             for lang in self.languages:
 
  49                 self.name_tags.append(f"{tag}:{lang}")
 
  50                 self.name_tags.append(f"_place_{tag}:{lang}")
 
  52     def display_name(self, names: Optional[Mapping[str, str]]) -> str:
 
  53         """ Return the best matching name from a dictionary of names
 
  54             containing different name variants.
 
  56             If 'names' is null or empty, an empty string is returned. If no
 
  57             appropriate localization is found, the first name is returned.
 
  63             for tag in self.name_tags:
 
  67         # Nothing? Return any of the other names as a default.
 
  68         return next(iter(names.values()))
 
  71     def from_accept_languages(langstr: str) -> 'Locales':
 
  72         """ Create a localization object from a language list in the
 
  73             format of HTTP accept-languages header.
 
  75             The functions tries to be forgiving of format errors by first splitting
 
  76             the string into comma-separated parts and then parsing each
 
  77             description separately. Badly formatted parts are then ignored.
 
  79         # split string into languages
 
  81         for desc in langstr.split(','):
 
  82             m = re.fullmatch(r'\s*([a-z_-]+)(?:;\s*q\s*=\s*([01](?:\.\d+)?))?\s*',
 
  85                 candidates.append((m[1], float(m[2] or 1.0)))
 
  87         # sort the results by the weight of each language (preserving order).
 
  88         candidates.sort(reverse=True, key=lambda e: e[1])
 
  90         # If a language has a region variant, also add the language without
 
  91         # variant but only if it isn't already in the list to not mess up the weight.
 
  93         for lid, _ in candidates:
 
  95             parts = lid.split('-', 1)
 
  96             if len(parts) > 1 and all(c[0] != parts[0] for c in candidates):
 
  97                 languages.append(parts[0])
 
  99         return Locales(languages)
 
 101     def localize(self, lines: AddressLines) -> None:
 
 102         """ Sets the local name of address parts according to the chosen
 
 105             Only address parts that are marked as isaddress are localized.
 
 107             AddressLines should be modified in place.
 
 110             if line.isaddress and line.names:
 
 111                 line.local_name = self.display_name(line.names)
 
 113     def localize_results(self, results: List[BaseResultT]) -> None:
 
 114         """ Set the local name of results according to the chosen
 
 117         for result in results:
 
 118             result.locale_name = self.display_name(result.names)
 
 119             if result.address_rows:
 
 120                 self.localize(result.address_rows)