RSS-to-Telegram-Bot/src/i18n/__init__.py

163 lines
6.5 KiB
Python

from __future__ import annotations
from typing import Optional
from json import load
from os import listdir, path
from multidict import CIMultiDict, istr
from telethon.tl import types
I18N_PATH = path.split(path.realpath(__file__))[0]
ALL_LANGUAGES = tuple(sorted(lang[:-5] for lang in listdir(I18N_PATH) if lang.endswith('.json')))
FALLBACK_LANGUAGE = istr('en')
NO_FALLBACK_KEYS = {istr('iso_639_code')}
REPO_TYPE = 'GitHub'
REPO_URL = 'https://github.com/Rongronggg9/RSS-to-Telegram-Bot'
NEED_PRE_FILL = {
# istr('default_emoji_header_description'):
# ('↩',),
istr('read_formatting_settings_guidebook_html'):
('https://github.com/Rongronggg9/RSS-to-Telegram-Bot/blob/dev/docs/formatting-settings.md',),
}
COMMANDS = ('sub', 'unsub', 'unsub_all', 'list', 'set', 'set_default', 'import', 'export', 'activate_subs',
'deactivate_subs', 'version', 'help', 'lang')
MANAGER_COMMANDS = ('test', 'set_option', 'user_info')
REQUIRED_KEYS = {istr('lang_code'), istr('lang_native_name'), istr('select_lang_prompt')}
class _I18N:
__instance: Optional["_I18N"] = None
__initialized: bool = False
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__new__(cls)
return cls.__instance
def __init__(self):
global ALL_LANGUAGES
if self.__initialized:
return
self.__l10n_d: CIMultiDict[_L10N] = CIMultiDict()
self.__iso_639_d: CIMultiDict[str] = CIMultiDict()
for lang in ALL_LANGUAGES:
l10n = _L10N(lang)
iso_639_code = l10n['iso_639_code']
if not all(l10n.key_exist(key) for key in REQUIRED_KEYS):
ALL_LANGUAGES = tuple(filter(lambda l: l != lang, ALL_LANGUAGES))
continue
self.__l10n_d[lang] = l10n
if iso_639_code:
self.__iso_639_d[iso_639_code] = lang
self.__initialized = True
self.set_help_msg_html()
def __getitem__(self, lang_code: Optional[str]) -> "_L10N":
if not lang_code or not isinstance(lang_code, str):
return self.get_fallback_l10n()
return self.__l10n_d[lang_code] if lang_code in self.__l10n_d else self.get_fallback_l10n(lang_code)
def get_all_l10n_string(self, key: str, html_escaped: bool = False,
only_iso_639: bool = False) -> tuple[str, ...]:
languages = self.__iso_639_d.keys() if only_iso_639 else ALL_LANGUAGES
all_l10n = tuple(self[lang_code] for lang_code in languages)
res = tuple(
l10n.html_escaped(key) if html_escaped else l10n[key]
for l10n in all_l10n
if l10n.key_exist(key)
)
return res or (key,)
def get_fallback_l10n(self, lang_code: Optional[str] = None) -> "_L10N":
if not lang_code or not isinstance(lang_code, str):
return self.__l10n_d[FALLBACK_LANGUAGE]
iso_639_code = lang_code.split('-')[0].split('_')[0]
if iso_639_code in self.__iso_639_d:
return self.__l10n_d[self.__iso_639_d[iso_639_code]]
return self.__l10n_d[FALLBACK_LANGUAGE]
def set_help_msg_html(self):
cmd_lang_description = ' / '.join(self.get_all_l10n_string('cmd_description_lang', html_escaped=True,
only_iso_639=True))
for l10n in self.__l10n_d.values():
l10n_cmd_description_lang = l10n['cmd_description_lang']
_cmd_description_lang = (
(f'{l10n_cmd_description_lang} / '
if not l10n['iso_639_code'] and l10n_cmd_description_lang not in cmd_lang_description else '')
+ cmd_lang_description
)
help_msg_html = (
f"{l10n.html_escaped('rsstt_slogan')}\n\n"
f"{REPO_TYPE}: {REPO_URL}\n\n"
f"{l10n.html_escaped('commands')}:\n"
)
help_msg_html += '\n'.join(
f"<b>/{command}</b>: "
f"{l10n.html_escaped(f'cmd_description_{command}') if command != 'lang' else _cmd_description_lang}"
for command in COMMANDS
)
manager_help_msg_html = help_msg_html + '\n\n' + '\n'.join(
f"<b>/{command}</b>: {l10n.html_escaped(f'cmd_description_{command}')}"
for command in MANAGER_COMMANDS
)
l10n.set_help_msg_html(help_msg_html, manager_help_msg_html)
class _L10N:
def __init__(self, lang_code: str):
self.__lang_code: str = lang_code
self.__l10n_lang: CIMultiDict[str]
with open(path.join(I18N_PATH, f'{lang_code}.json'), encoding='utf-8') as f:
l10n_d = load(f)
l10n_d_flatten = {}
assert isinstance(l10n_d, dict)
for key, value in l10n_d.items():
assert isinstance(value, dict)
for k, v in value.items():
assert isinstance(v, str) and k not in l10n_d_flatten
if v and k in NEED_PRE_FILL:
try:
v = v % NEED_PRE_FILL[k]
except TypeError:
v = ""
l10n_d_flatten[k] = v
self.__l10n_lang = CIMultiDict(l10n_d_flatten)
def key_exist(self, key: str):
return key in self.__l10n_lang and (self.__l10n_lang[key] or key in NO_FALLBACK_KEYS)
def __getitem__(self, key: str) -> str:
if self.key_exist(key):
return self.__l10n_lang[key]
if self.__lang_code != FALLBACK_LANGUAGE:
# get ISO 639 fallback if needed
return _I18N().get_fallback_l10n(None if self.__l10n_lang['iso_639_code'] else self.__lang_code)[key]
return key
def html_escaped(self, key: str):
return self[key].replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
def set_help_msg_html(self, msg_html: str, manager_msg_html: str = None):
self.__l10n_lang['help_msg_html'] = msg_html
self.__l10n_lang['manager_help_msg_html'] = manager_msg_html or msg_html
i18n = _I18N()
def get_commands_list(lang: Optional[str] = None, manager: bool = False) -> list[types.BotCommand]:
commands = [types.BotCommand(command=command, description=i18n[lang][f'cmd_description_{command}'])
for command in COMMANDS]
if manager:
commands.extend(types.BotCommand(command=command, description=i18n[lang][f'cmd_description_{command}'])
for command in MANAGER_COMMANDS)
return commands