Translations (i18n)
Localize the docx-editor UI into any language. i18n support with community-contributed locales and CLI tools to add your own. Contribute new translations.
Overview
@eigenpal/docx-js-editor ships with built-in internationalization that supports any language. Every toolbar label, tooltip, context menu item, dialog, and UI string can be translated.
All locale files live in packages/react/i18n/ as JSON. English (en.json) is the source of truth — community-contributed locales mirror its structure and use null for untranslated keys (which fall back to English automatically).
Community contributions for new locales are welcome — see Contributing a new locale below.
Quick start
Import a locale JSON file and pass it via the i18n prop:
import { DocxEditor } from "@eigenpal/docx-js-editor";
import de from "@eigenpal/docx-js-editor/i18n/de.json";
import "@eigenpal/docx-js-editor/styles.css";
function App() {
return (
<DocxEditor
documentBuffer={buffer}
i18n={de}
showToolbar
/>
);
}That's it — all toolbar buttons, tooltips, dialogs, and status messages now appear in German.
Available locales
| Code | Language | Import path |
|---|---|---|
en | English (default) | Built-in, no import needed |
pl | Polish (Polski) | @eigenpal/docx-js-editor/i18n/pl.json |
de | German (Deutsch) | @eigenpal/docx-js-editor/i18n/de.json |
Dynamic locale switching
Wrap the locale import in state to let users choose their language at runtime:
import { useState } from "react";
import { DocxEditor } from "@eigenpal/docx-js-editor";
import pl from "@eigenpal/docx-js-editor/i18n/pl.json";
import de from "@eigenpal/docx-js-editor/i18n/de.json";
import "@eigenpal/docx-js-editor/styles.css";
const locales = {
en: undefined, // built-in default
pl,
de,
};
function App() {
const [lang, setLang] = useState<"en" | "pl" | "de">("en");
return (
<>
<select value={lang} onChange={(e) => setLang(e.target.value as "en" | "pl" | "de")}>
<option value="en">English</option>
<option value="pl">Polski</option>
<option value="de">Deutsch</option>
</select>
<DocxEditor
documentBuffer={buffer}
i18n={locales[lang]}
showToolbar
/>
</>
);
}Only the imported locale gets bundled — tree-shaking keeps your bundle lean.
Key states in locale files
Every key in a community locale file can be in one of three states:
| State | Value | Behavior |
|---|---|---|
| Translated | "Pogrubienie" | Displayed to user |
| Not yet translated | null | Falls back to English |
| Missing | (key absent) | CI fails — must be added as null |
Setting a key to null means "this key exists but hasn't been translated yet." The editor shows the English string as a fallback, and translators can find untranslated strings by searching for null:
{
"toolbar": {
"bold": "Pogrubienie",
"italic": null,
"underline": null
}
}Here, "Bold" shows as "Pogrubienie", while "Italic" and "Underline" display in English until translated.
Interpolation and pluralization
Interpolation
Insert dynamic values with {variable} placeholders:
"greeting": "Hello {name}!"Cardinal pluralization
The system uses ICU MessageFormat syntax for plural forms:
"followers": "You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}."=0,=1,=2— exact matches (checked first)one,few,many,other— CLDR plural categories (language-dependent)#— replaced with the actual number
Each locale file includes a _lang field that tells Intl.PluralRules which rules to apply. Polish, for example, has one/few/many/other:
"followers": "{count, plural, =0 {brak obserwujących} one {# obserwujący} few {# obserwujących} many {# obserwujących} other {# obserwujących}}"Contributing a new locale
Want to add a new language to the editor? The project includes CLI tools that make it straightforward.
1. Scaffold a new locale
bun run i18n:new de # German
bun run i18n:new pt-BR # Brazilian Portuguese
bun run i18n:new zh-Hans # Simplified ChineseThis creates packages/react/i18n/<lang>.json with all keys set to null, mirroring the structure of en.json. Use a BCP 47 language tag.
2. Translate strings
Open the generated file and replace null values with translations. You don't need to translate everything at once — partial translations are welcome. Untranslated keys (null) fall back to English.
Tips:
- Keep
{variable}placeholders as-is — they get replaced at runtime - Keyboard shortcuts (Ctrl+B, Ctrl+Z) should keep the key part unchanged
- Font names (Arial, Calibri) and typography terms (Sans Serif, Monospace) are typically not translated
- Page size identifiers (Letter, A4) keep the standard name
3. Check your progress
bun run i18n:statusSource: en.json (483 keys)
Locale Translated Untranslated Coverage
----------------------------------------------
de 210 273 43% ████████░░░░░░░░░░░░
4. Validate
bun run i18n:validate # check all locale files are in sync
bun run i18n:fix # auto-repair if needed5. Open a PR
Include your coverage in the PR description (e.g., "German: 100% translated" or "Japanese: toolbar + dialogs translated, errors section still null").
See the full contribution guide at docs/i18n.md on GitHub.
Adding new English strings (for contributors)
When adding a new feature with user-facing text:
- Add to
en.json— nest by feature area:
{
"dialogs": {
"myNewDialog": {
"title": "My Dialog",
"description": "Some description"
}
}
}- Use in component with the
useTranslation()hook:
const { t } = useTranslation();
<h2>{t('dialogs.myNewDialog.title')}</h2>Types update automatically — t() will autocomplete your new key.
- Sync locale files:
bun run i18n:fixThis adds your new keys as null in all community locale files. CI will fail if you skip this step.
Key naming conventions
- Nest by feature area:
toolbar.*,dialogs.findReplace.*,comments.* - Use camelCase for keys:
matchCount,insertRowAbove - Shared strings go in
common.*: Cancel, Insert, Apply, Close, Delete - Don't duplicate — if a string exists in
common.*, use it
CLI reference
| Command | Description |
|---|---|
bun run i18n:new <lang> | Scaffold a new locale file with all keys set to null |
bun run i18n:status | Show translation coverage for all locales |
bun run i18n:validate | Check all locale files are in sync with en.json |
bun run i18n:fix | Auto-repair: add missing keys as null, remove extras |
Architecture
packages/react/i18n/en.json → Source of truth (all strings)
packages/react/src/i18n/types.ts → Auto-derived types from en.json
packages/react/src/i18n/LocaleContext.tsx → React Context + useTranslation hook
packages/react/src/i18n/index.ts → Barrel exports
scripts/validate-i18n.mjs → CI/pre-commit validation
- Zero runtime dependencies — uses React Context only
- Type-safe —
t()accepts only valid dot-notation keys, autocomplete works - Tree-shakeable — only the imported locale gets bundled
- Null = untranslated — deep merge skips nulls, falls back to English
Limitations
- No RTL layout — string translation works, but the editor UI layout is not RTL-ready. RTL support is a separate effort.
Next steps
- Polish translation blog post — deep dive into the Polish locale
- German translation blog post — deep dive into the German locale
- Full i18n contribution guide on GitHub — upstream source of truth
- Props Reference — full list of configuration props
- Toolbar Customization — customize toolbar layout and actions