Claude-skill-registry i18n
Internationalization with i18next for UI translations and JSONB for database content. Use when adding translations, working with localized content, or implementing multi-language features.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/i18n-mavrick91-tanstack-start-app" ~/.claude/skills/majiayu000-claude-skill-registry-i18n && rm -rf "$T"
manifest:
skills/data/i18n-mavrick91-tanstack-start-app/SKILL.mdsource content
i18n Helper
Complete internationalization guide for this application.
Supported Languages
| Code | Language | URL Pattern |
|---|---|---|
| English (default) | |
| French | |
| Indonesian | |
Two Types of i18n
1. UI Translations (i18next)
Static strings in the interface: buttons, labels, messages.
import { useTranslation } from 'react-i18next' function MyComponent() { const { t } = useTranslation() return ( <div> <h1>{t('Welcome')}</h1> <button>{t('Add to Cart')}</button> </div> ) }
2. Database Content (JSONB)
Dynamic content stored in the database: product names, descriptions.
type LocalizedString = { en: string; fr?: string; id?: string } // In database schema name: jsonb('name').$type<LocalizedString>().notNull() // Usage const productName = product.name[currentLang] || product.name.en
Adding UI Translations
1. Add Keys to Locale Files
// src/i18n/locales/en.json { "Welcome": "Welcome", "Add to Cart": "Add to Cart", "{{count}} items": "{{count}} items", "{{count}} items_one": "1 item", "{{count}} items_other": "{{count}} items" } // src/i18n/locales/fr.json { "Welcome": "Bienvenue", "Add to Cart": "Ajouter au panier", "{{count}} items_one": "1 article", "{{count}} items_other": "{{count}} articles" } // src/i18n/locales/id.json { "Welcome": "Selamat datang", "Add to Cart": "Tambah ke Keranjang", "{{count}} items": "{{count}} barang" }
2. Use in Components
import { useTranslation } from 'react-i18next' function CartSummary({ itemCount }: { itemCount: number }) { const { t } = useTranslation() return ( <div> <h2>{t('Cart')}</h2> <p>{t('{{count}} items', { count: itemCount })}</p> </div> ) }
3. Scan for Missing Keys
yarn locales:scan
Translation Patterns
Basic Translation
t('Hello') // "Hello" or localized version
With Interpolation
t('Hello {{name}}', { name: 'John' }) // "Hello John" t('Price: {{price}}', { price: '$99.99' }) // "Price: $99.99"
Pluralization
// en.json { "{{count}} selected_one": "1 selected", "{{count}} selected_other": "{{count}} selected", "{{count}} selected_zero": "None selected" }
t('{{count}} selected', { count: 0 }) // "None selected" t('{{count}} selected', { count: 1 }) // "1 selected" t('{{count}} selected', { count: 5 }) // "5 selected"
Nested Keys
{ "errors": { "required": "This field is required", "email": "Please enter a valid email" } }
t('errors.required') // "This field is required"
Database Localized Content
Schema Definition
// src/db/schema.ts type LocalizedString = { en: string; fr?: string; id?: string } export const products = pgTable('products', { id: uuid('id').defaultRandom().primaryKey(), name: jsonb('name').$type<LocalizedString>().notNull(), description: jsonb('description').$type<LocalizedString>(), metaTitle: jsonb('meta_title').$type<LocalizedString>(), metaDescription: jsonb('meta_description').$type<LocalizedString>(), })
Creating Localized Content
// API endpoint const body = await request.json() const { name, description } = body // Validate English is present if (!name?.en?.trim()) { return simpleErrorResponse('Name (English) is required') } await db.insert(products).values({ name: { en: name.en, fr: name.fr || undefined, id: name.id || undefined, }, description: description || undefined, })
Displaying Localized Content
import { useParams } from '@tanstack/react-router' function ProductName({ product }: { product: Product }) { const { lang } = useParams({ from: '/$lang' }) // Fallback to English if translation missing const name = product.name[lang as keyof typeof product.name] || product.name.en return <h1>{name}</h1> }
Helper Function
// src/lib/i18n.ts export function getLocalizedValue<T extends Record<string, unknown>>( obj: T | null | undefined, lang: string, fallback: string = '', ): string { if (!obj) return fallback const value = obj[lang as keyof T] || obj['en' as keyof T] return typeof value === 'string' ? value : fallback } // Usage const name = getLocalizedValue(product.name, lang) const description = getLocalizedValue( product.description, lang, 'No description', )
Multi-Language Admin Forms
Tab-Based Locale Editor
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' const LOCALES = ['en', 'fr', 'id'] as const function LocalizedInput({ value, onChange, label, }: { value: LocalizedString onChange: (value: LocalizedString) => void label: string }) { return ( <div className="space-y-2"> <Label>{label}</Label> <Tabs defaultValue="en"> <TabsList> {LOCALES.map((locale) => ( <TabsTrigger key={locale} value={locale}> {locale.toUpperCase()} {locale === 'en' && <span className="text-red-500 ml-1">*</span>} </TabsTrigger> ))} </TabsList> {LOCALES.map((locale) => ( <TabsContent key={locale} value={locale}> <Input value={value[locale] || ''} onChange={(e) => onChange({ ...value, [locale]: e.target.value }) } placeholder={`${label} (${locale.toUpperCase()})`} /> </TabsContent> ))} </Tabs> </div> ) }
Usage in Product Form
function ProductForm() { const [name, setName] = useState<LocalizedString>({ en: '' }) const [description, setDescription] = useState<LocalizedString>({ en: '' }) return ( <form> <LocalizedInput value={name} onChange={setName} label="Product Name" /> <LocalizedInput value={description} onChange={setDescription} label="Description" /> </form> ) }
URL-Based Language Switching
Route Structure
src/routes/ ├── $lang/ # Language prefix │ ├── index.tsx # /$lang/ │ ├── products/ │ │ ├── index.tsx # /$lang/products │ │ └── $handle.tsx # /$lang/products/:handle │ └── cart.tsx # /$lang/cart
Language Switcher Component
import { Link, useParams, useLocation } from '@tanstack/react-router' const languages = [ { code: 'en', label: 'English' }, { code: 'fr', label: 'Français' }, { code: 'id', label: 'Indonesia' }, ] function LanguageSwitcher() { const { lang } = useParams({ from: '/$lang' }) const location = useLocation() // Replace language in current path const switchPath = (newLang: string) => { return location.pathname.replace(`/${lang}`, `/${newLang}`) } return ( <div className="flex gap-2"> {languages.map((language) => ( <Link key={language.code} to={switchPath(language.code)} className={lang === language.code ? 'font-bold' : ''} > {language.label} </Link> ))} </div> ) }
Sync i18next with URL
// In layout component import { useEffect } from 'react' import { useParams } from '@tanstack/react-router' import { changeLanguage } from '@/lib/i18n' function Layout() { const { lang } = useParams({ from: '/$lang' }) useEffect(() => { changeLanguage(lang) }, [lang]) return <Outlet /> }
SEO with Localized Content
function ProductPage({ product }: { product: Product }) { const { lang } = useParams({ from: '/$lang' }) const title = getLocalizedValue(product.metaTitle, lang) || getLocalizedValue(product.name, lang) const description = getLocalizedValue(product.metaDescription, lang) || getLocalizedValue(product.description, lang) return ( <> <head> <title>{title}</title> <meta name="description" content={description} /> <link rel="alternate" hreflang="en" href={`/en/products/${product.handle}`} /> <link rel="alternate" hreflang="fr" href={`/fr/products/${product.handle}`} /> <link rel="alternate" hreflang="id" href={`/id/products/${product.handle}`} /> </head> {/* ... */} </> ) }
Common Translation Keys
{ "common": { "Save": "Save", "Cancel": "Cancel", "Delete": "Delete", "Edit": "Edit", "Loading": "Loading...", "Error": "Error", "Success": "Success" }, "validation": { "Required": "This field is required", "Invalid email": "Please enter a valid email", "Too short": "Must be at least {{min}} characters" }, "cart": { "Add to Cart": "Add to Cart", "Remove": "Remove", "Empty cart": "Your cart is empty", "Checkout": "Checkout" } }
See Also
- i18next setupsrc/lib/i18n.ts
- Translation filessrc/i18n/locales/
- Localized routessrc/routes/$lang/
skill - Localized form inputsforms