dimsav/laravel-translatable
Add multi-language support to any Eloquent model without touching your schema. laravel-translatable stores translations in a dedicated table and lets you read, write, and fall back between locales using familiar Eloquent syntax, making it ideal for multilingual apps and APIs.
Release review — v10.0.0
The first release after a long dormancy. Three changes, one of which is materially risky and none of which address the finding that drove the original assessment.
Locales helper class (#574) — consolidates locale list, fallback, and default-locale resolution into a single first-class object instead of config reads scattered through the trait. Additive and low risk; a useful seam for us if we ever fork (fewer places to patch locale logic). Watch for two sources of truth: the helper and the trait must both read config/translatable.php identically, or locale behavior diverges depending on code path.getRelationKey() fix (#575) — correctness fix. Behavior change for any model that overrides the translation relation name or $translationModel; audit those before upgrading.save() in favor of a saved event listener (#567) — the material change, and the reason this must be treated as breaking. Translation persistence moves from an overridden save path into Eloquent's saved event. Translation writes now depend on events actually being dispatched: saveQuietly(), Model::withoutEvents(), insert()/upsert() bulk paths, seeders, and any event-suppressing wrapper will persist the parent row and silently drop the translations. Observer ordering relative to translation writes also changes.10.0.0 number is a semver major — do not read it as "Laravel 10 support."Architecture fit
posts + post_translations) accessed through a Translatable trait and a $translatedAttributes array. Normalized side-table design, not JSON-column.whereTranslation('slug', ...), locale-scoped listings). Weaker fit for cross-locale full-text search or teams standardizing on JSON columns (spatie/laravel-translatable).Locales helper slightly improves the fit for multi-locale apps: locale configuration becomes an addressable object rather than implicit config reads, which helps when locale lists are runtime-driven. It also adds one more coupling point between our code and the package.Integration feasibility
require block of v10.0.0 before anything else. Three outcomes: (a) it supports our baseline — dimsav is viable again and the substitution argument weakens to a preference; (b) it does not — the successor swap stands as the recommended path; (c) partial support — successor wins on maintenance cadence and docs.astrotomic/laravel-translatable remains the safer default: same schema, same trait semantics, current docs, and active compatibility work. Revived upstream does not erase several years of missing maintenance; it only makes the fork contingency less urgent.Technical risk
saved-event refactor means translation persistence is now conditional on event dispatch. Any code path that suppresses events — saveQuietly(), withoutEvents(), raw bulk inserts, some seeder and import patterns — will write the parent and lose the translations with no error. This is a silent-data-loss class risk and it is new in this release.Key questions
composer.json actually allow — PHP and Laravel constraints? This single fact decides whether the archived-dependency disqualifier still stands.saveQuietly(), withoutEvents(), or bulk insert paths on translatable models? Any "yes" turns the saved-event refactor into a data-loss risk.$translationModel, and therefore depend on the pre-fix getRelationKey() behavior?Stack fit
astrotomic/laravel-translatable still wins on documentation and compatibility work. Prefer the successor unless we have a specific reason to stay.Migration path — dimsav 9.x → 10.0.0 (only if the compatibility gate passes)
require block against our PHP/Laravel baseline. If it fails, stop and route to the successor swap instead.config/translatable.php (locales, fallback, use_property_fallback). Verify the new Locales helper resolves the same values the trait previously read directly — test both paths explicitly.saveQuietly, withoutEvents, withoutObservers, raw insert/upsert, and seeder/import patterns touching translatable models. Each hit is a candidate for silent translation loss under the new saved-listener design.$translationModel; add explicit tests for relation resolution.translate(), translateOrNew(), getTranslation(), whereTranslation(), toArray() shape, and — new — a saveQuietly() test that asserts the documented behavior, whatever we decide it should be.Migration path — dimsav → successor (unchanged, still the default)
Dimsav\Translatable\Translatable imports; add the translatable contract where required.Compatibility
(parent_id, locale, slug) under utf8mb4.saved-listener design, translation writes are now ordered relative to observers, created/saved hooks, and transaction boundaries. Any observer that reads translations in a saved handler may now see stale or missing data depending on listener registration order.translations array shape is unchanged before and after, regardless of path.with('translations') everywhere; the lazy per-accessor load remains an N+1 hazard.Sequencing
composer.json; check upstream cadence. Output: dimsav-upgrade vs. successor-swap.Maintenance
Locales helper, adding a translated model, adding a locale) are still ours to author.Support
Scaling
with('translations') as a code-review rule; measure query counts per endpoint before and after the change.(locale, ...) indexes or listings degrade.saved-listener path changes the cost and timing profile of writes and interacts with bulk operations — re-measure import throughput, not just read paths.Failure modes
saveQuietly(), withoutEvents(), bulk inserts, and some seeder/import patterns persist the parent and drop translations with no error. Mitigation: static-analysis or grep-based lint rule, integration tests on quiet-save paths, and a row-count reconciliation check in import jobs.saved handlers may see stale data depending on listener registration order. Mitigation: contract tests on any observer that touches translations.Locales helper and the trait must resolve the same config; divergence produces locale-dependent behavior differences. Mitigation: a single test asserting both paths return identical locale sets and fallbacks.toArray() shape changes break mobile clients silently. Mitigation: contract tests on API responses.Ramp-up
$translatedAttributes, migration with composite unique index, factory states per locale, API contract test, and the event-suppression rule.How can I help you explore Laravel packages today?