omaralalwi/lexi-translate
A Laravel package for managing multilingual translations: store and edit language keys in the database, switch locales at runtime, and translate model attributes with a simple trait, so you no longer need to hand-edit lang files.
Architecture fit. lexi-translate implements the well-established "translation table + polymorphic relation" pattern for multilingual Eloquent models. Each translatable model gains a morphMany relationship to a shared translations table keyed by (translatable_type, translatable_id, locale, field), and the package layers a cache in front of reads. This is architecturally sound for the common case (content catalogs, product/marketing copy, CMS entities) where the set of translated fields is stable and bounded. It competes directly with spatie/laravel-translatable (JSON column) and astrotomic/laravel-translatable (dedicated table). The morph approach is the right call when you need uniform cross-model queries ("all content in fr"), per-locale partial writes, and clean indexing on locale — and the wrong call when translated content is a single short string that a JSON column would handle with less join overhead. Confirm which pattern your data actually is before adopting.
Integration feasibility. Standard Laravel package shape: Composer install, publishable config and migrations, a trait applied to models, and a facade/helper surface for setting and retrieving locales. That is a low-friction, well-understood integration path — a Laravel developer can complete a spike in hours. The dependency depth appears shallow (framework + cache contracts), which limits transitive risk. The real integration work is not installing it; it is data shaping (morph map registration, backfilling existing content) and cache lifecycle design.
Technical risk. Rated honestly, this is a low-adoption, single-maintainer package (72 stars). That is the dominant risk, not the code. Specific technical concerns to validate during evaluation:
saved/deleted events, and does it bust on bulk/query()->update() paths that bypass events? Stale translations are a customer-visible defect.with('translations') actually collapse queries, or does each field access re-query?(translatable_type, translatable_id, locale, field), index on (translatable_type, translatable_id), and MySQL utf8mb4 index-length limits on the morph type column.redis/memcached vs file/array (the latter two will silently break in multi-node deployments).Key questions for the maintainer/repo review:
spatie later without touching domain code)?Stack fit. Designed for Laravel with Eloquent, requiring a supported PHP 8.x runtime and a relational database (MySQL/PostgreSQL/MariaDB) plus a shared cache backend. It fits cleanly into a conventional Laravel monolith or modular monolith. It is a poor fit for multi-tenant schemas where each tenant has its own database unless the migrations are tenant-scoped, and a poor fit for applications already standardized on a JSON-column translation strategy — running both patterns creates two sources of truth for locale content.
Migration path. For greenfield models, adoption is direct. For retrofits, plan a three-phase move:
For any model where translation writes are user-generated and latency-sensitive, do the backfill in batches with cache warming immediately after, so the first post-deploy requests don't stampede the database.
Compatibility. Check three things before committing: (a) the Composer constraint against your Laravel minor version, (b) table-name collision with any existing translations table (this is a real conflict if astrotomic/laravel-translatable is already present), and (c) whether the migration runs cleanly on your DB engine's index-length limits. Also confirm the package's morph type expectations align with your registered morph map — a mismatch produces silent empty results rather than an error, which is a nasty failure mode to debug.
Sequencing.
Maintenance. Single-maintainer, 72-star project. Budget for one of two outcomes: (a) the package is actively maintained and you take routine minor upgrades, or (b) it goes quiet and you become the de facto maintainer. MIT licensing means forking is legally trivial — the operational cost is the engineering time to own a fork, not legal friction. Mitigate by pinning an exact version, vendoring the package into a fork under your org if any critical bug appears, and reviewing the upstream repo quarterly.
Support. There is no vendor SLA. Internal ownership must be assigned explicitly — an individual or team on the hook for upgrades, security triage, and answering "why is this translation stale" at 2am. Add it to the dependency register with a named owner and a review cadence. Do not let it enter the codebase as an unowned transitive convenience.
Scaling. Two scaling axes matter. Row multiplication: the translations table grows as models × locales × translated fields; a 100k-product catalog across 6 locales and 4 fields is 2.4M rows before any history. Index (translatable_type, translatable_id) and (locale) deliberately, and monitor table size and index bloat. Cache pressure: the cache is the performance story, so watch hit ratio, Redis memory, and invalidation throughput. A bulk content import that updates thousands of rows will generate an invalidation storm — batch it outside peak hours and suppress per-row invalidation in favor of a targeted flush.
Failure modes to instrument for:
Ramp-up. Low for anyone fluent in Laravel — the trait-and-relation model is familiar, and a competent developer is productive in roughly half a day. The real learning curve is not the API but the cache and fallback semantics, which are where teams get burned. Produce a short internal runbook covering: how to add a new translatable model, how to add a locale, how to safely bulk-import translations, and how to force-invalidate a specific model's cache. Without that runbook, every future translation bug becomes an archaeology exercise against a small package's source.
How can I help you explore Laravel packages today?