Translating refrakt
Because framework chrome is identical on every site, refrakt ships first-party translations so a site owner just sets locale and gets a localized UI with zero dictionary authoring. Languages are added by demand — contributing one is a three-step round trip.
1. Extract the keys
refrakt i18n extract emits every derivable key with its English default as a JSON dictionary — the exact shape a translation file uses:
# Print the full key set (core + all installed plugins)
refrakt i18n extract
# Write it to a file to start a new bundle
refrakt i18n extract -o packages/runes/i18n/en.json
The same command is exposed over MCP as refrakt.i18n_extract for agent-assisted translation.
2. Translate the bundle
Bundles live in-package, one JSON file per locale (never a separate per-language npm package):
- Core strings (
core.*,layout.*,behavior.*) →packages/runes/i18n/<locale>.json - Each plugin's strings (
<plugin>.*) →plugins/<name>/i18n/<locale>.json
Copy en.json to <locale>.json and translate the values. Keys are language- independent — never translate or reorder them. Values are a string, or a CLDR plural-category map ({ "one": "…", "other": "…" }) for count-bearing strings.
// packages/runes/i18n/de.json
{
"core.hint.warning": "Warnung",
"core.toc.title": "Auf dieser Seite",
"behavior.search.noResults": "Keine Ergebnisse gefunden."
}
Partial bundles are safe: any key you omit falls back to English per key, so you can ship an incomplete translation and fill it in over time.
3. Check coverage
--check compares a bundle against the derived key set, printing per-locale coverage and failing on missing or orphaned keys (a key renamed out from under the bundle):
refrakt i18n extract --check --locale packages/runes/i18n/de.json
# de: 94% (68/72)
# missing 4 key(s): …
Use it in CI to catch drift when a new labelled field lands without a translation.
The "supported" bar
Partial coverage is safe (English fallback), but a language is only advertised as supported once its bundle has been reviewed by a native speaker — a bad machine translation under the refrakt name is worse than English. Coverage is the measured signal; native review is the quality gate.
How bundles are built
The JSON files are the source of truth. A small generate step (scripts/generate-translations.mjs in packages/runes) bakes them into a typed TS module so the runtime never loads JSON at import time; only the active locale is selected at build time by assembleThemeConfig. Plugins embed their bundle the same way via Plugin.translations. en.json is the English reference (already the baked-in default), so it is not emitted into the runtime map.