Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Admin Translations Laravel Package

dejwcake/admin-translations

A Laravel package for managing application translations from your admin panel, letting you edit and organize language strings through a database-backed interface instead of editing language files by hand.

View on GitHub
Deep Wiki
Context7

Getting Started

What you are actually installing. Despite the "translations manager" label, this is a DB-backed translation overlay for Laravel: lang/ files stay the base, a translations table overrides them, and the package swaps Laravel's translation loader so the merge is invisible to __()/trans() at runtime. It also ships a scanner, Excel import/export, and an admin UI. It is a fork of brackets/admin-translations and lives in the Craftable ecosystem — the composer name is dejwcake/admin-translations but the PHP namespace is still Brackets\AdminTranslations.

Minimal setup path

  1. composer require dejwcake/admin-translations. If you previously had brackets/admin-translations, remove it first — same namespace, duplicate provider/class collisions otherwise.
  2. Register Brackets\AdminTranslations\TranslationServiceProvider in bootstrap/providers.php (Laravel 11+) — this is the step that matters, because it replaces the framework's translation loader with TranslationLoaderManager. Verify it took effect before doing anything else:
    // tinker
    app('translation.loader')::class; // expect TranslationLoaderManager, not FileLoader
    
  3. Publish and run the migration for the translations table (group, key, locale, namespace, text and metadata as jsonb, soft deletes).
  4. Register the gates you'll use: admin.translation.index, admin.translation.edit, admin.translation.rescan. Without them, every user except a super-admin gets 403 on /admin/translations.
  5. Populate the table from existing files: php artisan admin-translations:scan-and-save, then clear the cache.

Where to look first

  • README.md — it is unusually load-bearing here: scanning and key collation are documented there, not in the changelog.
  • src/TranslationServiceProvider.php and src/TranslationLoaders/TranslationLoaderManager.php — this is the whole "why it works" in two files.
  • src/Scanner/ScanAndSaveService.php — the command people break things with.
  • routes/admin.php — the exact UI surface and gate names.

First real use case. Run the scan, open /admin/translations, filter by a locale with obvious drift (usually en vs a secondary locale), and fix the shortlist of keys that a non-developer should own — marketing copy, error strings, emails. That single loop (scan → edit in UI → see the change without a deploy) is the package's core value; everything else is scaffolding around it.

Version-matrix caveat before you commit. The dev setup targets PHP ^8.5, Laravel 13, Testbench 11, MariaDB 12.3 and PostgreSQL. Validate against your own PHP/Laravel on a scratch branch; if you're on an older Laravel, this may simply not install.

Implementation Patterns

Decide up front whether DB or files are the source of truth. TranslationLoaderManager merges with array_replace_recursive, and DB always wins. That's the right default for copy overrides, but it produces a silent failure mode: a developer edits lang/en/messages.php, deploys, and nothing changes because a row shadows the key. The pattern that survives contact with a team:

  • Files are the source of truth for structure (which keys exist, per group/namespace).
  • DB is the source of truth for wording on a deliberately small, allowlisted set of groups — the ones a content owner edits.
  • Keep those two lists written down. Anything outside the allowlist should not have a DB row.

The daily loop: scan → triage → edit → export → import.

  • admin-translations:scan-and-save keeps the table in sync with lang/ so new keys appear in the UI.
  • Export a group per locale as XLSX (src/Exports/, maatwebsite/excel) and hand it to a translator or agency.
  • Import via TranslationImportService / src/Imports/. Round-trip the same group you exported; imports are a bulk write path and are the most common source of stale cache (see Gotchas).

Use the scanner as an inventory gate, not a linter. It's a regex scanner over source files. It is genuinely good at "which keys exist in lang/" and genuinely bad at "which keys are used". Wire it into CI to catch structural drift (a key present in en with no counterpart elsewhere), not to catch unused or missing keys — that needs extraction of __(), @lang, trans() calls, which this package doesn't do.

Cache discipline is a design constraint, not a detail. Translation::getTranslationsForGroupAndNamespace() caches essentially forever and is busted only by the model's saved/deleted events. Corollary: every write must go through the Eloquent model. No DB::table('translations')->update(...), no upsert() in a migration, no bulk import that bypasses events. If you must write raw, clear the cache yourself in the same transaction boundary. Encode this as a rule in code review; it's the single highest-value convention for this package.

Keep the scan transactional and off the hot path. ScanAndSaveService soft-deletes everything then restores/creates what it finds, inside a transaction. That's fine, but it means the scan is a full-table mutation — schedule it (deploy hook or nightly job), don't call it from a controller request, and never let two runs overlap.

Gate integration without touching vendor code. Map the three gate names to your own permission system in a Gate::before or a dedicated policy-registration provider. This keeps the package upgradeable and is where you'd also restrict rescan to a narrower role than edit — they are separate gates for exactly that reason.

Headless usage if you're not on Craftable/Admin UI. The admin UI assumes the Craftable brackets/admin-ui stack. If your app isn't on it, you can still use the valuable 80%: bind the package provider for the DB-override loader, run the scanner from a console command or job, and drive import/export from your own (Livewire/Nova/Filament) screens. Treat src/Scanner/, src/Services/TranslationImportService.php and src/TranslationLoaders/ as the API and the HTTP layer as optional.

Vendor/namespaced keys. The loader handles namespace, so you can override vendor::package.key in the DB. Use it sparingly — a package upgrade that renames a key leaves you with a row pointing at nothing, with no error. Prefer overriding your own groups and letting vendor strings come from the package.

Gotchas and Tips

"I changed the file and nothing happened." Expect this as your #1 support ticket. The DB row wins. Give the team a one-liner:

Translation::where('group', 'messages')->where('key', 'welcome')->get(['locale','namespace','text']);

Stale translations after imports or scripts. Bulk import paths and any raw query will not fire saved. Symptom: correct data in the table, wrong string on the page, and php artisan cache:clear "fixes" it. If you ever see that, you have a write path bypassing the model.

The scan can soft-delete DB-only keys. Because it soft-deletes all rows and then restores only what the regex found, any key that exists only in the DB (added through the UI, not in lang/) is at risk. Whether it comes back depends on restore semantics — test this explicitly on your own data before scheduling scans in an environment where people edit through the UI. Practical mitigations: keep UI-authored keys mirrored into files, or scope/disable the destructive part of the scan for those groups. The rows aren't hard-deleted (soft deletes), so recovery is restore() — verify deleted_at before assuming data loss.

Scanner blind spots. Expect nothing from: dynamically constructed keys (__($key)), trans_choice plurals with interpolated counts, keys inside JS (laravel-vue-i18n), concatenated group names, and unusual quoting. Nested arrays are handled, but if you find a miss, the fix is usually to add a literal call site, not to patch the regex.

Replacing the translation loader is a global change. Anything assuming Laravel's FileLoader — other packages registering their own loader, code calling load() and expecting file-only results, lang:publish-style tooling — can behave differently. After enabling the provider, smoke-test the areas most likely to care: validation messages, auth/password notifications, mailables, and any package whose lang files you rely on.

jsonb columns limit your test matrix. text and metadata are jsonb; the project tests against MariaDB and PostgreSQL, and SQLite is not in the picture. If your suite uses in-memory SQLite, migrations or assertions will fail — use a real MariaDB/Postgres connection for anything touching this table. It's also a reason not to hand-edit rows across engines.

Non-Craftable installs will get a broken UI, not an error. Missing Admin UI assets/views usually manifests as a blank or unstyled /admin/translations rather than a clean exception. If the page is empty, check route:list --path=admin/translations first, then gate evaluation for the current user, then the craftable view/asset dependency — in that order.

Debugging checklist, in order: app('translation.loader')::class → gate check for the acting user → row present in translations for that group/key/locale/namespace → cache cleared → provider actually registered (not just required in composer).

Extension points, cheapest first: register your own gates/policies (no fork); override the published config/views; bind a subclass of DbTranslationLoader or TranslationLoaderManager if you need different merge semantics (e.g. per-locale fallbacks); replace the importer/exporter for CSV or a TMS API. The loaders and the import service are the seams designed for replacement — the HTTP layer isn't.

Upgrades. 2.3.2 is explicitly a no-op: no migrations, no config changes, no deprecations. Verify your lockfile diff is limited to the version bump. Note dist archives no longer ship tests/, Docker or QA config (export-ignore on .gitattributes) — so if you need to reproduce a bug against the package's own test suite, clone the repository and run in its Docker setup rather than looking in vendor/.

Trust the source over the surrounding docs. The product-manager notes in the release bundle characterize this as inventory-only tooling with no admin UI and no runtime delivery, which contradicts both the package description and AGENTS.md (which documents the UI, routes, loaders and DB-override behaviour). Before expanding this beyond a dev-time tool, read src/ and routes/ directly — the changelog is thin, and it's the only reliable map.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core