webx-ui/localization
Webx UI Localization brings localization tools to your Laravel UI. It offers helpers and components for managing translations, switching locales, and keeping multilingual interfaces consistent, so you can ship apps that feel native across languages with less effort.
The storage-model decision — treat it as a one-way door and own it explicitly. This package answers a narrower question than "should we internationalize?" It answers: do we store translated content in per-locale columns/tables, or in a single JSON map on the row? It commits hard to the JSON map via HasTranslations and $table->translatable(). Migrating out of JSON maps into per-locale tables later is a data migration, not a config change. Make that call deliberately, with content volume and reporting needs in hand, before the first translated row is written, and record it with a named owner.
Feature scope for a localized public site plus an admin panel. The package ships the full minimum viable loop: a locales table with a single enforced default, a Locales service with fallback chaining, prefix-based request resolution, and panel dictionary publishing. This supports a roadmap decision like "ship v1 in N languages with prefix routing and an English-only admin UI" without writing locale plumbing. The 'strategy' => 'prefix' | 'header' | 'none' switch lets you defer the SEO/URL-structure debate — but it is a launch-time decision with permanent redirect implications, not a toggle you flip after launch.
SEO and URL architecture. OneSpellingPerAddress encodes a real product rule: unknown locale prefix → 404, default locale prefix → 301 to the unprefixed address unless prefix_default is on. PMs relitigate canonical-URL policy mid-project more often than any other SEO question. Having it enforced by middleware instead of by convention is worth a lot. Note the boundary: translated slugs are explicitly delegated to webx-ui/routing and are not covered here — scope that as a separate workstream or you will promise something this package cannot deliver.
Content lifecycle and data-retention policy. The docs make an opinionated, PM-relevant call: never delete a language to hide it — set is_active = false, because translated text persists in every JSON map. That underwrites a product policy ("we can retire a language from the UI without losing the corpus"), which matters for re-launch decisions, legal/archival obligations, and content-team trust. It also pre-empts the classic expensive mistake: an engineer deletes the row and the content is orphaned.
Localizing the admin experience as its own budget line. Panel language is a distinct concern from public-site language (webx.panel-locale vs webx.locale, plus a separate 'panel' config list). That lets you plan "public site in 6 languages, admin in 2" without conflating the two. It also surfaces the true cost: panel translation means translating published lang files per module — a recurring vendor-content cost that scales with modules × languages, not a one-time engineering task.
Translation completeness and fallback as a quality-vs-speed tradeoff. 'fallback' decides which language fills a gap. Read it as a shipping posture: fallback on an incomplete language produces mixed-language pages that users notice and report. Decide explicitly whether missing translations should fall back (ship faster, uglier) or be surfaced (ship slower, cleaner). The package supports either but defaults you into one — so the default becomes your policy unless you intervene.
Risk framing on the dependency itself. Stars 0, dependents 0, a read-only split of a monorepo. That is not a quality signal; it is a signal that this is an internal component published for the framework's own users. Your decision is therefore about adopting webx-ui as a platform, not about adopting this package à la carte. Frame it that way in any steering deck or the risk read will be wrong in both directions.
Consider it when:
module-admin, routing, cache); outside that stack you inherit only a fraction of the value.is_default via raw SQL, don't index translated slugs in the DB) is a support-cost reducer — it tells you which footguns the maintainers have already paid for.localization + routing + module-admin + cache as a three-to-four package unit, with pinning and upgrade rehearsals covering all of them.Look elsewhere when:
whereTranslation/orderByTranslation exist but will not match native-column performance at scale.routing's business.To executives — one paragraph. This is the localization layer of the webx-ui platform we would be adopting, not a standalone library. It gives us multi-language public pages and a translated admin panel with the unglamorous parts already solved: one enforced default language, correct SEO behavior for language-prefixed URLs, the ability to retire a language without data loss, and caching that stays coherent. The cost profile is low and predictable — MIT licensed, no vendor contract, no per-seat fees. Two caveats to carry forward: it is early-stage with no independent adoption, so we take on framework-coupling risk; and the content-storage model (translations in a JSON column) suits our content volume today but would need rework if we ever require database-level per-language uniqueness or heavy per-language reporting. Recommendation: adopt as part of a webx-ui platform decision, not as an isolated bet, and treat translated-slug handling as a separate workstream to be scoped with webx-ui/routing.
To engineering — the short version. Four things worth knowing before you estimate. (1) Storage: HasTranslations + $table->translatable() puts all locales in one nullable JSON column — no joins, no per-locale schema churn, but no per-locale indexes and no DB-level uniqueness; slugs are explicitly routing's problem. (2) State: the locales table wins over config after migration; config is seed-only; exactly one default is enforced by the model rather than the schema — so any bypass of the model requires a cache clear. (3) Request resolution is middleware aliases (webx.locale, webx.panel-locale) plus OneSpellingPerAddress, which makes unknown prefixes 404 and the default prefix a 301 — that is a tested invariant, don't reimplement it. (4) Extension is by published config, published lang tags, and model traits; editing vendor/ is unsupported, and a missing seam is a bug to report upstream rather than work around. Estimate panel translation separately — it scales with modules × languages, not with our code.
How can I help you explore Laravel packages today?