terranet/translatable
A Laravel package for adding multilingual attributes to Eloquent models. Store translations per locale, retrieve values with automatic fallback, and manage translated fields without duplicating your schema—useful for building translatable applications quickly.
Architecture fit. terranet/translatable is a trait-plus-service-provider package in the classic Laravel 5.x idiom, built as a companion to Terranet's "Admin Architect" admin panel rather than as a standalone i18n layer. That origin matters: the package's abstractions (attribute accessors, admin form bindings, locale resolution) are shaped around one specific CRUD generator's expectations. If we are not running Admin Architect, we inherit the coupling without the benefit. Functionally it overlaps almost entirely with spatie/laravel-translatable and astrotomic/laravel-translatable, both of which have 1,000x+ the adoption. The only defensible reason to evaluate this package is if we are already standardized on Terranet's admin stack, or if a spike shows its storage model (single translations table vs. JSON column) maps better to our reporting needs than the alternatives.
Integration feasibility. Mechanically simple: MIT license, Composer-installable, small surface area (one trait, one provider, probably one or two migrations). The real work is a compatibility spike, not the install. The last release is March 2019 — Laravel 5.7/5.8, PHP 7.1/7.2 era. Packages of that vintage routinely break on Laravel 9+ through removed string/array helpers (array_get, str_slug), the $this->app container re-binding signatures, and PHP 8 reserved-word and type-juggling changes. Expect to spend 1–3 engineering days just establishing whether it boots on our target Laravel/PHP matrix, and another 1–2 days patching if it does.
Technical risk. High, concentrated in abandonment rather than in code complexity. Signals: 2 stars, 0 dependents, no release in ~6 years, no visible CI or security policy, a single small vendor org as the only maintainer. Zero dependents is the most damning data point — it means no third party has ever validated this code against a production workload we can learn from. The codebase is small enough that the risk is fully mitigable by forking, but that fork becomes our liability, not theirs. Secondary risk is data-integrity: translation writes are cheap to get subtly wrong (silent fallback, locale collision, mass-assignment of the locale key) and the package's test coverage is unknown.
Key questions for engineering.
translations polymorphic table or a JSON column — and which model serves our analytics/search requirements?null, the key, or the base-locale value?Stack fit. Poor as a greenfield choice, acceptable as an incremental one. It assumes Laravel's Eloquent trait conventions and, implicitly, an admin surface that generates locale tabs or per-locale fields. Our fit depends on two yes/no gates: (a) Laravel version compatibility confirmed in a spike, and (b) a concrete reason Spatie's package is insufficient. If either gate fails, stop.
Migration path. Two viable routes. Route A — fork and vendor: copy the source into an internal namespace, add laravel/framework and php constraints that reflect reality, add characterization tests around the trait's read/write/fallback paths, and publish internally. This is the only route that preserves the package's storage semantics if we need them. Route B — adopt Spatie: treat this evaluation as a rejected candidate and standardize on spatie/laravel-translatable, which gives us active maintenance, a documented JSON-column model, and a hiring pool that already knows it. Route B is the default recommendation unless the spike surfaces a genuine capability gap.
Compatibility. Do not trust composer.json constraints — verify them. Build a matrix (Laravel 9/10/11 × PHP 8.1/8.2/8.3), run composer require with --prefer-lowest and latest, and record real failures rather than inferred ones. Pay specific attention to removed global helpers, Str/Arr migrations, and any Illuminate\Foundation provider registration that changed after 5.8.
Sequencing. (1) Two-day spike: install, boot, write a translation, read it back, verify fallback, confirm schema. (2) Decision gate: fork vs. Spatie vs. reject. (3) If forking, wrap the trait behind an internal Translatable contract so application code never references the vendor namespace directly. (4) Only then migrate a single low-traffic model as a pilot. Do not sequence this ahead of any higher-scored dependency decision — with a 0.07 score and zero dependents, this is a background candidate, not a blocking one.
Maintenance. If we adopt, we own it. Budget 2–4 engineer-days in year one for the fork, compatibility patches, and characterization tests, then ~1 day/year of dependency drift. That is cheap in absolute terms but not free in attention: it is one more internal fork on the list of things that silently rot. If we reject, maintenance cost is zero and the evaluation cost is the two-day spike, which is sunk either way.
Support. Effectively none. No upstream responsiveness expectation, no security advisory channel, no issue tracker activity to mine for known failure modes. Internal support means one or two engineers who have read the source. Document the trait's behavior in the repo README — including the fallback semantics — because there is no upstream doc to link to and the original README is a 2019 artifact.
Scaling. The scaling risk is query shape, not storage volume. Naive translatable implementations issue one query per model per locale when eager loading is not configured correctly; on a listing page with 50 rows and 5 locales, that is 250 queries. Insist on eager-loading the translation relation and add a composite index on (locale, translatable_type, translatable_id) (or the JSON equivalent). If we expose translated content to search or reporting, the JSON-column model forces full scans — a translations table does not. This distinction should drive the architecture decision more than package preference.
Failure modes. The ones to design against: missing translation returning a null/empty string that renders as a blank UI element rather than falling back; concurrent saves overwriting a different locale's payload; locale code drift (en vs en_US vs en-us) silently orphaning rows; cache staleness if we layer caching over translation reads; and a schema mismatch after a Laravel upgrade that fails at write time rather than at boot, so it surfaces in production rather than CI. Each needs an explicit test.
Ramp-up. Low technical ramp-up — the API surface is small and a competent Laravel engineer will read the whole trait in under an hour. The real ramp-up cost is institutional: nobody outside the team that ran the spike will know this is a fork, why it exists, or what it does differently from Spatie. That is a documentation debt, and it is the strongest argument for Route B.
Verdict. Reject as a default dependency. Reconsider only if the compatibility spike passes cleanly and Admin Architect is already in the stack, in which case fork, wrap behind an internal contract, and cap the investment at the spike plus one pilot model.
How can I help you explore Laravel packages today?