Localization
Content localization for Riebeckite Markdown. It detects a locale and a separate translation identity for every note, prefixes non-default URLs, contributes a standard language switcher, and adds hreflang links. It does not translate Riebeckite UI strings.
Basic setup
import { defineConfig } from "@riebeckite/core";
import { l10n } from "@riebeckite/plugin-l10n";
export default defineConfig({
plugins: [l10n({ defaultLang: "ja", languages: ["ja", "en", "zh-CN"] })],
});
Locale detection
Use README.ja.md-style filenames with a dot as the recommended form, matching this repository's convention. The plugin also accepts README-ja.md, README_ja.md, and configured BCP 47-style tags such as README.en-US.md and README.zh-CN.md.
Directory detection is not supported. Only filename suffixes and frontmatter are used to infer a language. Frontmatter works anywhere:
---
lang: en
translation: getting-started
---
Conventions may be mixed. Built-in precedence is frontmatter > custom detector > filename > default language. Conflicts emit L10N_LANGUAGE_CONFLICT; use strict: true to fail the build instead. A duplicate translation + lang emits L10N_DUPLICATE_TRANSLATION (and also fails in strict mode).
translation is independent of locale and filename. Use it to group files with unrelated paths or names:
日本語/はじめに.md lang: ja, translation: getting-started
English/getting-started.md lang: en, translation: getting-started
URLs and missing translations
The default language keeps Core's resolved URL (/about); other languages are prefixed (/en/about). The plugin augments the existing content-location resolver, so it works with other URL plugins rather than creating a router. Different source paths or a permalink plugin can provide different resolved URLs for translations.
No fallback pages are generated. getLocalization(manifest, slug) returns availableLanguages and language-to-href translations only for notes that exist; getLocalizedContent(manifest, slug, lang) returns null for a missing translation.
Standard LanguageSwitcher
l10n(...) publishes the built-in, styled LanguageSwitcher in the standard article.metadata layout slot. A standard Site consumer renders that slot, so no l10n-specific article integration is needed. It is omitted when a page has fewer than two real translations.
// Keep URLs and metadata but do not publish UI.
l10n({ defaultLang: "ja", languages: ["ja", "en"], ui: false });
// Use another standard slot, or replace the server-rendered component.
l10n({
defaultLang: "ja",
languages: ["ja", "en"],
ui: {
slot: "article.footer",
render: ({ localization }) => `<p>${localization.lang}</p>`,
},
});
Themes can override the default component through its .l10n-switcher CSS classes. ui.render is framework-neutral HTML so custom Sites can supply their own server-rendered component without coupling the plugin to a specific app.
Custom detector
For another convention, provide detect. Its language is considered after frontmatter and before filename; its translationId is explicit.
l10n({
defaultLang: "ja",
languages: ["ja", "en", "fr"],
detect: ({ path }) =>
path.startsWith("French/") ? { lang: "fr", translationId: "hello" } : undefined,
});
Links and SEO
Before the existing Content Graph is built, WikiLink graph targets are switched to the source note's language when that translation exists; otherwise their original target remains. The rendered article rewrites both Obsidian WikiLinks (after @riebeckite/plugin-obsidian-markdown resolves them) and Markdown links to an existing translation in the current page's language, preserving query strings and fragments. When no translation exists, the original/default target remains linked. External, fragment-only, unknown, asset, and other non-content URLs remain unchanged. The Content Graph and rendered links use the same resolution policy; no second graph is created.
Each translated entry receives one <link rel="alternate" hreflang="…"> per existing translation through Core's headTags extension point. The site shell remains responsible for rendering those tags and for selecting <html lang> for a request.