brunoscode/laravel-translation-handler
A Laravel package for handling application translations, offering tools to manage and work with language files in your project.
Architecture fit
The package is a conventionally-structured Laravel package: a service provider, a publishable config file, a facade (TranslationHandler), and a strategy pattern where each storage format is a swappable handler class behind FileHandlerInterface or DatabaseHandlerInterface. That is the right shape for this problem and it is the most credible thing about the package — it means we can substitute our own handler (e.g., a remote TMS client, a tenant-scoped DB table) without forking, via setOption() or config. State is carried in an immutable TranslationOptions snapshot and a TranslationCollection value object, both of which are sensible and testable. There is no queue, event, or observer machinery, so it does not intrude on application runtime.
The notable architectural decision is the four-format model: php_file, json_file, csv_file, db are peers, and sync/import/export are just copy operations between any two of them. This is genuinely useful for the translator round-trip (export CSV → vendor → import), but it also means the package has no single source of truth. That is a product decision we have to make on our side, not something the package imposes.
The second architectural surface is the Laravel Boost v2 MCP integration. When Boost is present, the package auto-registers translation read/write tools with no configuration. That is the differentiator and also the largest blast-radius decision in the package: an LLM agent gets set, delete, and delete-group against our translation store with no human-in-the-loop gate defined by the package.
Integration feasibility
Composer install, publish config, optionally run migrations for the db format, and the Artisan commands work immediately. The MCP path adds one real prerequisite — laravel/boost v2 — which is itself a young dependency and should be evaluated separately. Nothing here requires changes to application code; the package reads and writes files under lang_path() and storage_path('lang') by default, and the check command scans configured paths read-only.
Feasibility is high; the install is measured in an afternoon. The risk is not in making it work, it is in the operating model it implies.
Technical risk
| Risk | Severity | Notes |
|---|---|---|
| Whole-file rewrite semantics | High | Every write to PHP/JSON/CSV rewrites the entire file. N edits = N full rewrites. The package's own guidance ("edit in db, sync once at the end") exists because of this; it is a workaround for a design limitation, not a feature. |
| Destructive operations | High | delete wipes an entire format; deleteGroup removes every key under a prefix; force overwrites without prompting. Exposed over MCP, these are one hallucinated argument away from data loss. |
| No cross-format transactionality | Medium | A sync that fails midway leaves source and target divergent with no rollback. File writes are not documented as atomic (temp-file-and-rename). |
| Regex-based key extraction | Medium | check uses configurable static/dynamic regexes over source files. Expect false positives on concatenated keys and false negatives on keys built from variables; pluralization and trans_choice handling is unverified. |
| Delimiter/format collisions | Medium | keyDelimiter defaults to ., CSV delimiter to ;, and jsonNested/phpFormat toggle nesting. Misconfiguration silently changes the key namespace. |
| Supply-chain / bus factor | High | 0 stars, 0 dependents, single maintainer, no visible adoption. MIT license mitigates the legal risk but not the abandonment risk. |
| Metadata diligence | Low | Release date and package/registry naming should be verified against Packagist before it enters a lockfile. |
Key questions
composer.json declare, and is there a CI matrix?sync atomic per target file, and what happens to the target if the source read throws midway?check understand trans_choice, pluralization, @lang, Lang::get, and vendor/package namespaces?db rows keyed — is there a unique constraint on (key, locale), and does set upsert or insert?Stack fit
Strong fit for a standard Laravel app with a lang/ directory and more than two locales. Weak fit if we already use a database-backed translation model (spatie/laravel-translatable, astrotomic/laravel-translatable) or an external TMS — the package's db handler assumes its own schema, so we would be maintaining a third translation store rather than consolidating.
The MCP surface only pays off if the team is already using Laravel Boost or intends to. If Boost is not in the stack, evaluate the package purely as a CLI/format-conversion tool and set MCP aside.
Migration path
Adopt in stages, each stage independently valuable and independently reversible:
check at backend and frontend paths, run it in CI as a non-blocking report. Zero write risk, immediate signal on missing keys and orphans.import/export for translator round-trips only (PHP → CSV → vendor → PHP). Run manually, on a branch, never in a deploy step.sync php_file → db, and make db the working surface for edits. Files remain authoritative and are regenerated by a single sync db → php_file before merge.Compatibility
lang_path(). Any environment with a read-only or ephemeral filesystem (container images built once, Lambda, Heroku-style slugs) cannot write translations at runtime — file targets must only be touched during build or local development. This is the single biggest compatibility constraint and should drive the design toward db as the runtime source.lang/ will not be visible until cache refresh. Sequence any file sync before config:cache / deployment warmup, not after.vendor/*/lang are out of scope for this tool; the check command will flag them as missing unless paths are excluded.lang/*.json is unchanged by this package and arguably worsened by full-file rewrites.Sequencing
Phase 1 (audit) is low effort and produces a decision input. Phase 2 (round-trip tooling) is low effort, low risk. Phase 3 (db staging) requires a schema decision, a migration, and agreement on which format is authoritative — that is the real cost, and it is a process change more than an engineering one. Phase 4 (MCP) should be gated on Phase 3 being stable for at least one release cycle. I would not bundle these; each phase should be able to stop cleanly.
Maintenance
The package itself is small and the extension points mean we can absorb most changes locally without forking. But 0 stars, 0 dependents, and a single maintainer mean we should treat it as effectively unmaintained from day one: pin an exact version, vendor the source into review scope, and have a named owner who can take over the fork. The MIT license makes that fork legally trivial, which is the main reason this is acceptable risk rather than a blocker.
The unusual documentation posture deserves mention: the primary docs are agent skill files (SKILL.md) written for consumption by an LLM, not a human. They are accurate and detailed, but they are a workflow spec, not API reference. Onboarding a human engineer means reading code.
Support
Community-only, no SLA, no commercial backing. Internal support burden falls on whoever owns the translation pipeline. Budget for that explicitly rather than assuming it is free.
Scaling
Scaling concerns are about write amplification, not request throughput — nothing here runs on the hot path.
db writes are row-scoped and scale fine, but the schema is the package's, not ours, so multi-tenant or high-cardinality locale scenarios need verification before we rely on it.check scan is a full source-tree regex pass; on a large monorepo it will be slow and should run in CI, not on demand in a dev loop.Failure modes
| Mode | Trigger | Impact | Mitigation |
|---|---|---|---|
| Truncated/corrupt lang file | Interrupted write or full disk during full-file rewrite | Broken app boot, missing translations | Keep lang/ in version control; never write in production; diff review on every sync |
| Key loss on sync | force with a narrower source than expected |
Silently dropped translations | Always get and inspect counts before force; sync to a branch |
| Group/format wipe | delete / deleteGroup, especially via MCP |
Catastrophic | Disable destructive MCP tools outside dev; database-level backup |
| Partial sync | Exception midway through format copy | Source and target divergent | Re-run sync idempotently; verify with check after |
| Delimiter collision | Config drift between keyDelimiter, CSV delimiter, nesting flags |
Keys flattened or renamed on round-trip | Lock config in a shared base; assert round-trip in CI |
| Agent misfire | LLM calls set-translation-group or delete with wrong scope |
Unreviewed content or data loss | Read-only MCP in shared environments; PR-based review of all translation diffs |
| Abandonment | Maintainer stops; Laravel major bump | Stuck on old Laravel | Pinned version + fork readiness |
Ramp-up
Code ramp-up is genuinely low — a day to get productive, a day more for the collection/options API. The real ramp-up is procedural:
db, sync to files once" rule, and understand why (full-file rewrite), or people will violate it on day two.delete/deleteGroup/force.check so the missing-key report stays trustworthy; a noisy audit tool gets ignored within a sprint.Net TPM read: adopt Phase 1 and 2 now — they are cheap, reversible, and deliver real value. Treat Phase 3 as a process commitment with genuine design cost, and Phase 4 as an experiment that needs guardrails before it touches anything shared. The package is well-shaped but unproven and single-maintainer; the correct posture is a bounded bet with a fork exit, not a platform dependency.
How can I help you explore Laravel packages today?