we-are-neopix/laravel-model-translation
Add multi-language attributes to Eloquent models with we-are-neopix/laravel-model-translation. Store per-locale values, fall back to defaults, and retrieve translations via simple accessors—no extra tables or schema changes required.
Architecture fit. This package delivers one narrow capability: locale-resolved attribute values on Eloquent models. It does not touch routing, middleware, or Laravel's lang/ system, and it can be confined to a bounded set of content models (CMS pages, product catalogs, categories, help articles). That confinement is the property a TPM should value most, because it bounds blast radius — if the package is abandoned or proves unsuitable, the affected surface is a handful of models, not a subsystem. The driver contract is an architectural hedge, not a feature: it lowers the cost of changing storage, but it does nothing to lower the cost of the package itself going unmaintained. The scope-shaping question is where resolution happens — trait-based lazy accessors hide I/O behind property reads, so in Blade loops and API serializers this is a latent N+1. Whether that becomes a delivery line item (caching layer, eager-loading scope) must be settled in the spike, because it changes the estimate from "hours to integrate" to "hours to integrate plus a performance workstream."
Integration feasibility. Mechanically trivial and well-understood: composer require, publish config, add the trait, declare $translatable, choose a driver. On a greenfield model that is a morning's work. The TPM-relevant friction is not installation — it is the three unknowns that gate the estimate: (1) declared laravel/framework and php constraints against our pinned LTS and PHP baseline; (2) the DB driver's schema and index quality, which becomes permanent schema we own; (3) the cost of any custom driver, which converts a consumed dependency into an owned implementation. Present the estimate as a range and close it with the spike, not with the README.
Technical risk. The dominant risk is adoption signal, not code quality. Forty stars, zero dependents, a composite score of 0.32, and a single agency maintainer mean no public evidence of production use and no ecosystem pressure to stay current. That is a sustainability risk with a known mitigation (fork-and-own) and a known, budgetable cost. The technical risks are testable:
| Risk | Impact if unaddressed | Disposition |
|---|---|---|
| Abstraction leak — translated values not filterable/sortable without driver-specific code | The driver abstraction buys nothing; the package reduces to "a translations table plus a trait we could have written" | Spike question; unfavourable answer = build in-house (~150 lines) |
| JSON driver deployment hostility — filesystem writes on read-only/ephemeral containers, divergence across instances, write races | Data loss or inconsistent content in scaled environments | Exclude by written policy; treat as local-dev convenience only |
| Schema ownership — polymorphic table grows as models × attributes × locales; missing composite unique + covering index | Every read becomes a table scan; permanent runbook/DBA cost | Spike verifies indexes; migration review gates |
Fallback semantics — silent null vs. default-locale substitution vs. exception |
Ships blank labels to end users; surfaces as a product bug | Product decision made explicit, not inherited from library default |
| Upgrade fragility — trait packages touching Eloquent internals need a patch cycle per Laravel major | Blocks a framework upgrade or forces an out-of-band fork patch | Budget 1–2 engineer-days per major; contract test in CI |
Key questions. These are the spike's exit criteria; each needs a named owner and a written answer before go/no-go:
laravel/framework and php constraints — is there a tagged release compatible with our current LTS and PHP baseline?null, default-locale substitution, or exception?toArray() and API resources?where/orderBy without dropping to driver-specific code?Stack fit. Laravel-only, Eloquent-only, and compatible with existing locale middleware. The important consequence is that the product will run two localization mechanisms simultaneously. Make the boundary a written engineering rule — static UI copy in lang/, model content through this package — and put it in the onboarding doc. Unwritten boundaries erode, and eroding them turns into recurring localization debugging. For the build-vs-adopt framing, present three options, not two: an in-house trait (~150 lines, fully owned), an established package (spatie/laravel-translatable with a JSON column and no join, or astrotomic/laravel-translatable for the stronger query story), and this package. This package is only defensible where swappable storage is a genuine, articulable requirement. If that requirement cannot be stated in one sentence, the answer is one of the alternatives.
Migration path. The driver model makes storage a product-visible choice: DB table, JSON file, or custom (external TMS, cache-backed store, per-tenant store). That is genuinely useful for phasing a file-based → DB-backed migration without rewriting model-facing code. Regardless of which storage we start on, the exit strategy is decided before the first trait is written: route translation access through a thin application-level repository or accessor, so the trait remains the single coupling point and replacement means editing one class per model rather than every view and controller. Adopt with a per-model rollback flag so any single model can be reverted without a release.
Compatibility. Pin the version and adopt a "vendor and patch" posture from day one: fork into an internal namespace, keep the upstream remote for diffing, and own Laravel major-version compatibility at the budgeted 1–2 engineer-days per upgrade. Add a contract test covering one translatable model (read, write, fallback, query) to CI so framework bumps fail loudly and early rather than in a locale-specific bug report.
Sequencing. Deliver in four gated phases:
Tier the work accordingly: this is appropriate for tactical, contained, high-read/low-write content features, and inappropriate for anything on an auth, billing, audit, or compliance path.
Maintenance. The fork is the maintenance unit, and it needs an internal owner with the authority to patch it. The recurring costs are explicit: 1–2 engineer-days per Laravel major upgrade; index management and caching owned internally (not by the package); and a runbook for a translations table that grows multiplicatively with models, attributes, and locales. Even if we do not adopt the package, the driver-contract pattern is a reasonable reference architecture for internal translation plumbing — particularly if per-tenant storage variance is on the horizon.
Support. There is no vendor assurance and effectively no support channel. Bus factor appears to be one, at an agency whose priorities follow client work, so upstream response time is not a schedule input. Plan for self-support: internal owner, fork, pinned version, and a documented fallback to an established alternative. Do not place this on a critical path where an unanswered GitHub issue blocks a release.
Scaling. Three scaling concerns need owners before traffic grows:
models × attributes × locales; without the composite unique and covering index every resolution is a scan. Verify at pilot, not after.toArray()/API-resource output during the spike, and budget a caching workstream if the number is unacceptable.Failure modes. Name them, with detection and mitigation attached:
Ramp-up. Cost is low but non-zero, and mostly documentation and discipline rather than training: the boundary rule (lang files vs. model content), the wrapper/repository pattern, the rollback flag, and the contract test. One internal owner, one onboarding page in the engineering docs, and a recorded decision log entry covering the three-way build-vs-adopt comparison and the spike findings. Executives should hear it as: a small MIT-licensed accelerator for a contained multilingual content feature, with real adoption caveats — 40 stars, zero dependents, single maintainer — mitigated by a short gated spike, a thin internal wrapper, a documented fallback, and a per-upgrade budget. Tactical accelerator, not a platform bet.
How can I help you explore Laravel packages today?