Reference
The classes, without artisan
Everything the commands do is a plain class, so the most useful thing you can do with this package is put it in your own test suite.
Fail a test rather than a build
use Devix\Translations\{Catalogue, Linter, Scanner};
public function test_the_arabic_translations_are_not_broken(): void
{
$used = (new Scanner)->scan([app_path(), resource_path()])->keys();
$problems = (new Linter(
Catalogue::load(lang_path(), 'en'),
[Catalogue::load(lang_path(), 'ar'), Catalogue::load(lang_path(), 'ur')],
$used,
))->run(['placeholder', 'plural', 'missing']);
$this->assertSame([], array_map('strval', $problems));
}
array_map('strval', …) is worth doing: when it fails, PHPUnit prints the actual
list of what is broken rather than "failed asserting that array is empty".
Catalogue
$en = Catalogue::load(lang_path(), 'en'); // both the PHP files and the JSON
$en->get('messages.nested.deep.key');
$en->has('messages.welcome');
$en->keys();
$en->sourceOf('messages.welcome'); // which file it came from
Catalogue::of('en', ['a.b' => 'Hello']); // from an array, for a test
Catalogue::flatten(['a' => ['b' => 'x']]); // ['a.b' => 'x']
Catalogue::expand(['a.b' => 'x']); // ['a' => ['b' => 'x']]
Scanner
$scanner = (new Scanner)->scan([app_path(), resource_path(), base_path('routes')]);
$scanner->keys(); // ['messages.welcome' => [['file' => …, 'line' => 12]], …]
$scanner->dynamic(); // files where the key is a variable
It reads __, trans, trans_choice, @lang, @choice, Lang::get and
$t() / $tc() in JavaScript — PHP, Blade, JS, TS and Vue.
Only literal keys are found, deliberately. __($key) cannot be resolved
without running the program, and a scanner that guesses produces false positives
people quickly learn to ignore. Those files are listed by dynamic() instead, so
you know where to look before trusting unused.
PluralRules
PluralRules::forms('ar'); // 6
PluralRules::forms('ar-EG'); // 6
PluralRules::forms('ru'); // 3
PluralRules::forms('en'); // 2
PluralRules::forms('ja'); // 1
PluralRules::countIn('one|many'); // 2
PluralRules::usesRanges('{0} none|[1,*]'); // true
The counts come from Laravel's own MessageSelector::getPluralIndex, so the
linter and the framework cannot disagree at runtime.
A string using explicit ranges — {0} none|[1,19] some|[20,*] many — is the
author saying how many forms there are, so the plural check stands back.
Problem
$problem->reason; // 'placeholder'
$problem->locale; // 'ar'
$problem->key; // 'messages.welcome'
$problem->detail; // 'missing :name — it will render as literal text'
$problem->where(); // 'app/Http/Controllers/HomeController.php:34'
(string) $problem; // '[placeholder] ar messages.welcome — missing :name …'
Porter
Porter::toCsv([$en, $ar]); // the spreadsheet
Porter::fromCsv($csv); // ['ar' => ['key' => 'value']]
Porter::write(lang_path(), 'ar', $lines); // the PHP and JSON files