awes-io/localization-helper
A Laravel localization helper that scans your codebase for translation keys, finds missing or unused strings, and helps keep language files in sync. Streamline multi-language app management with handy Artisan commands and a clear view of translation coverage.
Install the package into an existing Laravel app (it targets Laravel's Blade compiler and translator, so it should be added to the same app you render views from):
composer require awes-io/localization-helper
Check for a publishable config and any registered Blade directive / helper function:
php artisan vendor:publish --provider="AwesIO\LocalizationHelper\LocalizationHelperServiceProvider"
php artisan list | grep -i locali
You are looking for two things: a config file (usually controlling the key-generation strategy / prefix) and an artisan command that scans Blade views and writes translation files.
First use case — replace a hardcoded string in a Blade view with the package directive:
{{-- before --}}
<h1>Welcome to our app</h1>
{{-- after --}}
<h1>@l('Welcome to our app')</h1>
Then run the collect/extract command (whatever php artisan list shows — commonly localization:collect). It scans resources/views for every @l(...) / __l(...) call and generates a translation file for the source locale. That generated file is the artifact you hand to translators or feed into your TMS.
Verify by switching locale (app()->setLocale('...')) and confirming the rendered output. If the key isn't found, the generated file is stale — re-run the collect command.
The mental model: the package is a build-time string extractor bolted onto Blade, not a runtime translation engine. __()/@lang() need the key to exist already; @l() lets you write source text inline and have the key materialized later.
Inline strings with placeholders. The value of the directive is that the source text and its parameters live in the same place:
<p>@l('Hello, :name! You have :count unread messages.', ['name' => $user->name, 'count' => $count])</p>
This keeps the view readable and gives the extractor everything it needs to produce a key plus a parameterized string for translators.
Treat generated keys as opaque. Don't try to hand-edit keys the package generates (hash/slug from the source text). If you need a stable, human-readable key, prefer a hybrid workflow: use __('messages.welcome') for long-lived strings, and @l() for content-heavy marketing views where inline authoring beats managing a key.
Programmatic helper in PHP. Mirror the Blade usage in controllers/services with the corresponding helper (__l(...) if exposed) so strings authored outside views are also picked up by the same extraction pass. Don't mix @l() and __() in the same feature if you rely on extraction — __() calls are invisible to the scanner.
CI / build pipeline. The collect command is the crux of the workflow. A sane pipeline:
# in CI, after code changes
php artisan localization:collect --source=en # or equivalent flags
git diff --exit-code lang/ || (echo "translations out of date" && exit 1)
This turns "did someone forget to re-run the extractor?" into a build failure instead of a production bug.
Namespacing / file organization. If the package lets you split by file or directory (most do via a config paths option), split by feature (lang/en/auth.json, lang/en/billing.json). It keeps diffs reviewable and lets translators work on isolated domains.
Locale bootstrapping. Wrap your first usage in a small feature test with a second locale to prove the generated key actually resolves:
public function test_welcome_string_translates(): void
{
app()->setLocale('fr');
$this->blade('<h1>@l(\'Welcome to our app\')</h1>')
->assertSee(__('_hash_or_key_for_welcome'));
}
@l() compiles to a key derived from the source string. Change the source text → key changes → the generated file no longer contains it → you get the raw source string (or an empty output) at runtime. Always re-run the collect command after editing @l() text.php artisan view:clear — otherwise you're debugging the old compiled PHP in storage/framework/views.@l('...') (apostrophes like "it's") breaks Blade parsing. Escape or use @l("It's ready"), and prefer the helper function inside PHP.@l() output is escaped by default. If it isn't, you're opening an XSS hole the moment a translation file is sourced from a translator or API. Prefer explicit {{ }} wrapping or the built-in escaped variant; keep raw output for a deliberately small allowlist.@l() string is an untranslated string — every edit is a translation-churn event. For frequently-worded UI copy, fall back to explicit keys.@l($dynamicVar) cannot be extracted. Keep the first argument a literal at all times if you want it picked up.php artisan config:cache) in production — the package's paths/output config is read at boot; a half-cached config is a common "works locally, not on prod" cause.composer.json and re-run your translation-diff CI job on upgrade to catch API drift in the directive signature.How can I help you explore Laravel packages today?