107 lines
3.7 KiB
Python
107 lines
3.7 KiB
Python
import os
|
|
from collections.abc import MutableMapping
|
|
from string import Template
|
|
from typing import TypedDict, Dict, Any
|
|
|
|
import ujson as json
|
|
|
|
from config import Config
|
|
from .text import remove_suffix
|
|
|
|
# Load all locale files into memory
|
|
|
|
# We might change this behavior in the future and read them on demand as
|
|
# locale files get too large
|
|
locale_cache = {}
|
|
|
|
|
|
# From https://stackoverflow.com/a/6027615
|
|
def flatten(d: Dict[str, Any], parent_key='', sep='.'):
|
|
items = []
|
|
for k, v in d.items():
|
|
new_key = parent_key + sep + k if parent_key else k
|
|
if isinstance(v, MutableMapping):
|
|
items.extend(flatten(v, new_key, sep=sep).items())
|
|
else:
|
|
items.append((new_key, v))
|
|
return dict(items)
|
|
|
|
|
|
def load_locale_file():
|
|
err_prompt = []
|
|
locales_path = os.path.abspath('./locales')
|
|
locales = os.listdir(locales_path)
|
|
try:
|
|
for l in locales:
|
|
with open(f'{locales_path}/{l}', 'r', encoding='utf-8') as f:
|
|
locale_cache[remove_suffix(l, '.json')] = flatten(json.load(f))
|
|
except Exception as e:
|
|
err_prompt.append(str(e))
|
|
modules_path = os.path.abspath('./modules')
|
|
for m in os.listdir(modules_path):
|
|
if os.path.isdir(f'{modules_path}/{m}/locales'):
|
|
locales_m = os.listdir(f'{modules_path}/{m}/locales')
|
|
for lm in locales_m:
|
|
ml = f'{modules_path}/{m}/locales/{lm}'
|
|
with open(ml, 'r', encoding='utf-8') as f:
|
|
try:
|
|
if remove_suffix(lm, '.json') in locale_cache:
|
|
locale_cache[remove_suffix(lm, '.json')].update(flatten(json.load(f)))
|
|
else:
|
|
locale_cache[remove_suffix(lm, '.json')] = flatten(json.load(f))
|
|
except Exception as e:
|
|
err_prompt.append(f'Failed to load {ml}: {e}')
|
|
|
|
return err_prompt
|
|
|
|
|
|
class LocaleFile(TypedDict):
|
|
key: str
|
|
string: str
|
|
|
|
|
|
class Locale:
|
|
def __init__(self, locale: str, fallback_lng=None):
|
|
"""创建一个本地化对象"""
|
|
|
|
if fallback_lng is None:
|
|
fallback_lng = ['zh_cn', 'zh_tw', 'en_us']
|
|
self.locale = locale
|
|
self.data: LocaleFile = locale_cache[locale]
|
|
self.fallback_lng = fallback_lng
|
|
|
|
def __getitem__(self, key: str):
|
|
return self.data[key]
|
|
|
|
def __contains__(self, key: str):
|
|
return key in self.data
|
|
|
|
def t(self, key: str, fallback_failed_prompt=True, *args, **kwargs) -> str:
|
|
'''获取本地化字符串'''
|
|
localized = self.get_string_with_fallback(key, fallback_failed_prompt)
|
|
return Template(localized).safe_substitute(*args, **kwargs)
|
|
|
|
def get_string_with_fallback(self, key: str, fallback_failed_prompt) -> str:
|
|
value = self.data.get(key, None)
|
|
if value is not None:
|
|
return value # 1. 如果本地化字符串存在,直接返回
|
|
fallback_lng = list(self.fallback_lng)
|
|
fallback_lng.insert(0, self.locale)
|
|
for lng in fallback_lng:
|
|
if lng in locale_cache:
|
|
string = locale_cache[lng].get(key, None)
|
|
if string is not None:
|
|
return string # 2. 如果在 fallback 语言中本地化字符串存在,直接返回
|
|
if fallback_failed_prompt:
|
|
return f'{{{key}}}' + self.t("i18n.prompt.fallback.failed", url=Config('bug_report_url'),
|
|
fallback_failed_prompt=False)
|
|
else:
|
|
return key
|
|
# 3. 如果在 fallback 语言中本地化字符串不存在,返回 key
|
|
|
|
|
|
def get_available_locales():
|
|
return list(locale_cache.keys())
|
|
|
|
|
|
__all__ = ['Locale', 'load_locale_file', 'get_available_locales']
|