Skip to content

Localize Redactor editor UI based on Bolt backend locale - #41

Open
Vondry wants to merge 5 commits into
bolt:mainfrom
Vondry:main
Open

Vondry wants to merge 5 commits into
bolt:mainfrom
Vondry:main

Conversation

@Vondry

@Vondry Vondry commented Jul 2, 2026 •

Copy link
Copy Markdown

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

  • Set Redactor's lang option from the current request locale resolved by Bolt.
  • Auto-load the matching assets/redactor/langs/<code>.js file from redactor_includes().
  • Always load langs/en.js too, so its normalized table overrides the built-in English shipped in redactor.min.js; unsupported locales still fall back to Redactor's built-in English when no file exists.
  • Localize the small format label: switch the formattingAdd entry from a hard-coded 'Small' title to the ## small ## i18n placeholder and add a small key to every bundled langs/<code>.js (previously it read "Small" in every locale, and empty under English).
  • Document the locale behavior in the README.
  • Add missing translation keys across bundled Redactor language files.
  • Update bundled inlinestyle and properties plugins to read more labels through Redactor's language system.

Testing

bolt-cms-redactor-localized.mp4

Vondry added 4 commits July 2, 2026 15:21
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.
@Vondry Vondry changed the title Localize Redactor editor UI from Bolt backend locale Localize Redactor editor UI based on Bolt backend locale Jul 3, 2026
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.

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

Locale normalization, English base loading, and asset synchronization remain unresolved.

Review effort: Lite
Findings: 1 High severity

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.

Comment thread src/RedactorConfig.php
Comment on lines +239 to +241
$locale = $request?->getLocale() ?: 'en';

return $this->hasLangFile($locale) ? $locale : 'en';

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 comment is valid, but needs to be solved in the TwigExtension instead.

@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.

Couple of things need to be adjusted, as well as the styling issues.

Comment thread assets/redactor/plugins/properties/properties.min.js
Comment thread assets/redactor/plugins/inlinestyle/inlinestyle.min.js
Comment thread src/RedactorConfig.php
Comment on lines +244 to +253
private function hasLangFile(string $locale): bool
{
if ($locale === '') {
return false;
}

$path = sprintf('%s/%s/assets/redactor/langs/%s.js', $this->projectDir, $this->publicFolder, $locale);

return is_file($path);
}

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.

There is no need to check for file existence here, as that is also done before the script is included.

Comment thread src/TwigExtension.php
// RedactorConfig::resolveLocale), so the toolbar is localized without the
// user having to add it to `includes` manually. This includes English:
// redactor.min.js ships a built-in `en` table, but langs/en.js overrides it
// with our normalized set (e.g. the `small` format label), so we load it too.

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 comment mentions that the en file is always loaded, but I am not seeing that here.

Comment thread src/RedactorConfig.php
Comment on lines +239 to +241
$locale = $request?->getLocale() ?: 'en';

return $this->hasLangFile($locale) ? $locale : 'en';

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 comment is valid, but needs to be solved in the TwigExtension instead.

Comment thread src/TwigExtension.php
}

$relative = sprintf('/assets/redactor/langs/%s.js', $lang);
$absolute = $this->projectDir . '/' . $this->publicFolder . $relative;

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.

Please use Path::join instead

@bobvandevijver bobvandevijver Sep 26, 2026 •

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.

You can also use this to prevent directory traversal: the lang file needs to be contained in the

Path::join($this->projectDir, $this->publicFolder, 'assets/redactor/langs');

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);

No need for the newline

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