3 min read
pyssg.plugins.i18n
Internationalisation (i18n) plugin: directory-based, route-driven locales.
The locale of a document is its top-level content directory: content/en/...
and content/vi/... are the English and Vietnamese variants of the same site.
The default locale is served at the site root (its directory segment is
stripped from the URL); every other locale keeps its segment as a URL prefix::
content/en/guide/intro.md -> /guide/intro/ (en = default)
content/vi/guide/intro.md -> /vi/guide/intro/
When i18n is enabled the only content the engine emits is content that lives
under a recognised locale directory. A file or folder whose first path segment
is not a configured locale (content/about.md, content/blog/...) produces
no page: the route tap returns "" and the permalink generator skips it.
There is deliberately no way to override a document's locale from frontmatter --
the directory is the single source of truth, which keeps the feature free of
edge cases.
Pure: the locale, the translation key and the routed URL are all functions of
the source path plus the static config, never of a clock or randomness, so two
builds of the same tree are byte-identical. The locale config is folded into
cache_version so changing locales / default_locale busts the cache.
This is a built-in plugin and uses the standard library only.
locale_of(source_path: str, locales: frozenset[str]) -> str | None
Locale of a document, or None if it is not under a locale directory.
The locale is the first path segment when that segment is a configured locale; otherwise the document lives outside the i18n tree and is dropped.
translation_key(source_path: str, locales: frozenset[str]) -> str
Path identifying a document across locales (the part after the locale dir).
en/guide/intro.md and vi/guide/intro.md share the key
guide/intro.md. A path outside any locale directory is returned verbatim.
route_url(url: str, source_path: str, locales: frozenset[str], default: str) -> str
Apply the locale routing rule to a computed URL.
Returns "" when the document is not under a locale directory (the page is
then suppressed). For the default locale the leading /{default}/ segment
is stripped (so it is served at the root); other locales keep their segment.
build_i18n_data(build: Build, default_locale: str, locales: frozenset[str]) -> None
Group pages by translation key and stash the result on site_data.
Runs at evaluate_collections (once every page has its final routed URL).
Pages sharing a translation key (same path under different locale dirs) are
cross-linked. The shape written is::
site_data["i18n"] = {
"default_locale": "en",
"languages": ["en", "vi"], # all configured, sorted
"by_url": {
"/guide/intro/": { # the English (default) page
"lang": "en",
"translations": [{"lang": "vi", "url": "/vi/guide/intro/", "title": ...}],
},
...
},
}
translations lists only the other locales that actually have the page,
so a language switcher links to real translations and never to a 404
(untranslated pages are simply absent).
discover_locales(*dirs: Path | None) -> frozenset[str]
Locale codes for which a <lang>.toml table exists in any of dirs.
UI-string tables are discovered from disk rather than tied to the i18n routing plugin's configured locales, so a single-language site (no i18n plugin) still gets its theme's strings. The result is sorted-deterministic via the set; callers that need order should sort.
load_strings(theme_i18n_dir: Path | None, site_i18n_dir: Path | None, locales: frozenset[str]) -> dict[str, dict[str, str]]
Load and merge UI-string translation tables for the given locales.
For each locale <lang> two optional files are read and merged, the site's
table overriding the theme's per key::
<theme>/i18n/<lang>.toml (theme default strings)
<site>/i18n/<lang>.toml (site override)
Tables are flattened to dotted keys. Locales with no table on disk are simply absent from the result. Pure given its path inputs: reading the same files twice yields the same mapping, so it preserves the build-twice invariant; the render cache folds a digest of these strings so editing a table busts it.
class I18nPlugin
Routes documents by their top-level locale directory.
I18nPlugin.__init__(self, default_locale: str, locales: Sequence[str]) -> None
I18nPlugin.apply(self, builder: Builder) -> None
i18n(default_locale: str, locales: Sequence[str]) -> I18nPlugin
Factory used in pyssg.config.py.