phpmyadmin/motranslator
A PHP library for translating strings using Gettext MO files, with fast in-memory lookup, a Gettext-compatible API, and support for plural forms and contexts. No eval required.
Architecture fit. motranslator is a framework-agnostic library with a clean internal layering: MoParser → CacheInterface (InMemoryCache / ApcuCache) → Translator (gettext, ngettext, pgettext, npgettext) → optional Loader that emulates gettext's setlocale/textdomain/bindtextdomain workflow. Nothing in it knows about Laravel: no ServiceProvider, no container bindings, no config, no facade.
The deeper fit problem is model mismatch. Laravel's translator (Illuminate\Contracts\Translation\Translator) is key-based, dot-namespaced, with pipe-delimited pluralization delegated to symfony/translation's MessageSelector. motranslator is msgid-based — the English source string is the key — with plural forms resolved by gettext plural equations parsed via symfony/expression-language. These are two different translation models, not a configuration difference. Any integration is impedance matching, and the impedance shows up in every __() call the team writes.
Integration feasibility. Three options, in increasing blast radius:
Translator (or a thin wrapper) as a singleton, use it explicitly in one bounded surface — e.g. marketing copy or a help center. Days of work, low risk, easy to abandon.Illuminate\Translation\LoaderInterface backed by MO, plus a translator decorator mapping __('group.msgid') to pgettext. Touches validation messages, pagination, auth, Blade @lang, trans_choice, and every package's strings. High risk; not a first move.Loader::loadFunctions()). Reject. Laravel already defines __() and friends in Foundation/helpers.php; motranslator added function guards in 5.3.1, so which implementation wins depends on load order. That is silent, environment-dependent behavior — exactly the class of bug that surfaces in production only.Technical risk.
symfony/expression-language dependency must resolve against the framework's pinned Symfony constraint — verify before planning.eval() (the reason this library exists, per CVE-2016-6175), but treat MO as a validated artifact.Key questions.
Stack fit. A v1 adapter is genuinely small. Require the package, bind PhpMyAdmin\MoTranslator\Translator (or a wrapper) as a singleton, and resolve the locale from Laravel's existing locale state (App::getLocale(), the SetLocale middleware) rather than calling Loader::setlocale(). Since 4.0 the library no longer mutates system locales, which makes it safe to run alongside Laravel — but two sources of locale truth will still drift. Pick Laravel's.
Keep the boundary visible in code review: expose a distinct helper or Blade directive (e.g. @mo('Source string')) rather than overloading __(). This preserves the option to remove the library file-by-file if the pilot fails.
A full replacement additionally needs: a LoaderInterface implementation returning group→array from MO; a translator decorator handling the msgid/context mapping; and a hard decision on plurals. Do not mix gettext ngettext with Laravel's | pipes in the same string — enforce that with a static check, because it will otherwise be discovered in a translated UI.
Migration path. There is none natively — MO files are compiled from PO, so the source of truth shifts from lang/*.php / lang/*.json to PO. That is a real process migration: add a msgfmt (or equivalent) build step, produce .mo as a release artifact, version it with the app, and hand translators a Weblate/Poedit/Crowdin workflow. If the team is not already on gettext tooling, this migration cost usually exceeds whatever MO buys over Laravel's native PHP/JSON files.
Compatibility. UTF-8 in and out, end to end — the library's stated limitation. A mismatched charset produces mojibake with no error thrown. MO files must be treated as trusted, pinned build artifacts.
Sequencing.
oscarotero/gettext (PO and MO read/write, broader adoption), a Symfony XLIFF/YAML loader riding on components Laravel already ships, or a SaaS i18n SDK. Write down why motranslator wins, if it does.Maintenance. The package is well-run for its size: changelog discipline, GitHub Actions tests, coverage tracking, sensible majors. The asymmetry to plan for is that majors drop PHP versions aggressively (6.0.0 removed 8.0/8.1), so an app that lags on PHP will be pinned to an older motranslator and eventually to an older Laravel. Budget the adapter as internal code with a named owner; no upstream will carry it.
Support. No vendor, no SLA. Escalation means your engineers reading the source — which is feasible here (small codebase, clear layering), and is the main mitigation for the low-adoption signal. Weigh that against a commercially supported alternative if the surface is customer-facing.
Scaling. Memory is the axis that bites. InMemoryCache loads every string of every loaded domain into every PHP-FPM worker; cost scales as strings × locales × workers. The documented APCu path is the right answer at scale — one parse per server, performance near native gettext — but it shifts the problem to ops: apc.shm_size sizing (and possibly apc.shm_segments), TTL, and a cache key prefix to avoid collisions on shared hosts. Reduce blast radius by splitting MO files into domains and loading lazily rather than eagerly.
Failure modes, ranked by likelihood × impact.
reloadOnMiss=false → users see raw English source strings. Silent and user-visible.reloadOnMiss=true with a high miss rate → repeated MO reparse and latency spikes; the docs call this out explicitly and it is easy to ship without noticing.parseIntoCache reload; needs a deploy hook.Ramp-up. Developers fluent in __('messages.welcome') will initially write __('Welcome') and get confusing results; expect roughly a sprint of friction plus review discipline. Translators face a tooling change if they currently edit PHP/JSON — that is a retraining cost and a process owner, not just a checkbox. Ops must internalize APCu sizing and the deploy-time reload path. Net assessment: acceptable ramp for a team already on gettext, steep for one that is not.
Bottom line. Viable as a narrowly scoped MO reader running alongside Laravel's native translator, justified only when existing PO/MO tooling and translator workflows must be preserved. Not viable as a drop-in replacement for Laravel's translator, and gated on GPL-2.0 clearance from legal before the first line of adapter code is written.
How can I help you explore Laravel packages today?