jaysontemporas/translation-overrides
A Laravel package for overriding your application's translation strings without editing vendor language files. Store custom translations in the database or config so they take precedence over the defaults, making it easy to tweak wording per project, environment, or locale.
Install the package with Composer and publish its assets:
composer require jaysontemporas/translation-overrides
php artisan vendor:publish --provider="JaysonTemporas\TranslationOverrides\TranslationOverridesServiceProvider"
The publish step typically exposes a config file (e.g. config/translation-overrides.php) and a migration for the table that stores overrides. Run php artisan migrate before touching anything else.
Where to look first, in order:
README.md — for the exact table/column names and whether the override table is keyed by locale + group + key or by a single dotted key, and whether there is an artisan command to import existing lang files.config/translation-overrides.php — cache driver/TTL, whether a model can be swapped, whether tenancy/scope is supported.src/ — specifically the class that hooks into Laravel's translator (a Translator decorator or a FileLoader replacement). That class tells you exactly when overrides win over lang/*.php files.First use case: let an admin change a single validation message without a deploy. Insert a row for locale=en, group=validation, key=required, value=..., then verify in Tinker:
app('translator')->get('validation.required'); // should return the override
If that returns the original lang-file string, the override is not being resolved — stop and read the translator hook before building anything on top of it.
Override as the outermost layer. The intended flow is __() → translator → override lookup → fall back to file loader. This means you almost never call the package directly in application code; you keep writing __('messages.welcome') in Blade and controllers exactly as before, and the override simply shadows the file value when one exists. Treat the package as infrastructure, not an API.
Admin CRUD over the override table. The day-to-day workflow is a small back-office screen listing translation keys with an editable value column. Two patterns work well:
lang/ (or running php artisan lang:publish-style discovery) and show existing overrides inline. Best for editors who need to see everything.Whichever you choose, resolve the "effective value" through the translator (__($key)) rather than reading the override row directly, so the UI always reflects what the user actually sees.
Invalidate cache on write. Almost every package of this shape caches the override map because hitting the DB on every __() call is unacceptable. Wire a model observer or the package's events (saved/deleted) to Cache::forget() the relevant key — or the whole map if it's coarse. If the package does not do this for you, do it yourself in the controller that persists edits; a stale translation cache is the #1 support ticket with this class of package.
Scope by locale deliberately. Build the editor so a locale selector drives the form. When a key exists in en but not de, decide explicitly: fall back to fallback_locale (Laravel default), or show an empty override. Document the choice — translators will ask.
Testing pattern. Overrides are data, so RefreshDatabase plus a factory works cleanly:
$this->assertSame('Please provide a name.', __('validation.required'));
Assert against __() (the observable behavior), not against the model, so the test survives a change in storage format.
Integration tip: if you already run a translation-management SaaS or spatie/laravel-translation-loader, do not stack two loaders — you will get order-dependent, hard-to-debug resolution. Pick one owner of the "overrides win" slot.
Missing base keys. If a key exists only as an override and not in any lang file, behavior depends entirely on whether the package's loader returns the override before or after the "file not found" branch. Many implementations return the raw key (e.g. messages.foo) in this case. Test it early; if it fails, always ship a base key in the lang file and use the override only to change its text.
Namespaced/vendor translations. __('package::file.key') resolves through a different lookup path than plain keys. Verify the package handles the namespace::group.key form, and if it doesn't, don't advertise vendor-string editing to your team — it will silently no-op.
The translator is a singleton. Decoration happens at container-binding time. If you also override the translator (common in multi-tenant apps), order matters: register the package provider and then rebind, or use $app->extend(). Rebinding naïvely in your own provider can drop the override layer entirely, and you will only notice in production.
Config cache. Deployments running php artisan config:cache will not pick up config edits until recached. Add config:clear/config:cache to the deploy script and never edit config/translation-overrides.php in production.
DB round-trips under load. Even with caching, the first request per cache key pays for a full-table read. If your override table grows into the thousands, check whether the package loads all rows or filters by locale; if all rows, request a locale filter or add one yourself. Watch query count on any page rendering many __() calls.
Cache stores without tags. File and database cache stores don't support tags. If the package uses Cache::tags(), it will throw on those drivers — switch to Redis/Memcached or a store that supports tagging, or fork the invalidation to use explicit keys.
Fallback locale edge case. An override in fr with fallback_locale=en may or may not be consulted when fr has no entry at all. Decide whether an override counts as a translation (it should) and confirm the loader treats it that way.
Extension points worth knowing: a swappable model in config (for adding updated_by, tenancy IDs, or approval workflow columns), events emitted on save (for audit logging), and any public resolver method you can bind to for custom lookup.
Project risk, stated plainly. Two stars, zero dependents, single author, latest release October 2025. There is essentially no community validation and no ecosystem pressure to keep the translator hook stable across Laravel majors — and the translator internals have changed before. Pin the version in composer.json, write a handful of tests that assert override resolution and fallback behavior, and read the translator integration class in src/ so a fork is a 30-minute job rather than a rewrite. For a critical admin-editing feature, the more established alternative (spatie/laravel-translation-loader) carries the same concept with far more mileage; choose this package only if its specific table shape or API saves you real work.
How can I help you explore Laravel packages today?