Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ build:
os: ubuntu-24.04
tools:
python: "3.10"
jobs:
# Run after .[doc] is installed so babel/sphinx-intl are available.
post_install:
- cd doc && python build_i18n.py

python:
install:
- method: pip
path: .
extra_requirements:
- doc

submodules:
include: all
recursive: true
18 changes: 16 additions & 2 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,24 @@ I18NSPHINXLANGS = -l zh_CN
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile html_zh_cn html_ja_jp gettext
.PHONY: help Makefile gettext build-mo html_i18n html_zh_cn html_de

# Usage:
# make html_i18n LOCALE=de
# make html_de
# make html_zh_cn
html_i18n:
python build_i18n.py $(LOCALE)
$(SPHINXBUILD) -b html $(SPHINXOPTS) $(if $(filter zh_CN,$(LOCALE)),-t zh_cn,) -D language='$(LOCALE)' "$(SOURCEDIR)" $(BUILDDIR)/html_$(LOCALE)

html_de:
$(MAKE) html_i18n LOCALE=de

html_zh_cn:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) -t zh_cn -D language='zh_CN' "$(SOURCEDIR)" $(BUILDDIR)/html_zh_cn
$(MAKE) html_i18n LOCALE=zh_CN

build-mo:
python build_i18n.py --all

gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
Expand Down
117 changes: 117 additions & 0 deletions doc/build_i18n.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Compile .po catalogs to .mo before Sphinx html build (RTD + local)."""

from __future__ import annotations

import argparse
import subprocess
import sys
from pathlib import Path

from i18n_locales import KNOWN_LOCALES, resolve_sphinx_language
Comment thread
kejhz653-stack marked this conversation as resolved.

try:
import babel # noqa: F401

HAS_BABEL = True
except ImportError:
HAS_BABEL = False

DOC_DIR = Path(__file__).resolve().parent
LOCALE_DIR = DOC_DIR / "source" / "locale"


def _messages_dir(locale: str) -> Path:
return LOCALE_DIR / locale / "LC_MESSAGES"


def _compile_po_file(po: Path, locale: str) -> None:
from babel.messages.mofile import write_mo
from babel.messages.pofile import read_po

with po.open("rb") as f:
catalog = read_po(f, locale=locale)
mo = po.with_suffix(".mo")
with mo.open("wb") as f:
write_mo(f, catalog)


def build_mo(locale: str) -> bool:
po_root = _messages_dir(locale)
if not po_root.is_dir():
print(f"[build_i18n] skip {locale}: missing {po_root}", flush=True)
return False
po_files = sorted(po_root.rglob("*.po"))
if not po_files:
print(f"[build_i18n] skip {locale}: no .po files under {po_root}", flush=True)
return False

total = len(po_files)
print(f"[build_i18n] compiling {total} catalogs for locale/{locale} ...", flush=True)
for index, po in enumerate(po_files, start=1):
rel = po.relative_to(po_root)
print(f"[build_i18n] [{index}/{total}] {rel}", flush=True)
try:
_compile_po_file(po, locale)
except Exception as exc:
print(f"[build_i18n] failed on {rel}: {exc}", flush=True)
raise
print(f"[build_i18n] done locale/{locale}", flush=True)
return True


def _build_mo_with_sphinx_intl(locale: str) -> bool:
print(f"[build_i18n] compiling locale/{locale} via sphinx-intl ...", flush=True)
subprocess.run(
["sphinx-intl", "build", "-l", locale, "-d", str(LOCALE_DIR)],
cwd=DOC_DIR,
check=True,
)
print(f"[build_i18n] done locale/{locale}", flush=True)
return True


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"locale",
nargs="?",
help="Sphinx locale folder name (e.g. de, zh_CN). Defaults to READTHEDOCS_LANGUAGE.",
)
parser.add_argument(
"--all",
action="store_true",
help="Compile every known locale directory that exists under source/locale/.",
)
args = parser.parse_args(argv)

if args.all:
if HAS_BABEL:
built = [loc for loc in KNOWN_LOCALES if build_mo(loc)]
if not built:
print("[build_i18n] no locale directories found")
else:
print(
"[build_i18n] babel not installed, falling back to sphinx-intl "
"for all locales...",
flush=True,
)
for loc in KNOWN_LOCALES:
if _messages_dir(loc).is_dir():
_build_mo_with_sphinx_intl(loc)
return 0

locale = resolve_sphinx_language(explicit=args.locale)
if not locale:
print("[build_i18n] English build; skipping mo compilation")
return 0

if HAS_BABEL:
build_mo(locale)
else:
_build_mo_with_sphinx_intl(locale)
return 0
Comment thread
kejhz653-stack marked this conversation as resolved.


if __name__ == "__main__":
sys.exit(main())
37 changes: 37 additions & 0 deletions doc/i18n_locales.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Shared RTD / Sphinx locale mapping for documentation i18n."""

from __future__ import annotations

import os

# RTD READTHEDOCS_LANGUAGE slug (lower case) -> doc/source/locale/ directory name
RTD_TO_SPHINX_LOCALE: dict[str, str] = {
"de": "de",
"es": "es",
"fr": "fr",
"it": "it",
"ja": "ja",
"ko": "ko",
"pt-br": "pt_BR",
"pt_br": "pt_BR",
"zh-cn": "zh_CN",
"zh_cn": "zh_CN",
"zh-tw": "zh_TW",
"zh_tw": "zh_TW",
}

KNOWN_LOCALES: tuple[str, ...] = tuple(sorted(set(RTD_TO_SPHINX_LOCALE.values())))


def resolve_sphinx_language(
rtd_language: str | None = None,
*,
explicit: str | None = None,
) -> str | None:
"""Return Sphinx ``language`` config value, or ``None`` for English."""
slug = (
explicit or rtd_language or os.environ.get("READTHEDOCS_LANGUAGE") or ""
).strip().lower()
if not slug or slug == "en" or slug.startswith(("en-", "en_")):
return None
return RTD_TO_SPHINX_LOCALE.get(slug, slug.replace("-", "_"))
52 changes: 46 additions & 6 deletions doc/source/_static/switcher.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,53 @@
[
{
"name": "简体中文(Chinese)",
"version": "zh-cn",
"url": "https://inference.readthedocs.io/zh-cn/latest/"
},
{
"name": "English",
"version": "en",
"url": "https://inference.readthedocs.io/en/latest/",
"preferred": true
},
{
"name": "简体中文 (Chinese)",
"version": "zh-cn",
"url": "https://inference.readthedocs.io/zh-cn/latest/"
},
{
"name": "繁體中文 (Traditional Chinese)",
"version": "zh-tw",
"url": "https://inference.readthedocs.io/zh-tw/latest/"
},
{
"name": "日本語 (Japanese)",
"version": "ja",
"url": "https://inference.readthedocs.io/ja/latest/"
},
{
"name": "한국어 (Korean)",
"version": "ko",
"url": "https://inference.readthedocs.io/ko/latest/"
},
{
"name": "Deutsch (German)",
"version": "de",
"url": "https://inference.readthedocs.io/de/latest/"
},
{
"name": "Français (French)",
"version": "fr",
"url": "https://inference.readthedocs.io/fr/latest/"
},
{
"name": "Español (Spanish)",
"version": "es",
"url": "https://inference.readthedocs.io/es/latest/"
},
{
"name": "Italiano (Italian)",
"version": "it",
"url": "https://inference.readthedocs.io/it/latest/"
},
{
"name": "Português (Portuguese)",
"version": "pt-br",
"url": "https://inference.readthedocs.io/pt-br/latest/"
}
]
]
Loading
Loading