From daffy0208-ai-dev-standards
Expert in internationalization (i18n), multi-language support, and localization
npx claudepluginhub joshuarweaver/cascade-content-creation-misc-1 --plugin daffy0208-ai-dev-standardsThis skill uses the workspace's default tool permissions.
I help you build multilingual applications with proper internationalization (i18n) and localization (l10n) support.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
I help you build multilingual applications with proper internationalization (i18n) and localization (l10n) support.
Internationalization:
Localization:
npm install next-intl
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
// middleware.ts
import createMiddleware from 'next-intl/middleware'
export default createMiddleware({
locales: ['en', 'es', 'fr', 'de', 'ja'],
defaultLocale: 'en'
})
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)']
}
// messages/en.json
{
"common": {
"welcome": "Welcome",
"loading": "Loading...",
"error": "Something went wrong"
},
"navigation": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"auth": {
"login": "Log in",
"logout": "Log out",
"signUp": "Sign up",
"emailPlaceholder": "Enter your email",
"passwordPlaceholder": "Enter your password"
}
}
// messages/es.json
{
"common": {
"welcome": "Bienvenido",
"loading": "Cargando...",
"error": "Algo salió mal"
},
"navigation": {
"home": "Inicio",
"about": "Acerca de",
"contact": "Contacto"
},
"auth": {
"login": "Iniciar sesión",
"logout": "Cerrar sesión",
"signUp": "Registrarse",
"emailPlaceholder": "Ingrese su correo electrónico",
"passwordPlaceholder": "Ingrese su contraseña"
}
}
'use client'
import { useTranslations } from 'next-intl'
export function LoginForm() {
const t = useTranslations('auth')
return (
<form>
<input
type="email"
placeholder={t('emailPlaceholder')}
/>
<input
type="password"
placeholder={t('passwordPlaceholder')}
/>
<button>{t('login')}</button>
</form>
)
}
import { useTranslations } from 'next-intl'
export default function HomePage() {
const t = useTranslations('common')
return (
<div>
<h1>{t('welcome')}</h1>
</div>
)
}
'use client'
import { useLocale } from 'next-intl'
import { useRouter, usePathname } from 'next/navigation'
const languages = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'es', name: 'Español', flag: '🇪🇸' },
{ code: 'fr', name: 'Français', flag: '🇫🇷' },
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
{ code: 'ja', name: '日本語', flag: '🇯🇵' }
]
export function LanguageSwitcher() {
const locale = useLocale()
const router = useRouter()
const pathname = usePathname()
const switchLanguage = (newLocale: string) => {
// Remove current locale from pathname
const pathWithoutLocale = pathname.replace(`/${locale}`, '')
// Navigate to new locale
router.push(`/${newLocale}${pathWithoutLocale}`)
}
return (
<select
value={locale}
onChange={(e) => switchLanguage(e.target.value)}
className="px-4 py-2 border rounded"
>
{languages.map((lang) => (
<option key={lang.code} value={lang.code}>
{lang.flag} {lang.name}
</option>
))}
</select>
)
}
'use client'
import { useFormatter } from 'next-intl'
export function FormattedDate({ date }: { date: Date }) {
const format = useFormatter()
return (
<div>
{/* Full date */}
<p>{format.dateTime(date, { dateStyle: 'full' })}</p>
{/* Short date */}
<p>{format.dateTime(date, { dateStyle: 'short' })}</p>
{/* Custom format */}
<p>{format.dateTime(date, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric'
})}</p>
{/* Relative time */}
<p>{format.relativeTime(date)}</p>
</div>
)
}
// Examples:
// en: "Monday, October 22, 2025"
// es: "lunes, 22 de octubre de 2025"
// ja: "2025年10月22日月曜日"
'use client'
import { useFormatter } from 'next-intl'
export function FormattedNumber({ value }: { value: number }) {
const format = useFormatter()
return (
<div>
{/* Number */}
<p>{format.number(value)}</p>
{/* Currency */}
<p>{format.number(value, { style: 'currency', currency: 'USD' })}</p>
{/* Percentage */}
<p>{format.number(value / 100, { style: 'percent' })}</p>
{/* Compact notation */}
<p>{format.number(value, { notation: 'compact' })}</p>
</div>
)
}
// Examples:
// en: "1,234.56" "$1,234.56" "12%" "1.2K"
// de: "1.234,56" "1.234,56 $" "12 %" "1200"
// ja: "1,234.56" "$1,234.56" "12%" "1.2千"
// messages/en.json
{
"items": {
"count": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
}
'use client'
import { useTranslations } from 'next-intl'
export function ItemCounter({ count }: { count: number }) {
const t = useTranslations('items')
return <p>{t('count', { count })}</p>
}
// count = 0: "No items"
// count = 1: "1 item"
// count = 5: "5 items"
// app/[locale]/layout.tsx
import { useLocale } from 'next-intl'
const rtlLanguages = ['ar', 'he', 'fa']
export default function LocaleLayout({ children }) {
const locale = useLocale()
const isRTL = rtlLanguages.includes(locale)
return (
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'}>
<body>{children}</body>
</html>
)
}
RTL CSS:
/* Automatically flips for RTL */
.container {
margin-inline-start: 1rem; /* Use logical properties */
padding-inline-end: 1rem;
}
/* Manual RTL handling */
[dir='rtl'] .menu {
left: auto;
right: 0;
}
// lib/detect-locale.ts
export function detectUserLocale(): string {
// 1. Check URL parameter
const urlParams = new URLSearchParams(window.location.search)
const urlLocale = urlParams.get('lang')
if (urlLocale) return urlLocale
// 2. Check localStorage
const savedLocale = localStorage.getItem('preferredLocale')
if (savedLocale) return savedLocale
// 3. Check browser language
const browserLocale = navigator.language.split('-')[0]
return browserLocale
// 4. Default
return 'en'
}
// messages/en.json
{
"welcome": "Welcome, {name}!",
"itemsInCart": "You have {count} {count, plural, one {item} other {items}} in your cart",
"priceDisplay": "Price: {price, number, ::currency/USD}"
}
'use client'
import { useTranslations } from 'next-intl'
export function Greeting({ userName }: { userName: string }) {
const t = useTranslations()
return (
<div>
<h1>{t('welcome', { name: userName })}</h1>
<p>{t('itemsInCart', { count: 3 })}</p>
<p>{t('priceDisplay', { price: 49.99 })}</p>
</div>
)
}
// Output:
// "Welcome, John!"
// "You have 3 items in your cart"
// "Price: $49.99"
// app/[locale]/page.tsx
import { useLocale } from 'next-intl'
export default function HomePage() {
const locale = useLocale()
const content = {
en: {
hero: 'Build amazing apps',
description: 'The best platform for developers'
},
es: {
hero: 'Crea aplicaciones increíbles',
description: 'La mejor plataforma para desarrolladores'
},
ja: {
hero: '素晴らしいアプリを作成',
description: '開発者のための最高のプラットフォーム'
}
}
return (
<div>
<h1>{content[locale].hero}</h1>
<p>{content[locale].description}</p>
</div>
)
}
// scripts/sync-translations.ts
async function syncTranslations() {
// Download translations from service
const response = await fetch('https://api.lokalise.com/api2/projects/PROJECT_ID/files/download', {
headers: {
'X-Api-Token': process.env.LOKALISE_API_KEY!
}
})
const data = await response.json()
// Save to messages folder
await fs.writeFile('./messages/en.json', JSON.stringify(data.en, null, 2))
console.log('Translations synced!')
}
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default,
onError: error => {
console.error('Translation error:', error)
},
getMessageFallback: ({ namespace, key, error }) => {
return `${namespace}.${key}` // Show key if translation missing
}
}))
// app/[locale]/layout.tsx
import { useLocale } from 'next-intl'
export async function generateMetadata({ params: { locale } }) {
const t = await useTranslations('metadata')
return {
title: t('title'),
description: t('description'),
alternates: {
canonical: `/${locale}`,
languages: {
en: '/en',
es: '/es',
fr: '/fr',
de: '/de',
ja: '/ja'
}
}
}
}
HTML Output:
<link rel="canonical" href="https://example.com/en" />
<link rel="alternate" hreflang="en" href="https://example.com/en" />
<link rel="alternate" hreflang="es" href="https://example.com/es" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />
Perfect for:
I'll help you:
🌍 Multi-Language Support
📅 Date/Time Formatting
💰 Currency Formatting
🔄 Language Switching
📝 Translation Management
🌐 RTL Support
Let's make your app globally accessible!