Skip to content
Devix Open Source
Laravel package v1.0.0 MIT Alpha

Laravel Translations

Translation manager with import, export and missing-key finder.

composer require devix-labs/laravel-translations
PHP Laravel
Laravel Translations

Live demo coming soon

Laravel's own MessageSelector returns six plural indices for Arabic, so an Arabic trans_choice string needs six segments separated by pipes. English needs two, a translator who has only seen English gives you two, nothing complains — not the framework, not the existing managers — and in production trans_choice returns the wrong form. One step down, the same shape: :name present in the English and gone from the Arabic renders a page without the name while every test passes. Pointed at Laravel's own published Arabic pack, this finds two of those — mimes and mimetypes both drop :attribute. The incumbents are barryvdh at 83k/mo with 82 open issues and still v0.6.9, and joedixon at 25k/mo with no release since April 2023 and 'Laravel 11 Support' as its top issue; both are a database and a web UI bolted onto what is really a file problem, and their open issues are about exactly that. This is neither: six checks with reason codes, a non-zero exit for CI, a scanner that reads Blade, and an export and import that do not lose nested arrays.

What you get

Arabic needs six plural forms

Laravel's MessageSelector returns six plural indices for Arabic — for 0, 1, 2, 3, 11 and 100. A translator gives you two, nothing complains, and trans_choice picks the wrong one in production. The counts here come from that same getPluralIndex, so the linter and the framework cannot drift apart.

It finds bugs in Laravel's own translations

Run against the published Laravel-Lang Arabic pack, it reports mimes and mimetypes: both drop :attribute, so a user is told their file is the wrong type without being told which field. Those are real, today, in the pack most Arabic Laravel apps install.

No database, no UI, no production code

The service provider registers nothing outside the console. That is the incumbent's open question #445 — 'How can I use this lib in production environment?' — answered by not being there. It reads the files you already have and exits non-zero, so it lives in CI.

It reads Blade

@lang and @choice, along with __, trans, trans_choice, Lang::get and $t() in JavaScript — across PHP, Blade, JS, TS and Vue, with the file and line of every use. 'Find translations in blade files' is #442 on the incumbent, still open.

Six checks, as reason codes

missing, unused, placeholder, plural, empty and same — each with the key, the detail and where to go. --only and --except pick the ones your build should fail on, and --json hands the lot to your own tooling.

Nested arrays survive the round trip

Export and import both run through one flattener, with a test that takes a three-level array out and back unchanged. 'Translations get lost in export when there are sub-elements' is #456 on the incumbent.

A spreadsheet a translator can open

One column per locale so they see the English beside their own language, and a byte-order mark so Excel opens Arabic as Arabic rather than as mojibake. A blank cell means 'not yet', never 'erase it' — an importer that overwrites with nothing destroys work.

It does not guess

__($key) cannot be resolved without running the program, so those files are listed rather than guessed at. A scanner that invents keys produces false positives people learn to ignore, and then the tool is worth nothing.

Laravel Translations — overview

The bug that ships

Laravel's own MessageSelector returns six plural indices for Arabic — 0, 1, 2, 3, 4 and 5 for the counts 0, 1, 2, 3, 11 and 100. So an Arabic trans_choice string needs six segments separated by |.

English needs two. A translator who has only ever seen English gives you two. Nothing complains — not the framework, not the existing translation managers — and in production trans_choice('items', 3) returns the wrong form, or the raw string with its pipes showing.

One step down, the same shape: :name present in the English string and missing from the Arabic. The page renders without the name, and every test passes.

Pointed at Laravel's own published Arabic translations, this finds two of those: mimes and mimetypes both drop :attribute, so a user is told their file is the wrong type without being told which field.

Why a linter rather than another manager

Monthly Open issues Last release
barryvdh/laravel-translation-manager 83,357 82 v0.6.9
joedixon/laravel-translation 24,813 61 April 2023

Their open issues are not about missing features. They are about the shape:

  • #445 — "How can I use this lib in production environment?" It wants a database table and a web UI, and people do not want either on a production box.
  • #442 — the scanner does not read @lang in Blade.
  • #449 — the UI breaks when a string contains {...}.
  • #456 — nested arrays are lost in export.
  • #435 — export is broken for subdirectories.

Both are a database and a UI bolted onto a file problem, and the database and the UI are where they break. So this is neither: a set of commands over the files you already have, that runs in CI and cannot break in production because it does not run there. The service provider registers nothing outside the console.

The six checks

missing · unused · placeholder · plural · empty · same — each a reason code, with the file and line, and a non-zero exit.

The plural counts come from Laravel's own getPluralIndex, so the linter and the framework cannot drift apart. A string using explicit ranges — {0} none|[1,*] some — is the author declaring how many forms there are, and the check stands back.

One thing found by running it on real data

An earlier version reported الحقل:attribute as a missing placeholder. It is not: no space before a placeholder is ordinary in Arabic and Laravel replaces it perfectly well. The cause was a lookbehind of (?<![\w:]) — and PCRE2's \w under /u counts an Arabic letter as a word character, so every placeholder written that way was reported missing.

That would have made the tool useless for exactly the language it was written for, and no synthetic fixture would have caught it. It came from pointing the thing at Laravel's published Arabic pack and reading the output.

What we do not claim

That barryvdh's package is bad. It has 1,701 stars for a reason, and a web UI is genuinely what a non-technical translator wants. If you want people editing strings in a browser, use it — and use this in CI as well.

Translation memory or machine translation. Both are somebody else's API and neither belongs in a linter.

Perfect unused detection. A key reached through a variable looks unused. Those files are reported by dynamic() rather than guessed at, and unused is the check to leave out of a build.

How it compares

Questions

Why is a strength of six plural forms only a problem in Arabic?

Because Arabic is the language with six, and almost everybody writing the translation has only ever seen English's two. Russian needs three, Welsh and Slovenian four, Irish five, Japanese one. All of them are checked; Arabic is the one that costs people, because a pipe-separated string that looks complete to an English speaker is missing four forms.

Should I use this instead of barryvdh's translation manager?

Alongside it, if you want one. It has 1,701 stars for a reason and a web UI is genuinely what a non-technical translator wants. This is the other half: knowing what is broken, in CI, before it ships. What it is not is a replacement for a UI, and it does not pretend to be.

Will 'unused' give me false positives?

On keys reached through a variable, yes — and it says so. __($key) cannot be resolved without running the program, so those files are reported by dynamic() rather than guessed at, and unused is the check to leave out of a build. Everything else is exact.

Does it need a database or a migration?

No. There is no table, no migration and no UI, and the service provider registers nothing at all outside the console. That is deliberate: both existing packages put a table and a web interface in front of what is really a file problem, and their open issues are mostly about the table and the interface.

Can it translate for me?

No. Machine translation and translation memory are somebody else's API, they cost money, and neither belongs in a linter that is supposed to run on every commit. sync and export get the strings to a person who can translate them, and import brings them back without losing anything.