Archived
1
0
Fork 0

I18n Tree & locale reload (#695)

This commit is contained in:
Nattiden 2023-07-18 01:42:29 +08:00 committed by GitHub
parent fafe8e5d80
commit 4731ab00df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 76 additions and 36 deletions

View file

@ -12,8 +12,40 @@ from .text import remove_suffix
# We might change this behavior in the future and read them on demand as
# locale files get too large
locale_cache = {}
class LocaleNode():
'''本地化树节点'''
value: str
childen: dict
def __init__(self,v:str = None):
self.value = v
self.childen = {}
def qurey_node(self,path:str):
'''查询本地化树节点'''
return self._qurey_node(path.split('.'))
def _qurey_node(self,path:list):
'''通过路径队列查询本地化树节点'''
if len(path) == 0:
return self
nxt_node = path[0]
if nxt_node in self.childen.keys():
return self.childen[nxt_node]._qurey_node(path[1:])
else:
return None
def update_node(self,path:str,write_value:str):
'''更新本地化树节点'''
return self._update_node(path.split('.'),write_value)
def _update_node(self,path:list,write_value:str):
'''通过路径队列更新本地化树节点'''
if len(path) == 0:
self.value = write_value
return
nxt_node = path[0]
if nxt_node not in self.childen.keys():
self.childen[nxt_node] = LocaleNode()
self.childen[nxt_node]._update_node(path[1:],write_value)
locale_root = LocaleNode()
# From https://stackoverflow.com/a/6027615
def flatten(d: Dict[str, Any], parent_key='', sep='.'):
@ -26,15 +58,15 @@ def flatten(d: Dict[str, Any], parent_key='', sep='.'):
items.append((new_key, v))
return dict(items)
def load_locale_file():
locale_dict = {}
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))
locale_dict[remove_suffix(l, '.json')] = flatten(json.load(f))
except Exception as e:
err_prompt.append(str(e))
modules_path = os.path.abspath('./modules')
@ -45,29 +77,24 @@ def load_locale_file():
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)))
if remove_suffix(lm, '.json') in locale_dict:
locale_dict[remove_suffix(lm, '.json')].update(flatten(json.load(f)))
else:
locale_cache[remove_suffix(lm, '.json')] = flatten(json.load(f))
locale_dict[remove_suffix(lm, '.json')] = flatten(json.load(f))
except Exception as e:
err_prompt.append(f'Failed to load {ml}: {e}')
for lang in locale_dict.keys():
for k in locale_dict[lang].keys():
locale_root.update_node(f'{lang}.{k}',locale_dict[lang][k])
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.data: LocaleNode = locale_root.qurey_node(locale)
self.fallback_lng = fallback_lng
def __getitem__(self, key: str):
@ -80,16 +107,20 @@ class Locale:
'''获取本地化字符串'''
localized = self.get_string_with_fallback(key, fallback_failed_prompt)
return Template(localized).safe_substitute(*args, **kwargs)
def get_locale_node(self, path: str):
'''获取本地化节点'''
return self.data.qurey_node(path)
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. 如果本地化字符串存在,直接返回
node = self.data.qurey_node(key)
if node is not None:
return node.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 lng in locale_root.childen:
string = locale_root.qurey_node(lng).qurey_node(key).value
if string is not None:
return string # 2. 如果在 fallback 语言中本地化字符串存在,直接返回
if fallback_failed_prompt:
@ -99,9 +130,7 @@ class Locale:
return key
# 3. 如果在 fallback 语言中本地化字符串不存在,返回 key
def get_available_locales():
return list(locale_cache.keys())
return list(locale_root.keys())
__all__ = ['Locale', 'load_locale_file', 'get_available_locales']
__all__ = ['Locale', 'load_locale_file', 'get_available_locales']

View file

@ -1,4 +1,4 @@
import secrets
import secrets
from config import Config
from core.builtins import Bot
@ -36,7 +36,7 @@ async def flipCoins(count: int, msg):
if FACE_UP_RATE + FACE_DOWN_RATE > 10000 or FACE_UP_RATE < 0 or FACE_DOWN_RATE < 0 or MAX_COIN_NUM <= 0:
raise OverflowError(msg.locale.t("error.config"))
if count > MAX_COIN_NUM:
return msg.locale.t("coin.message.error.out_of_range", max=count_max)
return msg.locale.t("coin.message.error.out_of_range", max=MAX_COIN_NUM)
if count == 0:
return msg.locale.t("coin.message.error.nocoin")
if count < 0:

View file

@ -1,4 +1,4 @@
{
{
"coin.help": "抛任意枚硬币。",
"coin.help.desc": "抛硬币。",
"coin.help.regex.desc": "(丢|抛)[<count>(个|枚)]硬币",
@ -14,7 +14,7 @@
"coin.message.mix.stand": "……还有 ${stand} 枚立起来了!",
"coin.message.mix.tail": "${tail} 枚是反面",
"coin.message.mix.tail2": "…有 ${tail} 枚是反面",
"coin.message.prompt": "你抛了 ${count} 枚硬币",
"coin.message.prompt": "你抛了 ${count} 枚硬币",
"coin.message.stand": "…\n…它立起来了",
"coin.message.tail": "…\n…是反面"
}

View file

@ -16,7 +16,8 @@
"core.help.alias.remove": "移除自定义命令别名。",
"core.help.alias.reset": "重置自定义命令别名。",
"core.help.leave": "使机器人离开群组。",
"core.help.locale": "设置机器人运行语言。",
"core.help.locale.reload": "重载机器人语言文件",
"core.help.locale.set": "设置机器人运行语言。",
"core.help.module.disable": "关闭一个/多个模块。",
"core.help.module.disable_all": "关闭所有模块。",
"core.help.module.enable": "开启一个/多个模块。",
@ -66,7 +67,8 @@
"core.message.confirm": "你确定吗?",
"core.message.leave.confirm": "你确定吗?此操作不可逆。",
"core.message.leave.success": "已执行,再见。",
"core.message.locale.invalid": "语言格式错误,支持的语言有:${lang}。",
"core.message.locale.set.invalid": "语言格式错误,支持的语言有:${lang}。",
"core.message.locale.reload.failed": "以下字符串重载失败:${detail}。",
"core.message.module.disable.already": "失败:“${module}”模块已关闭。",
"core.message.module.disable.base": "失败:“${module}”为基础模块,无法关闭。",
"core.message.module.disable.not_found": "失败:“${module}”模块不存在。",

View file

@ -6,7 +6,7 @@ import time
from config import Config
from core.builtins import Bot, PrivateAssets
from core.component import module
from core.utils.i18n import get_available_locales, Locale
from core.utils.i18n import get_available_locales, Locale, load_locale_file
from cpuinfo import get_cpu_info
from database import BotDBUtil
from datetime import datetime
@ -128,20 +128,29 @@ async def config_ban(msg: Bot.MessageSession):
locale = module('locale',
base=True,
required_admin=True,
developers=['Dianliang233']
developers=['Dianliang233','Light-Beacon']
)
@locale.handle(['<lang> {{core.help.locale}}'])
@locale.handle(['set <lang> {{core.help.locale.set}}'])
async def config_gu(msg: Bot.MessageSession):
lang = msg.parsed_msg['<lang>']
if lang in ['zh_cn', 'zh_tw', 'en_us']:
if BotDBUtil.TargetInfo(msg.target.targetId).edit('locale', lang):
await msg.finish(Locale(lang).t('success'))
else:
await msg.finish(msg.locale.t("core.message.locale.invalid", lang=''.join(get_available_locales())))
await msg.finish(msg.locale.t("core.message.locale.set.invalid", lang=''.join(get_available_locales())))
@locale.handle(['reload {{core.help.locale.reload}}'])
async def reload_locale(msg: Bot.MessageSession):
if msg.checkSuperUser():
err = load_locale_file()
if len(err) == 0:
await msg.finish(msg.locale.t("success"))
else:
await msg.finish(msg.locale.t("core.message.locale.reload.failed",detail='\n'.join(err)))
else:
await msg.finish(msg.locale.t("parser.superuser.permission.denied"))
whoami = module('whoami', developers=['Dianliang233'], base=True)