2023-04-12 11:55:34 +00:00
|
|
|
"""
|
|
|
|
A localization module that loads strings from JSONs in the locale directory.
|
|
|
|
It provides a function to get a localized string by its ID.
|
|
|
|
If the string is not found in the current locale, it will try to find it in the default locale.
|
|
|
|
If the string is not found in the default locale, it will return the ID.
|
|
|
|
|
|
|
|
The locales are loaded into the memory at the api startup and kept in a singleton.
|
|
|
|
"""
|
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import typing
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
from selfprivacy_api.utils.singleton_metaclass import SingletonMetaclass
|
|
|
|
|
|
|
|
DEFAULT_LOCALE = "en"
|
|
|
|
LOCALE_DIR: Path = Path(__file__).parent.parent / "locales"
|
|
|
|
|
|
|
|
|
|
|
|
class Localization(metaclass=SingletonMetaclass):
|
|
|
|
"""Localization class."""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.locales: typing.Dict[str, typing.Dict[str, str]] = {}
|
|
|
|
self.load_locales()
|
|
|
|
|
|
|
|
def load_locales(self):
|
|
|
|
"""Load locales from locale directory."""
|
|
|
|
for locale in os.listdir(str(LOCALE_DIR)):
|
|
|
|
locale_path = LOCALE_DIR / locale
|
|
|
|
if not locale_path.is_dir():
|
|
|
|
continue
|
|
|
|
self.locales[locale] = {}
|
|
|
|
for file in os.listdir(str(locale_path)):
|
|
|
|
if file.endswith(".json"):
|
|
|
|
with open(locale_path / file, "r") as locale_file:
|
|
|
|
locale_data = self.flatten_dict(json.load(locale_file))
|
|
|
|
self.locales[locale].update(locale_data)
|
|
|
|
|
|
|
|
def get(self, string_id: str, locale: str = DEFAULT_LOCALE) -> str:
|
|
|
|
"""Get localized string by its ID."""
|
|
|
|
if locale in self.locales and string_id in self.locales[locale]:
|
|
|
|
return self.locales[locale][string_id]
|
|
|
|
if DEFAULT_LOCALE in self.locales and string_id in self.locales[DEFAULT_LOCALE]:
|
|
|
|
return self.locales[DEFAULT_LOCALE][string_id]
|
|
|
|
return string_id
|
|
|
|
|
2023-04-12 13:59:23 +00:00
|
|
|
def supported_locales(self) -> typing.List[str]:
|
|
|
|
"""Return a list of supported languages."""
|
|
|
|
return list(self.locales.keys())
|
|
|
|
|
|
|
|
def get_locale(self, locale: typing.Optional[str]) -> str:
|
|
|
|
"""Parse the value of Accept-Language header and return the most preferred supported locale."""
|
|
|
|
if locale is None:
|
|
|
|
return DEFAULT_LOCALE
|
|
|
|
for lang in locale.split(","):
|
|
|
|
lang = lang.split(";")[0]
|
|
|
|
if lang in self.locales:
|
|
|
|
return lang
|
|
|
|
return DEFAULT_LOCALE
|
|
|
|
|
2023-04-12 11:55:34 +00:00
|
|
|
def flatten_dict(
|
|
|
|
self, d: typing.Dict[str, typing.Any], parent_key: str = "", sep: str = "."
|
|
|
|
) -> typing.Dict[str, str]:
|
|
|
|
"""Flatten a dict."""
|
|
|
|
items = []
|
|
|
|
for k, v in d.items():
|
|
|
|
new_key = parent_key + sep + k if parent_key else k
|
|
|
|
if isinstance(v, dict):
|
|
|
|
items.extend(self.flatten_dict(v, new_key, sep=sep).items())
|
|
|
|
else:
|
|
|
|
items.append((new_key, v))
|
|
|
|
return dict(items)
|