dick/translationmanager
A Laravel package for managing application translations from a central interface, letting you browse, edit, and organize language files and locale strings without touching PHP arrays by hand.
1. Confirm you can actually install it. This is a legacy dick/* package — it predates the rename to Backpack for Laravel and targets the Laravel 5.x era. Before anything else, read composer.json in the repo and check the illuminate/* and dick/admin constraints. If you're on Laravel 8+, stop here and look at backpack/translation-manager or barryvdh/laravel-translation-manager instead — the effort of porting this one is not worth it.
2. Install and publish.
composer require dick/translationmanager
php artisan vendor:publish --provider="Tabacitu\TranslationManager\TranslationManagerServiceProvider"
Publish config and views, then check config/ for the package file — the locales list and the language-file path are usually configurable there.
3. Find the route. The package auto-registers its routes inside the admin route group, so the UI is protected by whatever middleware/guard Dick Admin uses. Open the package's routes/ file (or run php artisan route:list | grep -i translat) to get the exact URI, then log in as an admin and visit it.
4. Make sure PHP can write.
ls -ld resources/lang resources/lang/en
# needs to be writable by the web user, e.g. www-data
This is the single most common reason the UI "does nothing" on save.
First use case: someone notices a typo or an awkward string in resources/lang/en/messages.php during QA. Instead of a developer opening an SSH session and a PR, a PM or content editor fixes it in the admin UI and it's live. That's the whole value proposition — treat it as a content tool, not a development tool.
Where to look first: the package is small. Read the routes file, the controller that enumerates groups/locales, and the Blade view that renders the edit form. That's the entire surface area, and understanding it takes maybe 20 minutes.
Gate it to non-production, or to a super-admin. The most important day-one decision. Either:
// AppServiceProvider::boot()
Gate::define('edit-translations', fn ($user) => $user->isSuperAdmin());
…and wrap the package routes in that gate via middleware, or simply only register the service provider in local/staging:
// config/app.php
if (app()->environment(['local', 'staging'])) {
App\Providers\TranslationManagerServiceProvider::class,
}
Direct file writes in production are a deployment hazard (see Gotchas).
Use it as the "last mile" editor, not the source of truth. Keep resources/lang/** in git. The workflow is: developer adds keys via PR with English values → deploy → translator/admin fills in the other locales through the UI → someone commits the resulting diff back to git. If the edited files never make it back into VCS, the next deploy silently reverts everything.
Onboarding a new locale. Create the directory (resources/lang/fr/) with at least one file, and add the locale code to the package config's locale list. The UI scans the filesystem, so an empty or missing directory is the usual reason a language doesn't appear.
Key-parity workflow. The most useful pattern is copying the structure of the base locale into a new one with empty values:
// artisan command of your own
foreach (glob(lang_path('en/*.php')) as $file) {
$group = basename($file, '.php');
$keys = array_keys(require $file);
// write array_fill_keys($keys, '') to lang_path("$locale/$group.php")
}
Then use the UI to fill in the blanks — an empty string is a visible "not done yet" marker, and trans() falls back cleanly.
Finding missing keys. The package edits files; it does not scan your code for __()/trans() calls. Run that separately:
grep -rhoE "(__|trans)\(\s*'[^']+'" app/ resources/views/ | sort -u
Feed the output into your own sync command. This combination — scan in CI, edit in UI — is where the workflow actually pays off.
Vendor translations. php artisan vendor:publish --tag=laravel-errors (and per-package tags) puts package strings into resources/lang/vendor/.... If the UI supports a nested group path, those are editable too; if not, you'll be overriding them in your own validation.php/auth.php instead. Check which before promising anyone they can edit package strings.
Nested keys. Backpack/Laravel supports 'nav' => ['home' => 'Home'] accessed as trans('messages.nav.home'). Verify how the editor renders nested arrays — many implementations of this pattern flatten to dotted keys on read and rebuild on write, which loses integer-indexed arrays (e.g. validation.php's 'attributes' => []). If your files contain arrays-of-arrays, spot-check a round-trip (edit, save, git diff) before rolling out.
The package name is a trap. dick/* is the pre-Backpack name. If you also have backpack/* installed, you can end up with duplicate service providers, two sets of routes, or namespace collisions. Pin exactly one.
Language version ceiling. Any package using array_except, str_plural, or Illuminate\Foundation\Application::LARAVEL_START-era APIs will not run on Laravel 9+. Expect to fork or reimplement. The feature is small enough that a 150-line custom controller is often cheaper than resurrecting this one.
OPcache will serve stale strings. Files are rewritten on disk. With opcache.validate_timestamps=0 (a common production setting), edits appear to have no effect until you reload PHP-FPM or opcache_reset(). In dev, this costs you an hour of confusion the first time; in prod, it means every save requires a reload.
Read-only filesystems. Containerized/pod-based deploys usually mount the app read-only or treat the image as immutable. Writes either fail silently or vanish on the next rollout. Don't ship this enabled in production unless you have a persistent volume and a git-sync step.
Writes must be atomic-ish. A var_export()-based writer that fails mid-write leaves a truncated PHP file, and your whole app 500s on the next trans() call for that group. Check the package does a file_put_contents to a temp file + rename. If it doesn't, add it, or keep resources/lang in git so you can git checkout a broken file instantly.
Escaping and var_export. Values with single quotes, backslashes, or $ are the classic failure mode. Write a few nasty strings (It's "quoted", Cost: $5, \n) through the UI, save, and confirm the file still parses: php -l resources/lang/en/messages.php.
The editor can't see placeholders or pluralization. :name, :count, and one|many pipes are just characters in a text field to whoever is editing. A well-meaning copywriter will delete :name and you'll get Undefined variable or literally Hello :name in production. Add a read-only hint column, or at minimum tell the editors what the colons mean.
Locale confusion. The UI writes to the locale you select; Laravel reads based on app()->getLocale() and fallback_locale. Editing en while the app runs as fr with fallback_locale = en looks broken until you realize you're seeing fallback text. Put the current locale in the page header.
Directory scans don't scale. If you enumerate every file in resources/lang on each request, a large app with 40 groups × 15 locales gets slow. Cache the group/locale index and invalidate on save.
Extension points are thin. Expect to override the controller or the view rather than configure behaviour. If you need per-user permissions, audit logging, or a "publish to production" button, plan on forking rather than extending — and if you're forking, seriously re-evaluate whether to write the 200 lines yourself.
How can I help you explore Laravel packages today?