Repository navigation
Conversation
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.
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.
Vondry
force-pushed
the
feature/reactive-editor-locale
branch
from
September 9, 2026 08:55
75b9202 to
29b61b1
Compare
There was a problem hiding this comment.
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
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.
bobvandevijver
requested changes
Sep 26, 2026
bobvandevijver
left a comment
Member
There was a problem hiding this comment.
Lot's of similarities with bolt/redactor#41, so comments made there can also apply here.
The request locale was matched against langs/<code>.js verbatim, so a Bolt locale such as pt_BR or zh-CN never matched a lowercase, underscore-named file and the editor fell back to English. Because the editor does not fall back on its own, `editor.lang` and the loaded file must always agree. Locale resolution now lives in TwigExtension, which is where the editor settings and the language include are rendered, instead of ArticleConfig: - Normalize the request locale (lowercase, `-` -> `_`), then match it exactly, then by its bare language code (de_AT -> de), else English. - article_settings() and article_includes() both use that one result, so `editor.lang` and the loaded file always agree. - Only plain locale codes are looked up on disk. - ArticleConfig, also used by the upload and image controllers, is back to request-independent config; its RequestStack and path arguments and the services.yaml entry are removed. - README: name new language files lowercase with an underscore.
article_includes() re-checked that the language file exists right after resolveLocale() had already established it. resolveLocale() is now the single existence check: it always returns a shipped file (the matched locale, or en.js), so the include is emitted as is.
The comment suggested langs/en.js is always loaded, but only the file for the resolved locale is. Say so, and that langs/en.js is that file when the locale resolves to English, including the fallback.
Use Path::join for the language file path and guard against directory traversal by requiring the resolved file to stay inside the langs directory (Path::isBasePath), replacing the locale-format regex. The langs directory is defined once in a LANGS_DIR constant.
Apply the ECS MethodChainingNewlineFixer and MethodChainingIndentationFixer fixes that fail the Coding Standard CI job (easy-coding-standard 13.3.2). Whitespace only: the touched files are unchanged apart from line breaks and indentation.
Bolt's LocaleSubscriber takes any `?_locale=` value, even in the backend. A value such as `x/../cs` stays inside the langs directory, so it passed the containment check: cs.js was loaded while the raw string became `editor.lang`, an unloaded table, leaving the toolbar empty. Also require the locale to equal the file's name, so the resolved locale is always a shipped file's code and anything with path segments falls back to English.
Plugins deep-merge their `translations.en` onto ArticleEditor.lang.en, not lang.<code>. The comment now says that, and why en.js must load before the plugins: language files assign their table outright, so a later en.js would wipe the plugin strings. The language file headers still named articleLangInclude, which no longer exists; they now point at articleIncludes.
Author
|
All should be addressed + adding a video test for switching languages + selecting non-existing translation with fallback to en article-test.mp4 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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
ArticleConfigsetseditor.langto the current request locale after the config merge, so the toolbar and popups always follow each user's Bolt admin locale (viaLocaleSubscriber/_backend_locale). This is intentionally not overridable.article_includes()loads the matchingassets/article/langs/<code>.jsbefore the plugin scripts, so each plugin's owntranslations.endeep-merges on top and the English fallback stays complete for every enabled plugin.editor.langand the loaded file always agree. Unknown or unshipped locales fall back to the built-in English instead of rendering an empty toolbar.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>.jsand translate. Missing keys fall back to English automatically.Also
config.yaml.Testing
bolt-cms-article-localized.mp4