0.2.0
This commit is contained in:
294
website/i18n.py
Normal file
294
website/i18n.py
Normal file
@@ -0,0 +1,294 @@
|
||||
"""站点界面的中英文文案。
|
||||
|
||||
只有「网站框架」的部分需要翻译:导航、按钮、页面标题、页脚统计等等。
|
||||
文档正文(README.md、INSTALLATION.md)、源码里的说明与注释、教程脚本
|
||||
都保持原样,不在这里翻译——它们是文件内容,不是界面。
|
||||
|
||||
站点是静态的,所以不做运行时切换,而是构建时生成两套页面:
|
||||
* 中文在站点根目录(index.html)
|
||||
* 英文在 en/ 子目录(en/index.html)
|
||||
两套页面里的文件引用都相对于各自所在的目录,所以每个语言目录都是自洽的。
|
||||
|
||||
新增文案时,zh 与 en 两边都要写;缺哪一边,构建时会直接报错,
|
||||
免得网站上线后有一半界面还是另一种语言。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# 站点支持的语言,按顺序构建
|
||||
LANGS: tuple[str, ...] = ("zh", "en")
|
||||
|
||||
# 默认语言(首页用的语言,也是没有指定语言时的回退)
|
||||
DEFAULT_LANG = "zh"
|
||||
|
||||
# 每种语言的页面放在站点根目录下的哪个子目录
|
||||
LANG_DIRS: dict[str, str] = {
|
||||
"zh": "",
|
||||
"en": "en",
|
||||
}
|
||||
|
||||
# <html lang="..."> 与 hreflang 用的取值
|
||||
HTML_LANG: dict[str, str] = {
|
||||
"zh": "zh-CN",
|
||||
"en": "en",
|
||||
}
|
||||
|
||||
# 语言在界面上的名字(切换按钮上显示的)
|
||||
LANG_NAMES: dict[str, str] = {
|
||||
"zh": "中",
|
||||
"en": "En",
|
||||
}
|
||||
|
||||
# 语言切换按钮上显示的完整语言名。
|
||||
# 用完整名字而不是「中 / En」,是因为无障碍规则要求:
|
||||
# 按钮上看得见的文字必须包含在它给读屏软件的无障碍名称里。
|
||||
# 「En」配「切换语言」对不上,写成「English」配「切换到 English 界面」就没问题。
|
||||
LANG_NAMES_FULL: dict[str, str] = {
|
||||
"zh": "中文",
|
||||
"en": "English",
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 界面文案
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
STRINGS: dict[str, dict[str, str]] = {
|
||||
"zh": {
|
||||
# 顶栏与页脚
|
||||
"skip": "跳到主要内容",
|
||||
"theme_toggle": "切换深浅色主题",
|
||||
"menu_open": "打开菜单",
|
||||
"menu_close": "关闭菜单",
|
||||
"to_top": "回到顶部",
|
||||
"foot_stats": "{version} · {modules} 个模块 · {functions} 个函数 · {lines} 行源码",
|
||||
"switch_language": "切换语言",
|
||||
"switch_to_lang": "切换到 {lang} 界面",
|
||||
# 导航
|
||||
"nav_home": "首页",
|
||||
"nav_installation": "安装",
|
||||
"nav_api": "API 参考",
|
||||
"nav_tutorials": "教程",
|
||||
"nav_about": "关于",
|
||||
# 页面标题与描述
|
||||
"page_home": "{name} 官方网站",
|
||||
"page_installation": "安装",
|
||||
"page_api": "API 参考",
|
||||
"page_tutorials": "教程",
|
||||
"page_about": "关于",
|
||||
"desc_installation": "{name} 的安装与使用说明",
|
||||
"desc_api": "{name} 的完整 API 参考",
|
||||
"desc_tutorials": "{name} 的教程与示例代码",
|
||||
"desc_about": "关于 {name}",
|
||||
# 首页
|
||||
"hero_eyebrow": "Python package",
|
||||
"hero_version": "当前版本",
|
||||
"hero_python": "支持 Python {version}",
|
||||
"copy": "复制",
|
||||
"stat_modules": "模块",
|
||||
"stat_functions": "函数",
|
||||
"stat_lines": "行源码",
|
||||
"section_modules": "模块一览",
|
||||
"all_api": "全部 API →",
|
||||
"section_package": "包信息",
|
||||
"field_display_name": "展示名",
|
||||
"field_pypi_name": "PyPI 名称",
|
||||
"field_import_name": "导入名",
|
||||
"field_version": "版本",
|
||||
"field_python": "Python",
|
||||
"field_homepage": "主页",
|
||||
# API 参考
|
||||
"api_modules_title": "模块",
|
||||
"api_functions_title": "函数",
|
||||
"api_classes_title": "类",
|
||||
"param_name": "参数",
|
||||
"param_type": "类型",
|
||||
"param_default": "默认值",
|
||||
"param_required": "必填",
|
||||
"badge_deprecated": "已弃用",
|
||||
"badge_not_exported": "未导出",
|
||||
"parse_error": "解析失败",
|
||||
"view_source": "查看源码",
|
||||
"calls": "调用了:",
|
||||
"base_classes": "基类:",
|
||||
"module_meta": "{lines} 行 · {count} 个函数",
|
||||
# 关于 / 安装 / 教程
|
||||
"toc_title": "目录",
|
||||
"tut_groups_title": "分组",
|
||||
"tut_root_title": "开始使用",
|
||||
"tut_root_subtitle": "Getting started",
|
||||
"file_not_found": "未找到 {path}。",
|
||||
"download_script": "下载脚本",
|
||||
"view_original": "查看原文",
|
||||
"lines": "{count} 行",
|
||||
"functions_count": "{count} 个函数",
|
||||
},
|
||||
"en": {
|
||||
# 顶栏与页脚
|
||||
"skip": "Skip to main content",
|
||||
"theme_toggle": "Toggle light and dark theme",
|
||||
"menu_open": "Open menu",
|
||||
"menu_close": "Close menu",
|
||||
"to_top": "Back to top",
|
||||
"foot_stats": (
|
||||
"{version} · {modules} modules · {functions} functions · {lines} lines of code"
|
||||
),
|
||||
"switch_language": "Switch language",
|
||||
"switch_to_lang": "Switch to {lang} interface",
|
||||
# 导航
|
||||
"nav_home": "Home",
|
||||
"nav_installation": "Install",
|
||||
"nav_api": "API Reference",
|
||||
"nav_tutorials": "Tutorials",
|
||||
"nav_about": "About",
|
||||
# 页面标题与描述
|
||||
"page_home": "{name} official site",
|
||||
"page_installation": "Installation",
|
||||
"page_api": "API Reference",
|
||||
"page_tutorials": "Tutorials",
|
||||
"page_about": "About",
|
||||
"desc_installation": "How to install and use {name}",
|
||||
"desc_api": "The complete API reference of {name}",
|
||||
"desc_tutorials": "Tutorials and example code for {name}",
|
||||
"desc_about": "About {name}",
|
||||
# 首页
|
||||
"hero_eyebrow": "Python package",
|
||||
"hero_version": "Current version",
|
||||
"hero_python": "Supports Python {version}",
|
||||
"copy": "Copy",
|
||||
"stat_modules": "Modules",
|
||||
"stat_functions": "Functions",
|
||||
"stat_lines": "Lines of code",
|
||||
"section_modules": "Modules",
|
||||
"all_api": "All API →",
|
||||
"section_package": "Package info",
|
||||
"field_display_name": "Display name",
|
||||
"field_pypi_name": "PyPI name",
|
||||
"field_import_name": "Import name",
|
||||
"field_version": "Version",
|
||||
"field_python": "Python",
|
||||
"field_homepage": "Homepage",
|
||||
# API 参考
|
||||
"api_modules_title": "Modules",
|
||||
"api_functions_title": "Functions",
|
||||
"api_classes_title": "Classes",
|
||||
"param_name": "Parameter",
|
||||
"param_type": "Type",
|
||||
"param_default": "Default",
|
||||
"param_required": "Required",
|
||||
"badge_deprecated": "Deprecated",
|
||||
"badge_not_exported": "Not exported",
|
||||
"parse_error": "Parse error",
|
||||
"view_source": "View source",
|
||||
"calls": "Calls: ",
|
||||
"base_classes": "Base classes: ",
|
||||
"module_meta": "{lines} lines · {count} functions",
|
||||
# 关于 / 安装 / 教程
|
||||
"toc_title": "Contents",
|
||||
"tut_groups_title": "Groups",
|
||||
"tut_root_title": "Getting started",
|
||||
"tut_root_subtitle": "Start here",
|
||||
"file_not_found": "{path} not found.",
|
||||
"download_script": "Download script",
|
||||
"view_original": "View original",
|
||||
"lines": "{count} lines",
|
||||
"functions_count": "{count} functions",
|
||||
},
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 脚本里用到的文案
|
||||
# --------------------------------------------------------------------------
|
||||
# site.js 在运行时才生成一些按钮(复制按钮、筛选框……),
|
||||
# 这些文字没法在构建时写进 HTML,只能由页面把文案传给脚本。
|
||||
|
||||
JS_STRINGS: dict[str, dict[str, str]] = {
|
||||
"zh": {
|
||||
"copied": "已复制",
|
||||
"copy": "复制",
|
||||
"copy_code": "复制代码",
|
||||
"copy_anchor": "复制本函数的链接",
|
||||
"theme_to_light": "切换到浅色主题",
|
||||
"theme_to_dark": "切换到深色主题",
|
||||
"menu_open": "打开菜单",
|
||||
"menu_close": "关闭菜单",
|
||||
"filter_placeholder": "在本页筛选函数…",
|
||||
"filter_label": "筛选函数",
|
||||
"filter_clear": "清除",
|
||||
"filter_count": "显示 {shown} / {total} 个函数",
|
||||
},
|
||||
"en": {
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"copy_code": "Copy code",
|
||||
"copy_anchor": "Copy link to this function",
|
||||
"theme_to_light": "Switch to light theme",
|
||||
"theme_to_dark": "Switch to dark theme",
|
||||
"menu_open": "Open menu",
|
||||
"menu_close": "Close menu",
|
||||
"filter_placeholder": "Filter functions on this page…",
|
||||
"filter_label": "Filter functions",
|
||||
"filter_clear": "Clear",
|
||||
"filter_count": "Showing {shown} / {total} functions",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Translator:
|
||||
"""按语言取文案的小工具。
|
||||
|
||||
t("nav_api") 取文案;t("lines", count=3) 取带占位符的文案。
|
||||
找不到的键原样返回,方便一眼看出漏了哪一条。
|
||||
"""
|
||||
|
||||
def __init__(self, lang: str) -> None:
|
||||
if lang not in STRINGS:
|
||||
raise ValueError(f"没有这种语言:{lang}")
|
||||
self.lang = lang
|
||||
self._strings = STRINGS[lang]
|
||||
self._js = JS_STRINGS[lang]
|
||||
|
||||
def __call__(self, key: str, **kwargs: object) -> str:
|
||||
text = self._strings.get(key)
|
||||
if text is None:
|
||||
# 漏写时退回默认语言,再退回键名本身
|
||||
text = STRINGS[DEFAULT_LANG].get(key, key)
|
||||
if not kwargs:
|
||||
return text
|
||||
return text.format(**kwargs)
|
||||
|
||||
def js(self, key: str, **kwargs: object) -> str:
|
||||
text = self._js.get(key)
|
||||
if text is None:
|
||||
text = JS_STRINGS[DEFAULT_LANG].get(key, key)
|
||||
if not kwargs:
|
||||
return text
|
||||
return text.format(**kwargs)
|
||||
|
||||
def js_all(self) -> dict[str, str]:
|
||||
"""整份脚本文案,供页面传给 site.js。"""
|
||||
return dict(self._js)
|
||||
|
||||
|
||||
def translator(lang: str) -> Translator:
|
||||
return Translator(lang)
|
||||
|
||||
|
||||
def missing_keys() -> list[str]:
|
||||
"""检查各语言的文案是否齐全,返回缺失的键。
|
||||
|
||||
构建前跑一次,缺哪边就补哪边——比上线后才发现强。
|
||||
"""
|
||||
problems: list[str] = []
|
||||
reference = set(STRINGS[DEFAULT_LANG])
|
||||
for lang, strings in STRINGS.items():
|
||||
for key in sorted(reference - set(strings)):
|
||||
problems.append(f"{lang} 缺少文案 {key}")
|
||||
for key in sorted(set(strings) - reference):
|
||||
problems.append(f"{lang} 多出文案 {key}")
|
||||
js_reference = set(JS_STRINGS[DEFAULT_LANG])
|
||||
for lang, strings in JS_STRINGS.items():
|
||||
for key in sorted(js_reference - set(strings)):
|
||||
problems.append(f"{lang} 缺少脚本文案 {key}")
|
||||
for key in sorted(set(strings) - js_reference):
|
||||
problems.append(f"{lang} 多出脚本文案 {key}")
|
||||
return problems
|
||||
Reference in New Issue
Block a user