Open-source project
barryvdh/laravel-translation-manager avatar
barryvdh/laravel-translation-manager

Translation Manager keeps PHP language files, and exports wipe your comments

Manage Laravel translation files

1,701 stars414 forksPHPMIT

At a glance

What is it?
This package does not replace Laravel's translator. It imports the PHP language files into a database, lets non-programmers edit them through a web interface, and writes them back out, so translations stay in git and nothing is added to production requests. The export command overwrites files and strips comments.
Who is it for?
Adopt laravel-translation-manager if your Laravel app keeps translations in PHP files and you need a translation interface for people who do not edit code, and use the Artisan commands rather than the buttons so export is reviewable. Skip it if your strings live in JSON files under a modern Laravel layout, where the documented paths point at `app/lang` rather than `lang`.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 113 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The database is the editing surface, the files stay the runtime

The distinction that defines this package is in its second line of description: it does not replace the Laravel translation system, it only imports and exports the PHP files to a database and makes them editable through a web interface.

So at runtime nothing changes. Your application keeps calling the normal translation functions and still reads PHP files from disk. The package is a development and translation tool that sits outside the request path, which is why the README says no overhead is introduced in production and translations can be saved in git history.

The workflow it describes is a loop. Import translations by reading all translation files into the database. Then find all translations in the PHP and Twig sources, optionally listen for missing translations with a custom Translator, translate the keys through the web interface, and export by writing everything back to the files.

Three of those five steps exist because the keys are not always knowable in advance. Import covers keys already in files. Find covers keys present in source but missing from the file. The custom Translator covers keys that are dynamic, generated by variables or built at request time, which no static scan can find.

So the design is a database as a staging area for translation, not as a runtime store. That keeps deployment simple and keeps git as the source of truth, at the cost of a round trip through files.

Three vendor:publish commands and a migration

Installation is one Composer command followed by three Artisan publishes.

The package is added to composer.json and installed, either by running composer update or directly:

bash
composer require barryvdh/laravel-translation-manager

It ships migrations, so they have to be published and run:

bash
php artisan vendor:publish --provider="Barryvdh\TranslationManager\ManagerServiceProvider" --tag=migrations
php artisan migrate

The config file comes next, published as `config/translation-manager.php`, which is where the package is configured:

bash
php artisan vendor:publish --provider="Barryvdh\TranslationManager\ManagerServiceProvider" --tag=config

Views are a third publish and are only needed if you want to edit the default template. They land in `resources/views/vendor/translation-manager`:

bash
php artisan vendor:publish --provider="Barryvdh\TranslationManager\ManagerServiceProvider" --tag=views

Routes are added by the ServiceProvider itself. The prefix and the middleware or filter are set in the configuration, and the default prefix puts the manager at `http://yourdomain.com/translations`. For anything beyond that the README points at extending the ServiceProvider and overriding its `map()` function, which is the documented escape hatch for full customisation.

One detail that catches people out is in the next section, and it is not optional.

On Laravel 5.2 and later, the web middleware is required

The configuration defaults to a single middleware entry:

php
'route' => [
    'prefix' => 'translations',
    'middleware' => 'auth',
],

That default no longer works on modern Laravel. From the 5.2 changes onwards, session variables are only accessible when the route includes the `web` middleware, so a route protected by `auth` alone has no session and the authentication check fails in a way that looks like a broken login.

The fix is to make middleware an array and put `web` first:

php
'route' => [
    'prefix' => 'translations',
    'middleware' => [
        'web',
        'auth',
    ],
],

The README marks this as needed only in Laravel 5.2 and up, which given the current state of the framework means always. The wording dates the section: it explains a migration that happened years ago and is still documented as though it is conditional.

That matters more than it looks. An admin interface protected only by session-based auth on a modern Laravel needs the web middleware group, and getting it wrong produces a login loop rather than an error message.

Automatic translation is a separate package. If you want Google Translate, the README points at laravel-google-translate and gives its own Composer and vendor:publish commands, so machine translation is an add-on rather than a feature here.

Five Artisan commands, two of which are destructive

The command set is the real interface, and the README is clear about which ones can lose work.

Import reads everything under `app/lang` into the database:

bash
php artisan translations:import

By default only new strings are added and translations already in the database are left alone. To overwrite everything with what is in the files, add `--replace` or `-R`.

Strings from `app/lang/locale.json` files go into the `__json_` group.

Find searches PHP and Twig files in the app directory for translation functions and extracts group and item names, adding the keys it finds to the database:

bash
php artisan translations:find

Projects that use translation strings as keys also land in `__json_`. This is the step that catches keys you forgot, as long as they appear literally in source.

Export writes the database back to PHP files in `app/lang`. The warning is explicit: it overwrites existing translations and removes all comments, and you should back up your data before using it. A group name limits the write to specific groups:

bash
php artisan translations:export <group>

With two locales, `php artisan translations:export reminders` writes to `app/lang/en/reminders.php` and `app/lang/nl/reminders.php`. To export the string-keyed entries as JSON, add `--json` or `-J`, which writes everything in the `__json_` group.

Clean deletes every NULL translation so the interface is tidier, with the note that empty translations are never exported:

bash
php artisan translations:clean

Reset clears all translations from the database so you can start fresh with a new import, and the README says to export first if you want the work:

bash
php artisan translations:reset

Read as a set, the destructive ones are export, because of the comment loss, and reset, because it discards the database entirely. Neither prompts.

Dynamic keys need a swapped service provider

Find covers keys that appear literally in source, which is most of them and not all of them. Keys built from a variable, a form field name or a database value cannot be found by scanning files.

For those, the package offers a different route: listen for missing translations at runtime. You swap Laravel's TranslationServiceProvider for the one in this package, which means editing `config/app.php` to comment out the original registration:

php
//'Illuminate\Translation\TranslationServiceProvider',

and adding this package's provider in its place.

This is a deeper change than the other configuration steps, and it deserves a moment of thought. You are replacing part of the framework's own service wiring to catch runtime misses, which means the behaviour of every translation call in your application now depends on this package's provider. That is acceptable during a translation sprint and questionable as a permanent arrangement, since the missing-key detection has to stay enabled or be removed and the config reverted.

There is also a permission requirement that appears only at export time. For publishing translations, the application has to be able to write to the language directory. A read-only container filesystem or a deploy user without write access produces a failure at export, which is exactly the moment you least want it.

On the web interface itself, clicking a translation opens an edit field, saving writes it immediately, and editing a translation that does not yet exist in another locale creates it. That last behaviour is what makes the tool usable by someone who is not reading your source.

Where this package is the wrong fit

Three cases.

If your translations already live in JSON files, stop and check. Every path in the documentation is `app/lang`, with `php` files and a `locale.json` handled as a special case, and that layout is the older Laravel convention. A current application with strings in `lang/en.json` and `lang/en/...` will need to confirm where the importer looks before anything else.

If you need translation strings in production to come from a database, for example edited without a deploy, this is the wrong design. The package is explicit that it exports to files and adds nothing to the request path, which is the right call for most teams and the wrong one if editors are expected to change copy live.

If your translation files carry meaningful comments, for translators, feature flags or temporary notes, exporting loses them. The README says so directly, which is at least better than discovering it after a merge conflict.

There is also a version history worth noting. v0.6.9 was published on 2026-03-19, following v0.6.8 on 2025-06-07, which is labelled a security release, and v0.6.7 on 2025-02-25. The last push was on 2026-07-04. The licence is MIT, which is permissive for commercial applications.

Editorial conclusion

Adopt laravel-translation-manager if your Laravel app keeps translations in PHP files and you need a translation interface for people who do not edit code, and use the Artisan commands rather than the buttons so export is reviewable. Skip it if your strings live in JSON files under a modern Laravel layout, where the documented paths point at `app/lang` rather than `lang`. Verify first that your application can write to the language directory, since publishing silently fails otherwise, and back up before any export.

Frequently asked questions

How do I install laravel-translation-manager?

Run `composer require barryvdh/laravel-translation-manager`, then publish and run the migrations, publish the config to `config/translation-manager.php`, and publish views if you want to change the templates. All three use vendor:publish with the package ServiceProvider and the migrations, config or views tag.

Why does the translation manager login not work on recent Laravel?

The default route configuration applies only the auth middleware, but since Laravel 5.2 session variables are only accessible on routes that include the web middleware. Change the route middleware to an array with 'web' listed before 'auth'.

Is it safe to run the export command?

The README warns that export overwrites existing translations and removes all comments, so back up your data first. Supplying a group name limits the write, and `--json` exports the `__json_` group to JSON files instead of PHP files.

Official sources

  1. barryvdh/laravel-translation-manager on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/barryvdh-laravel-translation-manager.svg)](https://hysenlabs.com/projects/barryvdh-laravel-translation-manager)

Was this useful?