Skip to content

Localize Article editor UI based on Bolt backend locale - #49

Open
Vondry wants to merge 5 commits into
bolt:mainfrom
Vondry:feature/reactive-editor-locale
Open

Vondry wants to merge 5 commits into
bolt:mainfrom
Vondry:feature/reactive-editor-locale

Conversation

@Vondry

@Vondry Vondry commented Jul 3, 2026

Copy link
Copy Markdown

Localize the Article editor UI

Adds full localization to the Article rich-text editor. Previously the editor shipped no language files and was English-only, with no way to follow the per-user Bolt backend locale that Bolt already resolves.

What changed

  • Locale-driven editor UI — ArticleConfig sets editor.lang to the current request locale after the config merge, so the toolbar and popups always follow each user's Bolt admin locale (via LocaleSubscriber / _backend_locale). This is intentionally not overridable.
  • Auto-loaded language files — article_includes() loads the matching assets/article/langs/<code>.js before the plugin scripts, so each plugin's own translations.en deep-merges on top and the English fallback stays complete for every enabled plugin.
  • Safe fallback — the locale is resolved against the actually-shipped language file, so editor.lang and the loaded file always agree. Unknown or unshipped locales fall back to the built-in English instead of rendering an empty toolbar.
  • Shipped locales — English (en.js, the canonical editable set) plus Czech, German, Spanish, French, Croatian, Italian, Dutch, Polish, Russian, Slovak, and Slovenian.

Adding a language

Copy langs/en.js → langs/<code>.js and translate. Missing keys fall back to English automatically.

Also

  • Updated the plugin options list in config.yaml.
  • Documented the localization behaviour in the README.

Testing

bolt-cms-article-localized.mp4

Vondry added 4 commits July 2, 2026 18:50
The Article editor had no localization: it shipped no language files and its UI
was English-only, with no way to follow the per-user Bolt backend locale that Bolt
already resolves (LocaleSubscriber sets the request locale from the user's
`_backend_locale`).

- ArticleConfig sets `editor.lang` to the current request locale after the config
  merge, so the toolbar/popups always follow each user's Bolt locale. It is
  intentionally not overridable — driven solely by the user's Bolt admin locale.
- article_includes() auto-loads the matching `assets/article/langs/<code>.js`,
  emitted BEFORE the plugin scripts so each plugin's own `translations.en`
  deep-merges on top and the English fallback stays complete for every enabled
  plugin. Locales without a file fall back to the built-in English.
- Ship `langs/en.js` (the canonical, editable English set) and `langs/cs.js`
  (Czech). Adding a language is copy en.js -> <code>.js and translate; missing
  keys fall back to English.
- Document the behaviour in the README.
resolveLocale() set editor.lang to the raw request locale even when no
matching langs/<code>.js is shipped, while article_includes() skipped the
missing file. The editor does not fall back on its own: an unknown lang
table resolves every toolbar label to undefined, rendering an empty UI.

Resolve the locale against the actually-shipped language file so editor.lang
and the loaded file always agree, falling back to English otherwise.
@Vondry Vondry changed the title Localize the Article editor UI Localize the Article editor UI based on Bolt backend locale Jul 3, 2026
@Vondry Vondry changed the title Localize the Article editor UI based on Bolt backend locale Localize Article editor UI based on Bolt backend locale Jul 20, 2026
Audited all twelve editor language files against the strings the editor
and plugins actually resolve, then fixed what was wrong in the eleven
translated locales. en.js needed no change: its 235 keys and values are
byte-identical to the built-in English set in article-editor.min.js plus
each plugin's own `translations.en`.

Structure
- cs.js was the only locale whose inline comments had been translated;
  restored to English so all eleven files mirror en.js exactly. Key sets
  and key order already matched everywhere.

Grammar
- add-column-after/before (and add-row-below/above in cs, sk) were
  translated word-for-word, leaving dangling prepositions. Reworded
  directionally in cs, sk, pl, ru, hr, sl.

Wrong language
- sl.js carried the Croatian `Neuredivo` for blocks.noneditable.

Wrong meaning for the context
- handle.handle labels the @-mention autocomplete plugin, not a drag
  grip; every locale had translated it as a physical handle.
- selector.selector edits a block's id/class, so cs `Výběr`, sk `Výber`
  and sl `Izbirnik` become `Selektor`.
- ru filelink.title is a link label field, not a heading.
- editor.multiple is the breadcrumb for a multi-block selection; bare
  adjectives read as fragments, so use noun phrases.

Label collisions on one surface
- fr blocks.line vs blocks.row, sl blocks.address vs blocks.heading,
  hr and sl clips vs snippets.

Spanish consistency
- File is es-ES throughout: `Vídeo`, and `Incrustar` for embed so it no
  longer collides with `Insertar`; caption split into `Leyenda` for
  embeds and `Pie de imagen` for images; shorter `Sobresalir` for outset.

Czech and Slovak accessibility.help-label dropped "rich".

Left alone deliberately: fr/it share `Annuler`/`Annulla` for undo and
cancel, which is standard and appears on different surfaces, and
blocks.paragraph/quoteitem share a label upstream because a quote item
is a paragraph.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The locale value requires validation to prevent path traversal, and the merge-target comment needs correction.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

Adds locale-aware Article editor UI localization using Bolt’s backend locale, with English fallback and translated language assets.

Changes:

  • Resolves and applies the active editor locale.
  • Loads language files before plugin scripts.
  • Adds translations, documentation, and configuration updates.
File Summary
src/​TwigExtension.php Loads the matching language asset before plugins.
src/​ArticleConfig.php Resolves and applies the editor locale.
README.md Documents localization behavior.
config/​services.yaml Supplies localization path parameters.
config/​config.yaml Updates plugin option documentation.
assets/​article/​langs/​sl.js Adds Slovenian translations.
assets/​article/​langs/​sk.js Adds Slovak translations.
assets/​article/​langs/​ru.js Adds Russian translations.
assets/​article/​langs/​pl.js Adds Polish translations.
assets/​article/​langs/​nl.js Adds Dutch translations.
assets/​article/​langs/​it.js Adds Italian translations.
assets/​article/​langs/​hr.js Adds Croatian translations.
assets/​article/​langs/​fr.js Adds French translations.
assets/​article/​langs/​es.js Adds Spanish translations.
assets/​article/​langs/​en.js Adds canonical English translations.
assets/​article/​langs/​de.js Adds German translations.
assets/​article/​langs/​cs.js Adds Czech translations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/ArticleConfig.php
Comment on lines +253 to +255
if ($locale === '') {
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid, but the same comment as made on the Redactor one apply. So, no check needed in the Config, but there needs to be a check in the Extension.

Also removes the need of some of the new arguments to this class by the way.

@bobvandevijver bobvandevijver left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lot's of similarities with bolt/redactor#41, so comments made there can also apply here.

Comment thread src/ArticleConfig.php
Comment on lines +253 to +255
if ($locale === '') {
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid, but the same comment as made on the Redactor one apply. So, no check needed in the Config, but there needs to be a check in the Extension.

Also removes the need of some of the new arguments to this class by the way.

Comment thread src/ArticleConfig.php
Comment on lines +61 to +63
if (! isset($this->config['editor']) || ! is_array($this->config['editor'])) {
$this->config['editor'] = [];
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (! isset($this->config['editor']) || ! is_array($this->config['editor'])) {
$this->config['editor'] = [];
}
$this->config['editor'] ??= [];

Comment thread src/TwigExtension.php
return '';
}

return sprintf('<script src="%s"></script>', $relative) . "\n";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return sprintf('<script src="%s"></script>', $relative) . "\n";
return sprintf('<script src="%s"></script>', $relative);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants