Files
py.guanjihuan.com/website/i18n.py
2026-11-02 17:00:28 +08:00

295 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""站点界面的中英文文案。
只有「网站框架」的部分需要翻译:导航、按钮、页面标题、页脚统计等等。
文档正文(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