agentsclimarketplace

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

Install
npx -y skills add aronpc/ai --skill i18n

Assembled 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

SkillQuando usar junto
enumsPara criar enums traduzíveis
uxPara UX com mensagens traduzidas
coderPara implementar traduções
testingPara 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ão messages.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

ContextoPadrãoExemplo
Messagesmessages.{context}.{action}messages.auth.login_success
Fieldsfields.{field_name}fields.email, fields.password
Enumsenums.{enum_name}.{value}enums.business_type.restaurant
Validationvalidation.custom.{field}validation.custom.email.required
Exceptionsexceptions.{exception_type}exceptions.business_limit
Navigationnavigation.{item}navigation.dashboard
Resourcesmessages.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ão messages.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 HasLabel em TODOS Enums
  • Teste traduções em todos idiomas suportados
  • Use pluralização para itens contáveis
  • Providencie contexto em translation keys (ex: auth.login vs navigation.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 HasLabel com __()
  • 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 exceptions para traduções de mensagens de erro
  • Filament: para traduzir Resources e Widgets, use a skill nativa do Laravel Boost (Filament)
  • Architecture: Integrado com architecture para Enums traduzíveis
  • Actions/Events: Integrado com actions para traduções de eventos

Referências

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.