laravel-internationalization-and-translation
Build with i18n in mind from day one using Laravel translation helpers, JSON files, Blade integration, and locale management
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Build with i18n in mind from day one using Laravel translation helpers, JSON files, Blade integration, and locale management
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Enforces an opinionated UI baseline to prevent AI-generated interface slop.
Build apps with the Claude API or Anthropic SDK. TRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`/`claude_agent_sdk`, or user asks to use Claude API, Anthropic SDKs, or Agent SDK. DO NOT TRIGGER when: code imports `openai`/other AI SDK, general programming, or ML/data-science tasks.
React composition patterns that scale. Use when refactoring components with boolean prop proliferation, building flexible component libraries, or designing reusable APIs. Triggers on tasks involving compound components, render props, context providers, or component architecture. Includes React 19 API changes.
Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".
Use when writing blog posts or documentation markdown files - provides writing style guide (active voice, present tense), content structure patterns, and MDC component usage. Overrides brevity rules for proper grammar. Use nuxt-content for MDC syntax, nuxt-ui for component props.
| name | laravel-internationalization-and-translation |
| description | Build with i18n in mind from day one using Laravel translation helpers, JSON files, Blade integration, and locale management |
Build your Laravel application with internationalization in mind from the start. Even if you're only supporting one language initially, wrapping strings in translation functions makes future localization much easier.
// BAD: Hardcoded strings are difficult to change later
return view('welcome', [
'message' => 'Welcome to our application!'
]);
// GOOD: Translatable from day one
return view('welcome', [
'message' => __('Welcome to our application!')
]);
// config/app.php
return [
'locale' => 'en',
'fallback_locale' => 'en',
'available_locales' => ['en', 'es', 'fr', 'de', 'ja'],
'faker_locale' => 'en_US',
];
// app/Http/Middleware/SetLocale.php
class SetLocale
{
public function handle(Request $request, Closure $next): Response
{
$locale = $request->user()?->locale
?? session('locale')
?? $request->getPreferredLanguage(config('app.available_locales'))
?? config('app.locale');
app()->setLocale($locale);
return $next($request);
}
}
// lang/en/messages.php
return [
'welcome' => 'Welcome to :app_name',
'greeting' => 'Hello, :name!',
'item_count' => '{0} No items|{1} One item|[2,*] :count items',
'account' => [
'profile' => 'Profile',
'settings' => 'Settings',
'logout' => 'Sign out',
],
];
// lang/es/messages.php
return [
'welcome' => 'Bienvenido a :app_name',
'greeting' => '¡Hola, :name!',
'item_count' => '{0} Sin artículos|{1} Un artículo|[2,*] :count artículos',
'account' => [
'profile' => 'Perfil',
'settings' => 'Configuración',
'logout' => 'Cerrar sesión',
],
];
// lang/en.json
{
"Welcome back!": "Welcome back!",
"Your order has been confirmed": "Your order has been confirmed",
"Please verify your email address": "Please verify your email address"
}
// lang/es.json
{
"Welcome back!": "¡Bienvenido de nuevo!",
"Your order has been confirmed": "Su pedido ha sido confirmado",
"Please verify your email address": "Por favor verifica tu dirección de correo"
}
// Using __() helper
echo __('messages.welcome', ['app_name' => config('app.name')]);
// Using trans() helper
echo trans('messages.greeting', ['name' => $user->name]);
// Direct strings (uses JSON files)
echo __('Welcome back!');
// Pluralization
echo trans_choice('messages.item_count', $count, ['count' => $count]);
{{-- Basic translation --}}
<h1>{{ __('messages.welcome', ['app_name' => config('app.name')]) }}</h1>
{{-- Direct string translation --}}
<p>{{ __('Please verify your email address') }}</p>
{{-- With @lang directive --}}
<p>@lang('messages.greeting', ['name' => $user->name])</p>
{{-- Pluralization --}}
<p>{{ trans_choice('messages.item_count', $cart->count(), ['count' => $cart->count()]) }}</p>
{{-- Inside attributes --}}
<button title="{{ __('Click to continue') }}">
{{ __('Next') }}
</button>
// app/Models/Product.php
use Spatie\Translatable\HasTranslations;
class Product extends Model
{
use HasTranslations;
public $translatable = ['name', 'description'];
protected $casts = [
'name' => 'array',
'description' => 'array',
];
}
// Usage
$product = Product::create([
'name' => [
'en' => 'Laptop',
'es' => 'Portátil',
'fr' => 'Ordinateur portable',
],
'description' => [
'en' => 'High-performance laptop',
'es' => 'Portátil de alto rendimiento',
'fr' => 'Ordinateur portable haute performance',
],
]);
// Automatic locale detection
echo $product->name; // Uses app()->getLocale()
// Specific locale
echo $product->getTranslation('name', 'es'); // Portátil
// routes/web.php
Route::middleware(['web', 'setlocale'])->group(function () {
Route::get('/', [HomeController::class, 'index'])->name('home');
// Localized routes
Route::prefix('{locale}')
->where(['locale' => '[a-z]{2}'])
->middleware('locale')
->group(function () {
Route::get('/', [HomeController::class, 'index'])->name('localized.home');
Route::get('/about', [PageController::class, 'about'])->name('localized.about');
});
});
// app/Http/Middleware/LocaleMiddleware.php
class LocaleMiddleware
{
public function handle($request, Closure $next)
{
if ($locale = $request->route('locale')) {
if (in_array($locale, config('app.available_locales'))) {
app()->setLocale($locale);
session(['locale' => $locale]);
}
}
return $next($request);
}
}
// lang/en/validation.php
return [
'required' => 'The :attribute field is required.',
'email' => 'The :attribute must be a valid email address.',
'custom' => [
'email' => [
'required' => 'We need your email address!',
'unique' => 'This email is already registered.',
],
],
'attributes' => [
'email' => 'email address',
'name' => 'full name',
],
];
// In FormRequest
class RegisterRequest extends FormRequest
{
public function messages()
{
return [
'email.required' => __('validation.custom.email.required'),
'email.unique' => __('validation.custom.email.unique'),
];
}
public function attributes()
{
return [
'email' => __('validation.attributes.email'),
'name' => __('validation.attributes.name'),
];
}
}
// app/Helpers/LocaleHelper.php
class LocaleHelper
{
public static function formatDate(Carbon $date): string
{
return match(app()->getLocale()) {
'en' => $date->format('m/d/Y'),
'es' => $date->format('d/m/Y'),
'de' => $date->format('d.m.Y'),
'ja' => $date->format('Y年m月d日'),
default => $date->toDateString(),
};
}
public static function formatCurrency(float $amount, string $currency = 'USD'): string
{
$formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::CURRENCY);
return $formatter->formatCurrency($amount, $currency);
}
public static function formatNumber(float $number, int $decimals = 2): string
{
$formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::DECIMAL);
$formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals);
return $formatter->format($number);
}
}
// Usage in Blade
<p>{{ LocaleHelper::formatDate($order->created_at) }}</p>
<p>{{ LocaleHelper::formatCurrency($order->total, 'EUR') }}</p>
<p>{{ LocaleHelper::formatNumber($product->weight, 2) }} kg</p>
{{-- resources/views/components/language-switcher.blade.php --}}
<div class="language-switcher">
<select onchange="window.location.href=this.value"
aria-label="{{ __('Select Language') }}">
@foreach(config('app.available_locales') as $locale)
<option value="{{ route('locale.switch', $locale) }}"
@selected(app()->getLocale() === $locale)>
{{ strtoupper($locale) }}
</option>
@endforeach
</select>
</div>
{{-- Or as links --}}
<ul class="language-menu">
@foreach(config('app.available_locales') as $locale)
<li>
<a href="{{ route('locale.switch', $locale) }}"
@class(['active' => app()->getLocale() === $locale])>
{{ __("languages.$locale") }}
</a>
</li>
@endforeach
</ul>
// app/Mail/OrderConfirmation.php
class OrderConfirmation extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public Order $order,
public string $locale
) {}
public function build()
{
return $this->locale($this->locale)
->subject(__('mail.order_confirmation.subject'))
->view('emails.order-confirmation');
}
}
// Send email in user's language
Mail::to($user)->send(new OrderConfirmation($order, $user->locale));
// app/Http/Controllers/TranslationController.php
class TranslationController extends Controller
{
public function index(Request $request)
{
$locale = $request->get('locale', app()->getLocale());
$translations = Cache::remember("translations.{$locale}", 3600, function () use ($locale) {
return collect(File::allFiles(lang_path($locale)))
->flatMap(function ($file) use ($locale) {
return [
$file->getBasename('.php') => include $file->getPathname()
];
})->toJson();
});
return response($translations)->header('Content-Type', 'application/json');
}
}
// In JavaScript
const translations = await fetch(`/api/translations?locale=${locale}`).then(r => r.json());
function __(key, replace = {}) {
let translation = key.split('.').reduce((t, i) => t?.[i], translations) || key;
Object.keys(replace).forEach(key => {
translation = translation.replace(`:${key}`, replace[key]);
});
return translation;
}
// Usage
console.log(__('messages.welcome', { app_name: 'MyApp' }));
// resources/js/i18n.js
import { createI18n } from 'vue-i18n';
const messages = {
en: require('./locales/en.json'),
es: require('./locales/es.json'),
fr: require('./locales/fr.json'),
};
export default createI18n({
locale: document.documentElement.lang || 'en',
fallbackLocale: 'en',
messages,
});
// In Vue component
<template>
<h1>{{ $t('messages.welcome', { app_name: appName }) }}</h1>
</template>
test('displays content in selected language', function () {
// Test English
$response = $this->withSession(['locale' => 'en'])
->get('/');
$response->assertSee('Welcome to our application');
// Test Spanish
$response = $this->withSession(['locale' => 'es'])
->get('/');
$response->assertSee('Bienvenido a nuestra aplicación');
});
test('falls back to default locale for missing translations', function () {
app()->setLocale('fr');
// If French translation missing, falls back to English
expect(__('some.missing.key'))->toBe('some.missing.key');
expect(__('messages.welcome', ['app_name' => 'Test']))->not->toBeEmpty();
});
test('pluralization works correctly', function () {
expect(trans_choice('messages.item_count', 0))->toBe('No items');
expect(trans_choice('messages.item_count', 1))->toBe('One item');
expect(trans_choice('messages.item_count', 5, ['count' => 5]))->toBe('5 items');
});
Always wrap user-facing strings
// Even in single-language apps
flash(__('Your changes have been saved.'));
Use meaningful translation keys
// BAD
__('msg1')
// GOOD
__('auth.login_successful')
Group related translations
// lang/en/auth.php
return [
'login' => 'Sign in',
'logout' => 'Sign out',
'register' => 'Register',
'forgot_password' => 'Forgot your password?',
];
Provide context in keys
// Different contexts may need different translations
__('button.save') // "Save"
__('message.save') // "Your changes will be saved"
__('title.save') // "Save Document"
Handle missing translations gracefully
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Log missing translations in production
if (app()->environment('production')) {
Event::listen(TranslationNotFound::class, function ($event) {
Log::warning('Missing translation', [
'key' => $event->key,
'locale' => $event->locale,
]);
});
}
}
}
Cache translations in production
# Cache for better performance
sail artisan lang:cache
# Clear when updating
sail artisan lang:clear
Remember: Start with translations from day one. It's much easier to maintain translations as you build than to retrofit them later!