I18n
Skill aronpc/ai/skills/i18n
Implementa internacionalização EN/ES/PT-BR com traduções e integração de enums em Laravel. Use quando precisar adicionar traduções, localização, ou suporte multi-idioma.From its SKILL.md
npx -y skills add aronpc/ai --skill i18nAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
16.0 KB, as published. Nobody here has run it
i18n
Resumo
Implementa internacionalização completa (EN, ES, PT-BR) com traduções e enums.
Skills Relacionadas
| Skill | Quando usar junto |
|---|---|
enums | Para criar enums traduzíveis |
ux | Para UX com mensagens traduzidas |
coder | Para implementar traduções |
testing | Para testar localização |
Quando usar
Use esta skill sempre que:
- Criar qualquer feature com interface de usuário
- Adicionar novos idiomas
- Criar Enums
- Traduzir mensagens de validação
- Traduzir resources Filament
- Atualizar arquivos de tradução
Princípios Fundamentais
Core Rules
- TODO texto DEVE ser traduzido para: English (en), Spanish (es), Portuguese BR (pt_BR)
- ZERO strings hardcoded - todo texto voltado ao usuário deve usar
__() - Chaves consistentes - use nomenclatura semântica (ex:
messages.welcome, nãomessages.msg1) - Context-aware - organize por domínio/feature, não por página
- Pluralização - use pluralização do Laravel para itens contáveis
- Fallback - English é idioma padrão/fallback
Estrutura de Arquivos
lang/
├── en/ # English (default)
│ ├── auth.php # Authentication & authorization messages
│ ├── enums.php # Enum labels (status, types, roles)
│ ├── exceptions.php # Exception messages
│ ├── fields.php # Form field labels
│ ├── messages.php # General UI messages
│ ├── navigation.php # Menu & navigation items
│ ├── pagination.php # Pagination text (Laravel default)
│ ├── passwords.php # Password reset messages (Laravel default)
│ └── validation.php # Validation messages (Laravel default)
├── es/ # Spanish (Español)
│ └── (same structure)
└── pt_BR/ # Portuguese Brazil (Português BR)
└── (same structure)
Exemplos de Arquivos de Tradução
messages.php - General UI Messages
<?php
// lang/en/messages.php
return [
'welcome' => 'Welcome to :app',
'dashboard' => 'Dashboard',
'saved_successfully' => ':resource saved successfully!',
'confirm_delete' => 'Are you sure you want to delete this :resource?',
'resources' => [
'business' => 'business',
'menu_item' => 'menu item',
],
];
// lang/es/messages.php
return [
'welcome' => 'Bienvenido a :app',
'dashboard' => 'Panel de control',
'saved_successfully' => '¡:resource guardado con éxito!',
'confirm_delete' => '¿Estás seguro de que deseas eliminar este :resource?',
'resources' => [
'business' => 'negocio',
'menu_item' => 'ítem del menú',
],
];
// lang/pt_BR/messages.php
return [
'welcome' => 'Bem-vindo ao :app',
'dashboard' => 'Painel',
'saved_successfully' => ':resource salvo com sucesso!',
'confirm_delete' => 'Tem certeza de que deseja excluir este :resource?',
'resources' => [
'business' => 'negócio',
'menu_item' => 'item do cardápio',
],
];
fields.php - Form Field Labels
<?php
// lang/en/fields.php
return [
'name' => 'Name',
'email' => 'Email',
'phone' => 'Phone',
'description' => 'Description',
'price' => 'Price',
];
// lang/es/fields.php
return [
'name' => 'Nombre',
'email' => 'Correo electrónico',
'phone' => 'Teléfono',
'description' => 'Descripción',
'price' => 'Precio',
];
// lang/pt_BR/fields.php
return [
'name' => 'Nome',
'email' => 'E-mail',
'phone' => 'Telefone',
'description' => 'Descrição',
'price' => 'Preço',
];
enums.php - Enum Labels
<?php
// lang/en/enums.php
return [
'business_type' => [
'restaurant' => 'Restaurant',
'cafe' => 'Café',
],
'order_status' => [
'pending' => 'Pending',
'confirmed' => 'Confirmed',
],
];
// lang/es/enums.php
return [
'business_type' => [
'restaurant' => 'Restaurante',
'cafe' => 'Cafetería',
],
'order_status' => [
'pending' => 'Pendiente',
'confirmed' => 'Confirmado',
],
];
// lang/pt_BR/enums.php
return [
'business_type' => [
'restaurant' => 'Restaurante',
'cafe' => 'Cafeteria',
],
'order_status' => [
'pending' => 'Pendente',
'confirmed' => 'Confirmado',
],
];
Padrões de Uso
1. PHP (Controllers, Actions, Classes)
<?php
// ✅ CORRECT - Simple translation
$message = __('messages.welcome');
// ✅ CORRECT - With parameters
$message = __('messages.saved_successfully', [
'resource' => __('messages.resources.business')
]);
// ✅ CORRECT - Pluralization
$count = 5;
$message = trans_choice('messages.items_count', $count, ['count' => $count]);
// ❌ WRONG - Hardcoded text
$message = 'Business created successfully!';
// ❌ WRONG - Concatenation
$message = __('messages.you_have') . ' ' . $count . ' ' . __('messages.items');
2. Blade Templates
{{-- ✅ CORRECT - Simple translation --}}
<h1>{{ __('messages.dashboard') }}</h1>
{{-- ✅ CORRECT - With parameters --}}
<p>{{ __('messages.welcome', ['app' => config('app.name')]) }}</p>
{{-- ✅ CORRECT - Form labels --}}
<label>{{ __('fields.name') }}</label>
{{-- ❌ WRONG - Hardcoded text --}}
<h1>Dashboard</h1>
3. Filament (Admin & Owner Panels)
<?php
use Filament\Forms;
use Filament\Tables;
// ✅ CORRECT - Form fields
Forms\Components\TextInput::make('name')
->label(__('fields.name'))
->placeholder(__('fields.name'))
->required();
// ✅ CORRECT - Table columns
Tables\Columns\TextColumn::make('name')
->label(__('fields.name'))
->searchable();
// ✅ CORRECT - Resource labels
public static function getNavigationLabel(): string
{
return __('navigation.businesses');
}
public static function getLabel(): ?string
{
return __('messages.resources.business');
}
// ❌ WRONG - Hardcoded labels
protected static ?string $navigationLabel = 'Businesses';
Enums com Tradução
CRÍTICO: TODOS Enums DEVEM implementar interface HasLabel e usar translation keys.
<?php
declare(strict_types=1);
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasLabel;
enum BusinessType: string implements HasLabel, HasColor
{
case RESTAURANT = 'restaurant';
case CAFE = 'cafe';
// ✅ CORRECT - Using translation keys
public function label(): string
{
return match ($this) {
self::RESTAURANT => __('enums.business_type.restaurant'),
self::CAFE => __('enums.business_type.cafe'),
};
}
public function color(): string
{
return match ($this) {
self::RESTAURANT => 'success',
self::CAFE => 'warning',
};
}
// Helper for Filament Select
public static function toSelectArray(): array
{
return collect(self::cases())
->mapWithKeys(fn ($case) => [$case->value => $case->label()])
->toArray();
}
// ❌ WRONG - Hardcoded labels
public function label(): string
{
return match ($this) {
self::RESTAURANT => 'Restaurant',
self::CAFE => 'Café',
};
}
}
Mensagens de Validação
Custom Validation (Form Requests)
<?php
declare(strict_types=1);
namespace App\Http\Requests\Business;
use Illuminate\Foundation\Http\FormRequest;
final class StoreBusinessRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email'],
];
}
// ✅ CORRECT - Custom messages with translations
public function messages(): array
{
return [
'name.required' => __('validation.custom.name.required'),
'email.required' => __('validation.custom.email.required'),
];
}
// ✅ CORRECT - Custom attribute names
public function attributes(): array
{
return [
'name' => __('fields.name'),
'email' => __('fields.email'),
];
}
}
Pluralização
<?php
// lang/en/messages.php
return [
'items_count' => '{0} No items|{1} :count item|[2,*] :count items',
];
// lang/es/messages.php
return [
'items_count' => '{0} Sin ítems|{1} :count ítem|[2,*] :count ítems',
];
// lang/pt_BR/messages.php
return [
'items_count' => '{0} Nenhum item|{1} :count item|[2,*] :count itens',
];
Usage:
$count = 0;
echo trans_choice('messages.items_count', $count, ['count' => $count]);
// Output: "No items" (en) / "Sin ítems" (es) / "Nenhum item" (pt_BR)
$count = 5;
echo trans_choice('messages.items_count', $count, ['count' => $count]);
// Output: "5 items" (en) / "5 ítems" (es) / "5 itens" (pt_BR)
Testando Traduções
CRÍTICO: SEMPRE teste que traduções existem para todos os 3 idiomas.
<?php
use function Pest\Laravel\{actingAs, get, post};
it('displays translated dashboard title in all languages', function () {
actingAs(User::factory()->create());
// Test English
app()->setLocale('en');
get('/dashboard')
->assertSee(__('messages.dashboard'));
// Test Spanish
app()->setLocale('es');
get('/dashboard')
->assertSee(__('messages.dashboard'));
// Test Portuguese BR
app()->setLocale('pt_BR');
get('/dashboard')
->assertSee(__('messages.dashboard'));
});
it('validates with translated error messages', function () {
actingAs(User::factory()->create());
app()->setLocale('en');
post('/businesses', [])
->assertSessionHasErrors('name')
->assertSee(__('validation.required', ['attribute' => __('fields.name')]));
});
it('enum returns translated labels in all languages', function () {
app()->setLocale('en');
expect(BusinessType::RESTAURANT->label())->toBe('Restaurant');
app()->setLocale('es');
expect(BusinessType::RESTAURANT->label())->toBe('Restaurante');
app()->setLocale('pt_BR');
expect(BusinessType::RESTAURANT->label())->toBe('Restaurante');
});
// Test that all translation keys exist
it('has all required translation keys', function () {
$requiredKeys = [
'messages.welcome',
'messages.dashboard',
'fields.name',
'fields.email',
];
foreach (['en', 'es', 'pt_BR'] as $locale) {
app()->setLocale($locale);
foreach ($requiredKeys as $key) {
expect(__($key))
->not->toBe($key)
->and(__($key))
->not->toBeEmpty();
}
}
});
Middleware & Detecção de Locale
<?php
// config/app.php
'locale' => 'en',
'fallback_locale' => 'en',
'locales' => ['en', 'es', 'pt_BR'],
// Middleware para definir locale baseado em preferência do usuário
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
final class SetLocale
{
public function handle(Request $request, Closure $next)
{
// Priority: User preference > Session > Browser > Default
$locale = $request->user()?->locale
?? session('locale')
?? $this->detectBrowserLocale($request)
?? config('app.locale');
if (in_array($locale, config('app.locales'))) {
app()->setLocale($locale);
session(['locale' => $locale]);
}
return $next($request);
}
private function detectBrowserLocale(Request $request): ?string
{
$browserLang = substr($request->server('HTTP_ACCEPT_LANGUAGE', ''), 0, 2);
return match ($browserLang) {
'en' => 'en',
'es' => 'es',
'pt' => 'pt_BR',
default => null,
};
}
}
Convenção de Nomenclatura de Translation Keys
| Contexto | Padrão | Exemplo |
|---|---|---|
| Messages | messages.{context}.{action} | messages.auth.login_success |
| Fields | fields.{field_name} | fields.email, fields.password |
| Enums | enums.{enum_name}.{value} | enums.business_type.restaurant |
| Validation | validation.custom.{field} | validation.custom.email.required |
| Exceptions | exceptions.{exception_type} | exceptions.business_limit |
| Navigation | navigation.{item} | navigation.dashboard |
| Resources | messages.resources.{name} | messages.resources.business |
Melhores Práticas
✅ FAÇA
- Sempre use
__()para TODO texto voltado ao usuário - Organize translation keys por domínio/feature
- Use nomes de keys semânticos (ex:
messages.welcome, nãomessages.text1) - Mantenha traduções sincronizadas através de todos idiomas (EN, ES, PT-BR)
- Use replacement de parâmetros para conteúdo dinâmico (
:name,:count, etc.) - Implemente interface
HasLabelem TODOS Enums - Teste traduções em todos idiomas suportados
- Use pluralização para itens contáveis
- Providencie contexto em translation keys (ex:
auth.loginvsnavigation.login) - Publique e traduza pacotes vendor
- Descomente e complete todos arrays de tradução vendor
- Teste traduções de pacotes vendor em todos idiomas
❌ NÃO FAÇA
- Não hardcode strings de texto
- Não use strings em inglês como keys (use keys semânticas em vez disso)
- Não esqueça de adicionar traduções para novas features
- Não misture texto hardcoded com traduções
- Não use concatenação de strings para traduções
- Não skip tradução para palavras "óbvias" como "OK" ou "Cancel"
- Não use keys genéricas como
message1,text2, etc. - Não esqueça de traduzir mensagens de validação
- Não use estruturas de keys diferentes através de arquivos
- Não deixe traduções vendor comentadas
- Não skip traduzindo pacotes third-party
- Não assuma que pacotes vendor já estão traduzidos
Checklist de Referência Rápida
Antes de finalizar QUALQUER feature:
- Todo texto voltado ao usuário usa
__() - Traduções adicionadas para EN, ES, PT-BR
- Enums implementam
HasLabelcom__() - Mensagens de validação de form traduzidas
- Mensagens de sucesso/erro traduzidas
- Items de navegação traduzidos
- Labels de campos traduzidos
- Resources Filament traduzidos
- Testes verificam que traduções funcionam em todos 3 idiomas
- Sem strings hardcoded no código
Padrões de Tradução Comuns
// ✅ Success messages
return redirect()
->route('businesses.index')
->with('success', __('messages.created_successfully', [
'resource' => __('messages.resources.business')
]));
// ✅ Flash messages
session()->flash('success', __('messages.saved_successfully', [
'resource' => __('messages.resources.menu_item')
]));
// ✅ Exception messages
throw new BusinessLimitExceededException(
message: __('exceptions.business_limit_exceeded', [
'current' => $currentCount,
'max' => $maxAllowed,
'plan' => $tenant->plan->name,
])
);
// ✅ Filament notifications
Notification::make()
->title(__('messages.created_successfully', [
'resource' => __('messages.resources.business')
]))
->success()
->send();
Referências Cruzadas
- Exceções: Integrado com
exceptionspara traduções de mensagens de erro - Filament: para traduzir Resources e Widgets, use a skill nativa do Laravel Boost (Filament)
- Architecture: Integrado com
architecturepara Enums traduzíveis - Actions/Events: Integrado com
actionspara traduções de eventos
Referências
- Laravel Localization Documentation - Documentação oficial