Agent skill
i18n-enforcer
Enforces internationalization best practices for Breath of Now. Use this skill when creating or editing any component, page, or UI element. Ensures NO hardcoded text and proper use of next-intl translations.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/i18n-enforcer
SKILL.md
i18n Enforcer Skill
Este skill garante que todo o texto visível ao utilizador no projecto Breath of Now está devidamente internacionalizado. ZERO texto hardcoded é aceitável.
Quando Usar
Aplica automaticamente este skill quando:
- Criar novos componentes ou páginas
- Editar elementos UI existentes
- Adicionar mensagens de erro, labels, ou qualquer texto
- Rever código para compliance de i18n
Regras Core
1. NUNCA Hardcodes Texto
// ❌ ERRADO:
<h1>Welcome to Breath of Now</h1>
<button>Sign In</button>
<p>Loading...</p>
<span aria-label="Close menu">×</span>
// ✅ CORRECTO:
const t = useTranslations('HomePage');
<h1>{t('title')}</h1>
<button>{t('signIn')}</button>
<p>{t('loading')}</p>
<span aria-label={t('closeMenu')}>×</span>
2. Sempre Usar useTranslations Hook
import { useTranslations } from 'next-intl';
export function MyComponent() {
const t = useTranslations('MyComponent');
return <div>{t('welcomeMessage')}</div>;
}
3. Estrutura de Ficheiros de Tradução
Localização: /messages/{locale}.json
Locales suportados:
en.json- English (primário)pt.json- Portuguêses.json- Españolfr.json- Français
4. Organização de Namespaces
{
"common": {
"loading": "Loading...",
"error": "An error occurred",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit"
},
"nav": {
"home": "Home",
"pricing": "Pricing",
"account": "Account"
},
"expenses": {
"title": "ExpenseFlow",
"addExpense": "Add Expense",
"categories": "Categories"
},
"fitlog": {
"title": "FitLog",
"startWorkout": "Start Workout",
"history": "History"
}
}
5. Valores Dinâmicos
// No componente
const t = useTranslations('Dashboard');
<p>{t('greeting', { name: user.name })}</p>
<p>{t('lastSync', { date: formatDate(lastSyncDate) })}</p>
// No ficheiro de tradução
{
"Dashboard": {
"greeting": "Hello, {name}!",
"lastSync": "Last synced: {date}"
}
}
6. Pluralização
// No componente
<p>{t('itemCount', { count: items.length })}</p>
<p>{t('daysRemaining', { count: daysLeft })}</p>
// No ficheiro de tradução
{
"itemCount": "{count, plural, =0 {No items} =1 {1 item} other {# items}}",
"daysRemaining": "{count, plural, =0 {Last day!} =1 {1 day remaining} other {# days remaining}}"
}
Checklist de Verificação
Antes de completar qualquer tarefa, verifica:
- Nenhuma string hardcoded em JSX/TSX
- Todo o texto usa hook
useTranslations() - Keys de tradução adicionadas a TODOS os 4 ficheiros de locale
- Namespace corresponde ao nome do componente/página
- Valores dinâmicos usam interpolação adequada
- Pluralização tratada onde necessário
- Alt text de imagens também traduzido
- Aria-labels traduzidos
Erros Comuns
Erro 1: Esquecer alt text
// ❌ ERRADO
<img src={logo} alt="Logo" />
// ✅ CORRECTO
<img src={logo} alt={t('logoAlt')} />
Erro 2: Hardcoded aria-labels
// ❌ ERRADO
<button aria-label="Close menu">X</button>
// ✅ CORRECTO
<button aria-label={t('closeMenu')}>X</button>
Erro 3: Placeholders hardcoded
// ❌ ERRADO
<Input placeholder="Enter your email" />
// ✅ CORRECTO
<Input placeholder={t('emailPlaceholder')} />
Erro 4: Títulos de página hardcoded
// ❌ ERRADO
<title>ExpenseFlow - Breath of Now</title>
// ✅ CORRECTO
<title>{t('pageTitle')}</title>
Erro 5: Mensagens de erro hardcoded
// ❌ ERRADO
toast.error('Failed to save');
// ✅ CORRECTO
toast.error(t('errors.saveFailed'));
Permitido: Console messages
// ✅ OK - mensagens de console não precisam tradução
console.log('Component mounted');
console.error('Failed to fetch data:', error);
Comandos de Detecção Rápida
Para encontrar texto hardcoded no codebase:
# Encontrar potenciais strings hardcoded em ficheiros TSX
grep -r ">[A-Z][a-z]" --include="*.tsx" src/
# Encontrar strings que podem estar hardcoded
grep -rn '"[A-Z][a-zA-Z ]{3,}"' --include="*.tsx" src/
# Encontrar elementos com text content directo
grep -rn ">[a-zA-Z].*<" --include="*.tsx" src/
Padrão de Implementação
Quando criar um novo componente:
Passo 1: Definir namespace no ficheiro
'use client';
import { useTranslations } from 'next-intl';
export function NewComponent() {
const t = useTranslations('NewComponent');
return (
<div>
<h2>{t('title')}</h2>
<p>{t('description')}</p>
</div>
);
}
Passo 2: Adicionar traduções a TODOS os locales
// messages/en.json
{
"NewComponent": {
"title": "Component Title",
"description": "Component description here"
}
}
// messages/pt.json
{
"NewComponent": {
"title": "Título do Componente",
"description": "Descrição do componente aqui"
}
}
// Repetir para es.json, fr.json
Passo 3: Verificar
# Verificar que não há texto hardcoded
grep -n ">[A-Z]" src/components/new-component.tsx
Prioridade
Este skill tem PRIORIDADE MÁXIMA. Nenhum PR ou mudança de código deve ser aceite sem implementação adequada de i18n. Este é um princípio core da acessibilidade global do Breath of Now.
Ficheiros de Referência
Verifica sempre:
/messages/en.json- Traduções principais.claude/commands/i18n-check.md- Comando de verificação- Componentes existentes para padrões de uso
REGRA DE OURO: Se um utilizador vai ver o texto, esse texto DEVE ser traduzido. Sem excepções.
Didn't find tool you were looking for?