This commit is contained in:
2026-11-02 17:00:28 +08:00
parent 5d0f629439
commit 9ed39b51f7
43 changed files with 7163 additions and 151 deletions

294
website/i18n.py Normal file
View 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