Configuration

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

CodeLanguageImport path
enEnglish (default)Built-in, no import needed
plPolish (Polski)@eigenpal/docx-js-editor/i18n/pl.json
deGerman (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:

StateValueBehavior
Translated"Pogrubienie"Displayed to user
Not yet translatednullFalls 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 Chinese

This 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:status
Source: 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 needed

5. 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:

  1. Add to en.json — nest by feature area:
{
  "dialogs": {
    "myNewDialog": {
      "title": "My Dialog",
      "description": "Some description"
    }
  }
}
  1. 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.

  1. Sync locale files:
bun run i18n:fix

This 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

CommandDescription
bun run i18n:new <lang>Scaffold a new locale file with all keys set to null
bun run i18n:statusShow translation coverage for all locales
bun run i18n:validateCheck all locale files are in sync with en.json
bun run i18n:fixAuto-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-safet() 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