A multilingual Laravel CMS needs two kinds of translation: the interface (buttons, menus, validation messages) and the content (posts, pages, product text). Lara Dashboard ships the first one out of the box. The admin comes with 32 locale files, a translation editor, a language switcher and two Artisan commands to keep strings in sync. Translating the content itself is not built into core posts today, so for a fully multilingual public site you add a package such as Spatie's laravel-translatable in your own module.
Disclosure: Lara Dashboard is our open-source, MIT-licensed Laravel CMS and admin. Everything below about Lara Dashboard comes from the public repo's develop branch as of November 2026 (core v1.5.0, Laravel 13, PHP 8.3+). Facts about other tools come from their official docs, checked the same month.
Quick answer: the admin speaks many languages, content translation is your call
If your team or your clients need to use the admin in their own language, you are covered on day one. Pick a language from the switcher and the interface changes for that session. If a word is wrong, an admin with the right permission fixes it in the translation editor, and the fix survives core updates.
If you need the same blog post in English, Spanish and Bengali, with its own URL per language, that is a content model decision. Lara Dashboard posts have one title and one body. You add per-locale fields in a module, and you own the routing and SEO side. We think that is the honest split, and the rest of this post explains both halves.
Interface translations use plain Laravel files, not a custom format
Lara Dashboard does not invent its own translation system. It uses the same two file types Laravel documents in its localization guide.
JSON string keys. The file
resources/lang/en.json holds the English strings, keyed by the English text itself. At the time of writing it has about 3,200 keys. Each other language has a matching file, such as de.json or bn.json. In Blade you call __('Save') as usual.PHP group files. Laravel's own messages for
auth, pagination, passwords and validation live in folders like resources/lang/en/validation.php. The translation editor shows these as separate groups next to the general JSON group.The repo ships 32 JSON locale files, including English, Arabic, Bengali, Chinese, French, German, Hindi, Japanese, Spanish, Turkish and Ukrainian. How complete each one is varies, and the editor shows you translation stats per language so you can see the gaps before a client does.
Because it is plain Laravel, any developer who has localized a Laravel app already knows how this works. There is nothing to learn that would not transfer to another Laravel project.
The language switcher sets the locale per session
The header of the admin and the login pages include a locale switcher. Choosing a language calls the
locale.switch route, which stores the code in the session. A small middleware in the web group, SetlocaleMiddleware, then reads the session value on each request and calls App::setLocale(). If nothing is in the session, it falls back to locale in config/app.php, which is en by default.The list of languages in the switcher is not hard-coded.
LanguageService scans resources/lang, maps each code to a readable name with its native spelling, and passes the list through the filter.languages hook. That means a module can add or remove languages from the switcher without editing core. If you have not used hooks before, our guide to building modular Laravel applications with Lara Dashboard shows where modules plug in.One detail to be clear about: the locale is a session choice. Users do have a locale field on their profile, but the middleware reads the session, not the profile. If you want a user's saved language applied at login, that is a small listener you would add.
Overrides live in user-lang, so core updates do not wipe your fixes
This is the part we like most, and the part that trips teams up on other systems. When you edit a string in the translation editor, Lara Dashboard does not change the core file. It compares your value with the core value and saves only the differences to
resources/user-lang/.A service provider,
CustomTranslationServiceProvider, adds that folder to Laravel's translation loader after the core paths. Later paths win, so your override is what users see. When we ship a new core version with an updated de.json, your custom German strings still apply on top.If you clear an override so it matches core again, the editor removes it from the user file. If a language has no overrides left, the user file is deleted. You do not end up with a pile of stale copies.
The editor sits under Settings and is gated by two permissions,
translations.view and translations.edit. Give translators the view and edit rights without handing them the rest of the settings. Our guide to role-based access control in Laravel covers how to build a role like that.Two Artisan commands keep every language in step with English
English is the source of truth. Two commands keep the other files honest.
translations:extract. Running
php artisan translations:extract scans resources/views, app and resources/js for translatable strings and adds any missing ones to en.json. Use --dry-run to preview. It skips the modules folder, so module strings stay in the module.translations:sync. Running
php artisan translations:sync sorts en.json and lines up every other JSON file against it. You can target one language with --lang=es, drop keys that no longer exist in English with --remove-stale, and preview with --dry-run.A good habit is to run both in CI as a dry run and fail the build if new strings appear. That stops untranslated English from leaking into a German admin after a feature merge. Our post on staging, tests and CI for Laravel CMS work shows where a check like that fits.
The LaraBuilder editor also has its own JavaScript helpers,
__(), _n() and _x(), imported from @lara-builder/i18n. They follow the same key style, so block labels in the visual editor can be translated too. There is also a small public endpoint, /api/translations/{lang}, that returns a core JSON file, with path checks so it cannot read outside the lang folder.Modules ship their own translations
A module keeps its strings in its own
resources/lang folder. The ModuleTranslationMiddleware looks for a JSON file for the current locale in each module and registers that path with the translator. The module guide also shows namespaced keys loaded with loadTranslationsFrom(), used as __('blog::blog.posts').This matters if you sell or reuse modules. A buyer in another country can add a language to your module without forking it. If you are building one now, our walkthrough on how to build a Laravel module step by step is the place to start, and you can add the lang folder from the first commit.
Content translation needs a model decision, and we leave it to you
Here is the honest gap. Core posts and pages have no language field and no per-language versions. If you need translated content on the public site, you have three common paths.
| Approach | How it works | Good fit | Watch out for |
|---|---|---|---|
| JSON columns per field | Spatie laravel-translatable stores each locale in one JSON column | A few translated fields on your own models | Search and sorting inside JSON need care |
| Separate rows per locale | One record per language, linked by a shared group ID | Editors who translate whole pages at a time | Keeping slugs, media and terms in sync |
| Separate sites | One install per language or region | Very different content per market | Double the hosting, updates and users |
For most Laravel teams, the first path is the smallest step. Spatie's laravel-translatable docs (v6 as of November 2026) describe storing translations as JSON with no extra table, then reading them with
getTranslation('name', 'nl') or simply $model->name for the current locale. You would use it on models inside your own module, such as a translated product catalogue or a translated docs section.Whichever path you pick, you also need locale-aware URLs, such as
/es/precios, and correct hreflang links so search engines show the right version. Google explains the rules in its guide to localized versions of your pages. If you are moving a multilingual WordPress site, plan these URLs before cutover. Our post on rebuilding a WordPress site in Laravel without losing rankings covers redirects and URL parity.Trade-offs: when not to use Lara Dashboard for a multilingual site
We would rather you pick the right tool than pick us and regret it.
- If you need translated content, per-language URLs and hreflang on day one with no code, a CMS with built-in content localization will be faster.
- If your editors expect a side-by-side translation workflow with status per language, you will have to build that UI yourself.
- If you depend on machine translation built into the editor, that is not a core feature today.
- If you need right-to-left layout polish for Arabic or Persian across every screen, test the admin in that language first and budget time for fixes.
- If your deploys replace the whole release folder, resources/user-lang is ignored by git and can be lost unless you keep it in shared storage.
That last one deserves a note. The repo's
.gitignore excludes resources/user-lang, by design, so local overrides do not end up in core. On a zero-downtime deploy, symlink that folder to shared storage the same way you do with storage, and include it in your backups. Our post on hosting and deploy paths for Lara Dashboard walks through shared folders.Self-check before you promise a client a multilingual site
- List which languages the admin needs and which the public site needs. They are often different.
- Open the translation editor and check the stats for each admin language.
- Create a translator role with only translations.view and translations.edit.
- Decide how content will be translated: JSON columns, rows per locale or separate sites.
- Pick a URL pattern per language and plan hreflang tags.
- Add translations:extract and translations:sync dry runs to CI.
- Give every custom module its own lang folder from the start.
- Symlink resources/user-lang to shared storage and back it up.
- Test the admin and the public pages in your longest language and any right-to-left language.
Verdict: a solid multilingual admin, with content translation you design
Lara Dashboard gives you a translated admin that uses standard Laravel files, a safe override layer that survives upgrades, a permission-gated editor and CLI tools to keep languages in sync. That covers a lot of agency and SaaS work, where staff and clients just need the back office in their own language.
For translated public content, it gives you a clean Laravel base rather than a finished feature. If your team is comfortable adding a package and a few routes, that is a good trade, because the content model ends up shaped around your project. If you want it all done for you, choose a tool that ships it. If you want to see how the admin side feels, try Lara Dashboard and switch the language from the header.
FAQ
How many languages does Lara Dashboard support?
The repo ships 32 JSON locale files, including English, as of November 2026. You can add a new language from the translation editor, and modules can change the language list through the filter.languages hook.
Will a core update overwrite my custom translations?
No. Edits from the translation editor are saved as differences in resources/user-lang, which is loaded after the core files. Core updates change the core files only. Just make sure that folder survives your deploys.
Can I translate blog posts and pages into several languages?
Not in core today. Posts have a single title and body. For translated content, add per-locale fields with a package like Spatie laravel-translatable in a module, or keep one record per language.
How do I add translations to my own module?
Put JSON files in the module's resources/lang folder, one per locale. Lara Dashboard registers them for the current locale on each request. You can also load namespaced PHP files and call them with the module prefix.
Who can edit translations in the admin?
Users whose role has the translations.view and translations.edit permissions. You can give those to a translator without giving them other settings access.
Does the language choice stick for each user?
The switcher stores the choice in the session. A locale field exists on the user profile, but applying it automatically at login is something you would add with a small listener.