Repository navigation
Conversation
The editor language was only settable via a static `lang:` in `bolt-redactor.yaml`, so on a multi-locale Bolt install every admin saw the editor in the same hard-coded language regardless of the locale they'd chosen for the backend. Bolt already resolves a per-user backend locale (LocaleSubscriber sets the request locale from the user's `_backend_locale`); the editor just never consulted it. - RedactorConfig now sets `lang` to the current request locale (RequestStack) after the config merge, so the toolbar always follows each user's Bolt locale. It is intentionally not overridable — the language is driven solely by the user's Bolt admin locale. - redactor_includes() auto-loads the matching `assets/redactor/langs/<code>.js` for the resolved locale, so users no longer add it to `includes` manually. English is built in; locales without a shipped translation are skipped and Redactor falls back to English on its own. - Document the behaviour in the README.
The formattingAdd 'small' entry used a hard-coded 'Small' title, so it showed English in every locale. Switch it to the '## small ##' i18n placeholder and add a 'small' entry to every langs/<locale>.js. Also always load langs/en.js (previously skipped for English), so its normalized table overrides redactor.min.js' built-in 'en' and provides the new 'small' label.
resolveLocale() set lang to the raw request locale even when no matching langs/<code>.js is shipped, while redactor_includes() skipped the missing file. This leaves the config pointing at a language table that was never loaded, which can leave the toolbar unlocalized or broken. Resolve the locale against the actually-shipped language file so lang and the loaded file always agree, falling back to English otherwise.
Audit of assets/redactor/langs against the key set actually used by redactor.min.js and the bundled plugins. The English file and the file structures were already correct (identical key order, comments and indentation everywhere); the values needed work. Mistranslations, e.g.: hu unorderedlist "Rendezett lista" (ordered) -> "Rendezetlen lista" hu undo "Kibont" (unfold) -> "Visszavonás" da deleted "Slettet" (deleted) -> "Gennemstreget" fi deleted "Poistettu" (deleted) -> "Yliviivaus" sv indent/outdent "utdrag" (excerpt) -> "indrag" hr horizontalrule "Redak" (row of text) -> "Crta" hu/fa/pl bulletslist "Golyók"/"گلولهها"/"Kule" (ammunition) it file "Documento" -> "File" fr image "Visuel" -> "Image" no lists "Punkt" -> "Lister" es quote "Citar" (verb) -> "Cita" Rendering bug: fi filename was "Nimi (valinnainen)", which rendered as "Nimi (valinnainen) (valinnainen)" because Redactor appends the separate `optional` key in parentheses. Also fixed: "window" instead of "tab" in link-in-new-tab (sk, sv, tr, ru); ungrammatical strings (hu upload-label, pt_br insert-row-above/below); spelling (de Hintergrundsfarbe, de Schriftgrösse, tr editorü, fr Editeur, pt_br Subtitulo); broken accessibility-help-label screen-reader text in nine locales (nl "RTF editor", hr, no, cs, da, pt_br, tr, pl, it); title/heading and cancel/undo collisions; and per-file inconsistencies in link terminology, capitalization, verb mood and *-abbr formatting. ar, en, ja and ko needed no changes. File modes normalized to 644.
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Locale normalization, English base loading, and asset synchronization remain unresolved.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Localizes the Redactor editor using Bolt’s backend locale and expands bundled translation coverage.
Changes:
- Resolves and loads locale-specific Redactor language files.
- Localizes format and plugin labels.
- Normalizes bundled translations and documents locale behavior.
| File | Description |
|---|---|
src/TwigExtension.php |
Generates locale script includes. |
src/RedactorConfig.php |
Resolves the active locale. |
README.md |
Documents localization behavior. |
config/services.yaml |
Configures required dependencies. |
config/config.yaml |
Localizes the small format label. |
assets/redactor/plugins/properties/properties.min.js |
Localizes property labels. |
assets/redactor/plugins/inlinestyle/inlinestyle.min.js |
Localizes inline-style labels. |
assets/redactor/langs/zh_tw.js |
Updates Traditional Chinese translations. |
assets/redactor/langs/zh_cn.js |
Updates Simplified Chinese translations. |
assets/redactor/langs/tr.js |
Updates Turkish translations. |
assets/redactor/langs/sv.js |
Updates Swedish translations. |
assets/redactor/langs/sl.js |
Updates Slovenian translations. |
assets/redactor/langs/sk.js |
Updates Slovak translations. |
assets/redactor/langs/ru.js |
Updates Russian translations. |
assets/redactor/langs/ro.js |
Updates Romanian translations. |
assets/redactor/langs/pt_br.js |
Updates Brazilian Portuguese translations. |
assets/redactor/langs/pl.js |
Updates Polish translations. |
assets/redactor/langs/no.js |
Updates Norwegian translations. |
assets/redactor/langs/nl.js |
Updates Dutch translations. |
assets/redactor/langs/ko.js |
Updates Korean translations. |
assets/redactor/langs/ja.js |
Updates Japanese translations. |
assets/redactor/langs/it.js |
Updates Italian translations. |
assets/redactor/langs/hu.js |
Updates Hungarian translations. |
assets/redactor/langs/hr.js |
Updates Croatian translations. |
assets/redactor/langs/he.js |
Updates Hebrew translations. |
assets/redactor/langs/fr.js |
Updates French translations. |
assets/redactor/langs/fi.js |
Updates Finnish translations. |
assets/redactor/langs/fa.js |
Updates Persian translations. |
assets/redactor/langs/es.js |
Updates Spanish translations. |
assets/redactor/langs/en.js |
Normalizes English translations. |
assets/redactor/langs/de.js |
Updates German translations. |
assets/redactor/langs/da.js |
Updates Danish translations. |
assets/redactor/langs/cs.js |
Updates Czech translations. |
assets/redactor/langs/ar.js |
Updates Arabic translations. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| $locale = $request?->getLocale() ?: 'en'; | ||
|
|
||
| return $this->hasLangFile($locale) ? $locale : 'en'; |
There was a problem hiding this comment.
This comment is valid, but needs to be solved in the TwigExtension instead.
bobvandevijver
left a comment
There was a problem hiding this comment.
Couple of things need to be adjusted, as well as the styling issues.
| $locale = $request?->getLocale() ?: 'en'; | ||
|
|
||
| return $this->hasLangFile($locale) ? $locale : 'en'; |
There was a problem hiding this comment.
This comment is valid, but needs to be solved in the TwigExtension instead.
The request locale was matched against langs/<code>.js verbatim, but the shipped regional files are lowercase with an underscore (pt_br, zh_cn, zh_tw) while Bolt uses pt_BR / zh_CN. Those users silently got English, and on a case-insensitive filesystem the config could even point at `pt_BR`, a key the language file never registers. Locale resolution now lives in TwigExtension, which is where the editor settings and the language include are rendered, instead of RedactorConfig: - Normalize the request locale (lowercase, `-` -> `_`), then match it exactly, then by its bare language code (de_AT -> de), else English. - redactor_settings() and redactor_includes() both use that one result, so `lang` and the loaded file always agree. - Only plain locale codes are looked up on disk. - RedactorConfig, 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.
redactor_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 `lang`. 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.
|
All should be addressed + adding a video test for switching languages + selecting non-existing translation with fallback to en redactor-test.mov |

Summary
This MR makes the Redactor editor UI follow the current Bolt backend locale and normalizes bundled Redactor translation files so plugin and format labels can be localized consistently.
Changes
langoption from the current request locale resolved by Bolt.assets/redactor/langs/<code>.jsfile fromredactor_includes().langs/en.jstoo, so its normalized table overrides the built-in English shipped inredactor.min.js; unsupported locales still fall back to Redactor's built-in English when no file exists.smallformat label: switch theformattingAddentry from a hard-coded'Small'title to the## small ##i18n placeholder and add asmallkey to every bundledlangs/<code>.js(previously it read "Small" in every locale, and empty under English).inlinestyleandpropertiesplugins to read more labels through Redactor's language system.Testing
bolt-cms-redactor-localized.mp4