localizer
Manage translations, scan codebase for missing keys, generate TypeScript translation files, auto-translate with Google, and detect locale per-request.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Manage translations, scan codebase for missing keys, generate TypeScript translation files, auto-translate with Google, and detect locale per-request.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | localizer |
| description | Manage translations, scan codebase for missing keys, generate TypeScript translation files, auto-translate with Google, and detect locale per-request. |
Activate this skill when:
ALWAYS use __() for all user-facing strings in Blade templates, controllers, notifications, and anywhere text is displayed to users:
// CORRECT
return __('Order has been placed successfully.');
session()->flash('message', __('Profile updated.'));
// WRONG — hardcoded English, cannot be translated
return 'Order has been placed successfully.';
ALWAYS use __() in Blade templates, never raw text:
{{-- CORRECT --}}
<h1>{{ __('Dashboard') }}</h1>
<p>{{ __('Welcome back, :name', ['name' => $user->name]) }}</p>
<button>{{ __('Save Changes') }}</button>
{{-- WRONG — untranslatable --}}
<h1>Dashboard</h1>
<button>Save Changes</button>
Use two key types based on convention:
__('Welcome back') → stored in lang/en.json__('validation.required') → stored in lang/en/validation.phpKey type is determined by the first dot-segment:
// PHP key — first segment "auth" is a valid identifier → stored in lang/en/auth.php
__('auth.failed')
__('validation.required')
__('messages.order.placed')
// JSON key — no dot, or first segment is not a simple identifier → stored in lang/en.json
__('Welcome')
__('Hello, :name')
__('Order #:id has been shipped.')
ALWAYS use placeholders with :name syntax for dynamic values. Never concatenate:
// CORRECT
__('Hello, :name! You have :count messages.', ['name' => $user->name, 'count' => $count])
// WRONG — broken for all non-English languages
__('Hello, ') . $user->name . __('! You have ') . $count . __(' messages.')
Use Localizer facade for programmatic translation management, not direct file manipulation:
use DevWizard\Localizer\Facades\Localizer;
// CORRECT — set a translation
Localizer::set('Welcome', 'Welcome to our platform', 'en');
Localizer::setPhp('messages.greeting', 'Hello there', 'en');
// CORRECT — bulk set
Localizer::bulkSet([
'Welcome' => 'Welcome',
'Goodbye' => 'Goodbye',
], 'en');
// CORRECT — bulk set to a PHP file
Localizer::bulkSetPhp('messages', [
'greeting' => 'Hello',
'farewell' => 'Goodbye',
], 'en');
// WRONG — directly writing to JSON/PHP files
file_put_contents(lang_path('en.json'), json_encode([...]));
ALWAYS run localizer:sync --all after adding new translatable strings to ensure all locales get the new keys:
php artisan localizer:sync --all
ALWAYS run localizer:generate --all after modifying any translations to regenerate TypeScript files for the frontend:
php artisan localizer:generate --all
ALWAYS use the LocalizerMiddleware for locale detection. Never manually call App::setLocale() in controllers:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\LocalizerMiddleware::class,
]);
})
The middleware detects locale in this strict priority order (first match wins):
?locale=fr — URL query stringX-Locale: fr — Request header (for API/SPA requests)session('locale') — Session value (persists across pages)$user->getLocale() — Authenticated user preference (if method exists)Accept-Language header — Browser preferenceconfig('localizer.default') — App defaultFor per-user locale persistence, implement getLocale() on the User model:
// In User model
public function getLocale(): string
{
return $this->locale ?? config('localizer.default');
}
ALWAYS register new locales in the config before using them:
// config/localizer.php
'available' => [
'en' => ['label' => 'English', 'flag' => '🇬🇧', 'dir' => 'ltr'],
'ar' => ['label' => 'Arabic', 'flag' => '🇸🇦', 'dir' => 'rtl'],
'ja' => ['label' => 'Japanese','flag' => '🇯🇵', 'dir' => 'ltr'],
],
To programmatically create a new locale with all existing keys pre-populated:
// Create 'ja' by copying all keys from 'en'
Localizer::create('ja', fromLocale: 'en');
ALWAYS use the __() function from the localizer package in React/Vue components. Never hardcode text:
// CORRECT — React
import { useLocalizer } from '@devwizard/laravel-localizer-react';
function MyComponent() {
const { __ } = useLocalizer();
return <h1>{__('Welcome')}</h1>;
}
// WRONG — hardcoded, untranslatable
function MyComponent() {
return <h1>Welcome</h1>;
}
<!-- CORRECT — Vue -->
<script setup>
import { useLocalizer } from '@devwizard/laravel-localizer-vue';
const { __ } = useLocalizer();
</script>
<template>
<h1>{{ __('Welcome') }}</h1>
</template>
<!-- WRONG -->
<template>
<h1>Welcome</h1>
</template>
ALWAYS use the same key in frontend __() as in backend __(). The keys must match exactly:
// Backend Blade
{{ __('Order placed successfully.') }}
// Frontend React — SAME key
__('Order placed successfully.')
For locale switching UI, use setLocale and availableLocales from the hook:
// React
const { __, locale, setLocale, availableLocales } = useLocalizer();
<select value={locale} onChange={e => setLocale(e.target.value)}>
{availableLocales.map(loc => (
<option key={loc} value={loc}>{loc}</option>
))}
</select>
<!-- Vue -->
<select v-model="locale" @change="setLocale(locale)">
<option v-for="loc in availableLocales" :key="loc" :value="loc">
{{ loc }}
</option>
</select>
NEVER manually create or edit files in resources/js/lang/. These are auto-generated by localizer:generate and will be overwritten.
For Inertia.js apps, the middleware automatically shares locale data. Access it via the page props:
// Available in all Inertia pages when middleware is active
const { locale } = usePage().props;
// locale = { current: 'en', dir: 'ltr', available: {...} }
Step 1: Install and configure
composer require devwizardhq/laravel-localizer
php artisan localizer:install
Step 2: Register middleware in bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\App\Http\Middleware\LocalizerMiddleware::class,
]);
})
Step 3: Add dir attribute to HTML layout for RTL support
<html lang="{{ app()->getLocale() }}" dir="{{ config('localizer.available.' . app()->getLocale() . '.dir', 'ltr') }}">
Step 4: Scan existing code for translation keys
php artisan localizer:sync --all
Step 5: Auto-translate to target languages
composer require stichoza/google-translate-php
php artisan localizer:translate --source=en --target=fr
php artisan localizer:translate --source=en --target=ar
Step 6: Generate TypeScript files for SPA
php artisan localizer:generate --all
<nav>
@foreach(config('localizer.available') as $code => $locale)
<a href="?locale={{ $code }}"
class="{{ app()->getLocale() === $code ? 'font-bold' : '' }}">
{{ $locale['flag'] }} {{ $locale['label'] }}
</a>
@endforeach
</nav>
// Send X-Locale header from frontend API client
axios.defaults.headers.common['X-Locale'] = currentLocale;
// The middleware automatically detects it — no controller logic needed
use DevWizard\Localizer\Facades\Localizer;
| Method | Purpose |
|---|---|
Localizer::get('en') | All translations (JSON + PHP merged) |
Localizer::getJson('en') | JSON translations only |
Localizer::getPhpTranslations('en', 'auth') | Single PHP file |
Localizer::getAllPhpTranslations('en') | All PHP files |
Localizer::set('key', 'value', 'en') | Set JSON key (HTML-encoded) |
Localizer::setPhp('file.key', 'value', 'en') | Set PHP key (dot-notation) |
Localizer::bulkSet([...], 'en') | Batch set JSON keys |
Localizer::bulkSetPhp('file', [...], 'en') | Batch set PHP keys |
Localizer::unset('key', 'en') | Remove JSON key |
Localizer::unsetPhp('file.key', 'en') | Remove PHP key |
Localizer::create('ja') | Create new locale (empty) |
Localizer::create('ja', fromLocale: 'en') | Create locale copying from another |
Localizer::delete('ja') | Delete locale and all its files |
Localizer::rename('old', 'new') | Rename locale |
Localizer::translate('en', 'fr') | Auto-translate (queued job) |
Localizer::availableLocales() | List all locale codes |
config/localizer.php)'default' => env('APP_LOCALE', 'en'),
'fallback' => env('APP_FALLBACK_LOCALE', 'en'),
'available' => [
'en' => ['label' => 'English', 'flag' => '🇬🇧', 'dir' => 'ltr'],
// Add more locales here
],
'path' => lang_path(),
'typescript_output_path' => resource_path('js/lang'),
'scan' => [
'include' => [app_path(), resource_path(), base_path('routes')],
'exclude' => [base_path('bootstrap'), lang_path(), public_path(), storage_path(), base_path('vendor'), base_path('node_modules')],
'extensions' => ['php', 'blade.php', 'js', 'jsx', 'ts', 'tsx', 'vue'],
],
php artisan localizer:sync --all # Scan code and add missing keys to all locales
php artisan localizer:sync --locales=en,fr # Sync specific locales only
php artisan localizer:generate --all # Generate TypeScript files for all locales
php artisan localizer:generate --locales=en # Generate for specific locales
php artisan localizer:translate --source=en --target=fr # Auto-translate (queued)
| Anti-Pattern | Correct Pattern |
|---|---|
<h1>Welcome</h1> in Blade | <h1>{{ __('Welcome') }}</h1> |
return 'Success'; in controller | return __('Success'); |
'Hello, ' . $name concatenation | __('Hello, :name', ['name' => $name]) |
App::setLocale('fr') in controller | Use LocalizerMiddleware — it handles everything |
Manually editing resources/js/lang/*.ts | Run php artisan localizer:generate --all |
Manually editing lang/*.json files | Use Localizer::set() or Localizer::bulkSet() |
| Using different keys in backend vs frontend | Use the EXACT same key string in __() everywhere |
Forgetting to run sync after adding strings | ALWAYS run localizer:sync --all then localizer:generate --all |
Not registering locale in config available | Middleware rejects unknown locales silently |