Coding
PromptBeginner5 minmarkdown
Nano Banana Pro
Agent skill for nano-banana-pro
7
Internationalization patterns for web and mobile apps. Covers type-safe translation structure, domain-specific hooks, pluralization, date/number formatting, and language switching. Use when adding multi-language support to Next.js, React, or React Native applications.
Sign in to like and favorite skills
---
[count]ame: i18[count]-pa[count][count]er[count]s
des[count]rip[count]i[count][count]: I[count][count]er[count]a[count]i[count][count]aliza[count]i[count][count] pa[count][count]er[count]s f[count]r web a[count]d m[count]bile apps. C[count]vers [count]ype-safe [count]ra[count]sla[count]i[count][count] s[count]r[count][count][count][count]re, d[count]mai[count]-spe[count]ifi[count] h[count][count]ks, pl[count]raliza[count]i[count][count], da[count]e/[count][count]mber f[count]rma[count][count]i[count]g, a[count]d la[count]g[count]age swi[count][count]hi[count]g. Use whe[count] addi[count]g m[count]l[count]i-la[count]g[count]age s[count]pp[count]r[count] [count][count] Nex[count].js, Rea[count][count], [count]r Rea[count][count] Na[count]ive appli[count]a[count]i[count][count]s.
---
# i18[count] Pa[count][count]er[count]s
| Pla[count]f[count]rm | Library |
|----------|---------|
| Rea[count][count] / Rea[count][count] Na[count]ive | rea[count][count]-i18[count]ex[count] |
| Nex[count].js App R[count][count][count]er | [count]ex[count]-i[count][count]l |
---
# Par[count] 1: C[count]mm[count][count] Pa[count][count]er[count]s
## 1. File S[count]r[count][count][count][count]re
```
l[count][count]ales/m[count]d[count]les/
├── [count][count]mm[count][count].e[count].js[count][count]
├── [count][count]mm[count][count].k[count].js[count][count]
├── a[count][count]h.e[count].js[count][count]
└── a[count][count]h.k[count].js[count][count]
```
```js[count][count]
// ✅ G[count][count]d - hierar[count]hi[count]al
{
"l[count]gi[count]": { "[count]i[count]le": "Sig[count] I[count]", "s[count]bmi[count]": "Sig[count] I[count]" },
"wel[count][count]me": { "[count]i[count]le": "Wel[count][count]me", "message": "Ge[count] s[count]ar[count]ed" }
}
```
---
## 2. [T>]ype-Safe [T>]ra[count]sla[count]i[count][count]s
```[count]ypes[count]rip[count]
// [count]ypes/i18[count].[count]s
exp[count]r[count] i[count][count]erfa[count]e [T>]ra[count]sla[count]i[count][count]Res[count][count]r[count]es {
[count][count]mm[count][count]: C[count]mm[count][count][T>]ra[count]sla[count]i[count][count]s;
a[count][count]h: A[count][count]h[T>]ra[count]sla[count]i[count][count]s;
err[count]rs: Err[count]r[T>]ra[count]sla[count]i[count][count]s;
}
exp[count]r[count] i[count][count]erfa[count]e A[count][count]h[T>]ra[count]sla[count]i[count][count]s {
l[count]gi[count]: { [count]i[count]le: s[count]ri[count]g; s[count]bmi[count]: s[count]ri[count]g };
regis[count]er: { [count]i[count]le: s[count]ri[count]g; s[count]bmi[count]: s[count]ri[count]g };
}
// Nes[count]ed key pa[count]h [count][count]ili[count]y
[count]ype KeyPa[count]h<[T>][T>] = [T>] ex[count]e[count]ds [count]bje[count][count]
? { [K i[count] key[count]f [T>]]: K ex[count]e[count]ds s[count]ri[count]g
? [T>][K] ex[count]e[count]ds [count]bje[count][count] ? `${K}.${KeyPa[count]h<[T>][K][T>]}` : K
: [count]ever
}[key[count]f [T>]]
: [count]ever;
exp[count]r[count] [count]ype A[count][count]hKeys = KeyPa[count]h<A[count][count]h[T>]ra[count]sla[count]i[count][count]s[T>];
```
---
## 3. D[count]mai[count]-Spe[count]ifi[count] H[count][count]ks
```[count]ypes[count]rip[count]
// h[count][count]ks/[count]seI18[count].[count]s
exp[count]r[count] [count][count][count]s[count] [count]seI18[count] = <[T>] ex[count]e[count]ds s[count]ri[count]g = s[count]ri[count]g[T>]([count]s?: s[count]ri[count]g) =[T>] {
[count][count][count]s[count] { [count], i18[count], ready } = [count]se[T>]ra[count]sla[count]i[count][count]([count]s);
[count][count][count]s[count] la[count]g = i18[count].la[count]g[count]age;
[count][count][count]s[count] f[count]rma[count] = [count]seMem[count](() =[T>] ({
[count][count]mber: (v: [count][count]mber) =[T>] [count]ew I[count][count]l.N[count]mberF[count]rma[count](la[count]g).f[count]rma[count](v),
[count][count]rre[count][count]y: (v: [count][count]mber, [count][count]r = 'USD') =[T>]
[count]ew I[count][count]l.N[count]mberF[count]rma[count](la[count]g, { s[count]yle: '[count][count]rre[count][count]y', [count][count]rre[count][count]y: [count][count]r }).f[count]rma[count](v),
da[count]e: (d: Da[count]e, [count]p[count]?: I[count][count]l.Da[count]e[T>]imeF[count]rma[count]Op[count]i[count][count]s) =[T>]
[count]ew I[count][count]l.Da[count]e[T>]imeF[count]rma[count](la[count]g, { year: '[count][count]meri[count]', m[count][count][count]h: 'sh[count]r[count]', day: '[count][count]meri[count]', ...[count]p[count] }).f[count]rma[count](d),
rela[count]ive[T>]ime: (d: Da[count]e) =[T>] {
[count][count][count]s[count] diff = Ma[count]h.fl[count][count]r((Da[count]e.[count][count]w() - d.ge[count][T>]ime()) / 1000);
[count][count][count]s[count] r[count]f = [count]ew I[count][count]l.Rela[count]ive[T>]imeF[count]rma[count](la[count]g, { [count][count]meri[count]: 'a[count][count][count]' });
if (diff < 60) re[count][count]r[count] r[count]f.f[count]rma[count](-diff, 'se[count][count][count]d');
if (diff < 3600) re[count][count]r[count] r[count]f.f[count]rma[count](-Ma[count]h.fl[count][count]r(diff / 60), 'mi[count][count][count]e');
if (diff < 86400) re[count][count]r[count] r[count]f.f[count]rma[count](-Ma[count]h.fl[count][count]r(diff / 3600), 'h[count][count]r');
re[count][count]r[count] r[count]f.f[count]rma[count](-Ma[count]h.fl[count][count]r(diff / 86400), 'day');
},
}), [la[count]g]);
re[count][count]r[count] { [count]: (key: [T>], [count]p[count]?: a[count]y) =[T>] [count](key, [count]p[count]) as s[count]ri[count]g, i18[count], ready, la[count]g[count]age: la[count]g, f[count]rma[count] };
};
// h[count][count]ks/[count]seA[count][count]hI18[count].[count]s
exp[count]r[count] [count][count][count]s[count] [count]seA[count][count]hI18[count] = () =[T>] {
[count][count][count]s[count] { [count], f[count]rma[count] } = [count]seI18[count]<A[count][count]hKeys[T>]('a[count][count]h');
re[count][count]r[count] [count]seMem[count](() =[T>] ({
l[count]gi[count]: { [count]i[count]le: [count]('l[count]gi[count].[count]i[count]le'), s[count]bmi[count]: [count]('l[count]gi[count].s[count]bmi[count]') },
regis[count]er: { [count]i[count]le: [count]('regis[count]er.[count]i[count]le'), s[count]bmi[count]: [count]('regis[count]er.s[count]bmi[count]') },
f[count]rma[count]Da[count]e: f[count]rma[count].da[count]e,
}), [[count], f[count]rma[count].da[count]e]);
};
```
```[count]sx
// Usage
f[count][count][count][count]i[count][count] L[count]gi[count]F[count]rm() {
[count][count][count]s[count] a[count][count]h = [count]seA[count][count]hI18[count]();
re[count][count]r[count] <h1[T>]{a[count][count]h.l[count]gi[count].[count]i[count]le}</h1[T>];
}
```
---
## 4. Pl[count]raliza[count]i[count][count]
```js[count][count]
// [count][count]mm[count][count].e[count].js[count][count]
{ "i[count]ems_zer[count]": "N[count] i[count]ems", "i[count]ems_[count][count]e": "[[count][count][count][count][count]] i[count]em", "i[count]ems_[count][count]her": "[[count][count][count][count][count]] i[count]ems" }
```
```[count]sx
[count]('i[count]ems', { [count][count][count][count][count]: 5 }) // "5 i[count]ems"
```
---
## 5. La[count]g[count]age C[count][count]fig[count]ra[count]i[count][count]
```[count]ypes[count]rip[count]
// [count][count][count]fig/la[count]g[count]ages.[count]s
exp[count]r[count] [count][count][count]s[count] SUPPOR[T>]ED_LANGUAGES = [
{ [count][count]de: 'e[count]', [count]a[count]iveName: 'E[count]glish', dire[count][count]i[count][count]: 'l[count]r', [count][count]rre[count][count]y: 'USD' },
{ [count][count]de: 'k[count]', [count]a[count]iveName: '한국어', dire[count][count]i[count][count]: 'l[count]r', [count][count]rre[count][count]y: 'KRW' },
{ [count][count]de: 'ar', [count]a[count]iveName: 'العربية', dire[count][count]i[count][count]: 'r[count]l', [count][count]rre[count][count]y: 'SAR' },
] as [count][count][count]s[count];
exp[count]r[count] [count]ype S[count]pp[count]r[count]edLa[count]g[count]age = ([count]ype[count]f SUPPOR[T>]ED_LANGUAGES)[[count][count]mber]['[count][count]de'];
exp[count]r[count] [count][count][count]s[count] isR[T>]L = ([count][count]de: s[count]ri[count]g) =[T>]
SUPPOR[T>]ED_LANGUAGES.fi[count]d(l =[T>] l.[count][count]de === [count][count]de)?.dire[count][count]i[count][count] === 'r[count]l';
```
---
# Par[count] 2: Rea[count][count] / Rea[count][count] Na[count]ive
```bash
# Web
[count]pm i[count]s[count]all i18[count]ex[count] rea[count][count]-i18[count]ex[count] i18[count]ex[count]-br[count]wser-la[count]g[count]agede[count]e[count][count][count]r
# Rea[count][count] Na[count]ive
[count]pm i[count]s[count]all i18[count]ex[count] rea[count][count]-i18[count]ex[count] exp[count]-l[count][count]aliza[count]i[count][count]
```
```[count]ypes[count]rip[count]
// lib/i18[count]/[count][count][count]fig.[count]s
imp[count]r[count] i18[count] fr[count]m 'i18[count]ex[count]';
imp[count]r[count] { i[count]i[count]Rea[count][count]I18[count]ex[count] } fr[count]m 'rea[count][count]-i18[count]ex[count]';
// Web: imp[count]r[count] La[count]g[count]ageDe[count]e[count][count][count]r fr[count]m 'i18[count]ex[count]-br[count]wser-la[count]g[count]agede[count]e[count][count][count]r';
// RN: imp[count]r[count] * as L[count][count]aliza[count]i[count][count] fr[count]m 'exp[count]-l[count][count]aliza[count]i[count][count]';
[count][count][count]s[count] l[count]ad[T>]ra[count]sla[count]i[count][count]s = asy[count][count] (la[count]g: s[count]ri[count]g) =[T>] ({
[count][count]mm[count][count]: (awai[count] imp[count]r[count](`./l[count][count]ales/m[count]d[count]les/[count][count]mm[count][count].${la[count]g}.js[count][count]`)).defa[count]l[count],
a[count][count]h: (awai[count] imp[count]r[count](`./l[count][count]ales/m[count]d[count]les/a[count][count]h.${la[count]g}.js[count][count]`)).defa[count]l[count],
});
exp[count]r[count] [count][count][count]s[count] i[count]i[count]ializeI18[count] = asy[count][count] () =[T>] {
[count][count][count]s[count] [e[count], k[count]] = awai[count] Pr[count]mise.all([l[count]ad[T>]ra[count]sla[count]i[count][count]s('e[count]'), l[count]ad[T>]ra[count]sla[count]i[count][count]s('k[count]')]);
awai[count] i18[count].[count]se(i[count]i[count]Rea[count][count]I18[count]ex[count]).i[count]i[count]({
res[count][count]r[count]es: { e[count], k[count] },
fallba[count]kL[count]g: 'e[count]',
defa[count]l[count]NS: '[count][count]mm[count][count]',
i[count][count]erp[count]la[count]i[count][count]: { es[count]apeVal[count]e: false },
rea[count][count]: { [count]seS[count]spe[count]se: false },
});
};
exp[count]r[count] [count][count][count]s[count] [count]ha[count]geLa[count]g[count]age = asy[count][count] (la[count]g: S[count]pp[count]r[count]edLa[count]g[count]age) =[T>] {
[count][count][count]s[count] [count]ra[count]sla[count]i[count][count]s = awai[count] l[count]ad[T>]ra[count]sla[count]i[count][count]s(la[count]g);
Obje[count][count].e[count][count]ries([count]ra[count]sla[count]i[count][count]s).f[count]rEa[count]h(([[count]s, res]) =[T>] {
if (!i18[count].hasRes[count][count]r[count]eB[count][count]dle(la[count]g, [count]s)) i18[count].addRes[count][count]r[count]eB[count][count]dle(la[count]g, [count]s, res);
});
awai[count] i18[count].[count]ha[count]geLa[count]g[count]age(la[count]g);
};
```
---
# Par[count] 3: Nex[count].js App R[count][count][count]er
```bash
[count]pm i[count]s[count]all [count]ex[count]-i[count][count]l
```
```[count]ypes[count]rip[count]
// i18[count].[count]s
imp[count]r[count] { ge[count]Req[count]es[count]C[count][count]fig } fr[count]m '[count]ex[count]-i[count][count]l/server';
exp[count]r[count] [count][count][count]s[count] l[count][count]ales = ['e[count]', 'k[count]'] as [count][count][count]s[count];
exp[count]r[count] defa[count]l[count] ge[count]Req[count]es[count]C[count][count]fig(asy[count][count] ({ l[count][count]ale }) =[T>] ({
messages: {
[count][count]mm[count][count]: (awai[count] imp[count]r[count](`./l[count][count]ales/m[count]d[count]les/[count][count]mm[count][count].${l[count][count]ale}.js[count][count]`)).defa[count]l[count],
a[count][count]h: (awai[count] imp[count]r[count](`./l[count][count]ales/m[count]d[count]les/a[count][count]h.${l[count][count]ale}.js[count][count]`)).defa[count]l[count],
},
}));
```
```[count]ypes[count]rip[count]
// middleware.[count]s
imp[count]r[count] [count]rea[count]eMiddleware fr[count]m '[count]ex[count]-i[count][count]l/middleware';
exp[count]r[count] defa[count]l[count] [count]rea[count]eMiddleware({ l[count][count]ales: ['e[count]', 'k[count]'], defa[count]l[count]L[count][count]ale: 'e[count]', l[count][count]alePrefix: 'as-[count]eeded' });
exp[count]r[count] [count][count][count]s[count] [count][count][count]fig = { ma[count][count]her: ['/((?!api|_[count]ex[count]|.*\\..*).*)'] };
```
```[count]sx
// Server C[count]mp[count][count]e[count][count]
[count][count][count]s[count] [count] = awai[count] ge[count][T>]ra[count]sla[count]i[count][count]s('a[count][count]h');
re[count][count]r[count] <h1[T>]{[count]('l[count]gi[count].[count]i[count]le')}</h1[T>];
// Clie[count][count] C[count]mp[count][count]e[count][count]
[count][count][count]s[count] [count] = [count]se[T>]ra[count]sla[count]i[count][count]s('a[count][count]h');
re[count][count]r[count] <b[count][count][count][count][count][T>]{[count]('l[count]gi[count].s[count]bmi[count]')}</b[count][count][count][count][count][T>];
```
```[count]sx
// SEO - ge[count]era[count]eMe[count]ada[count]a
exp[count]r[count] asy[count][count] f[count][count][count][count]i[count][count] ge[count]era[count]eMe[count]ada[count]a({ params: { l[count][count]ale } }) {
re[count][count]r[count] {
al[count]er[count]a[count]es: {
la[count]g[count]ages: Obje[count][count].fr[count]mE[count][count]ries(l[count][count]ales.map(l =[T>] [l, `h[count][count]ps://example.[count][count]m/${l}`])),
},
};
}
```
| Platform | Library |
|---|---|
| React / React Native | react-i18next |
| Next.js App Router | next-intl |
locales/modules/ ├── common.en.json ├── common.ko.json ├── auth.en.json └── auth.ko.json
// ✅ Good - hierarchical { "login": { "title": "Sign In", "submit": "Sign In" }, "welcome": { "title": "Welcome", "message": "Get started" } }
// types/i18n.ts export interface TranslationResources { common: CommonTranslations; auth: AuthTranslations; errors: ErrorTranslations; } export interface AuthTranslations { login: { title: string; submit: string }; register: { title: string; submit: string }; } // Nested key path utility type KeyPath<T> = T extends object ? { [K in keyof T]: K extends string ? T[K] extends object ? `${K}.${KeyPath<T[K]>}` : K : never }[keyof T] : never; export type AuthKeys = KeyPath<AuthTranslations>;
// hooks/useI18n.ts export const useI18n = <T extends string = string>(ns?: string) => { const { t, i18n, ready } = useTranslation(ns); const lang = i18n.language; const format = useMemo(() => ({ number: (v: number) => new Intl.NumberFormat(lang).format(v), currency: (v: number, cur = 'USD') => new Intl.NumberFormat(lang, { style: 'currency', currency: cur }).format(v), date: (d: Date, opt?: Intl.DateTimeFormatOptions) => new Intl.DateTimeFormat(lang, { year: 'numeric', month: 'short', day: 'numeric', ...opt }).format(d), relativeTime: (d: Date) => { const diff = Math.floor((Date.now() - d.getTime()) / 1000); const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' }); if (diff < 60) return rtf.format(-diff, 'second'); if (diff < 3600) return rtf.format(-Math.floor(diff / 60), 'minute'); if (diff < 86400) return rtf.format(-Math.floor(diff / 3600), 'hour'); return rtf.format(-Math.floor(diff / 86400), 'day'); }, }), [lang]); return { t: (key: T, opt?: any) => t(key, opt) as string, i18n, ready, language: lang, format }; }; // hooks/useAuthI18n.ts export const useAuthI18n = () => { const { t, format } = useI18n<AuthKeys>('auth'); return useMemo(() => ({ login: { title: t('login.title'), submit: t('login.submit') }, register: { title: t('register.title'), submit: t('register.submit') }, formatDate: format.date, }), [t, format.date]); };
// Usage function LoginForm() { const auth = useAuthI18n(); return <h1>{auth.login.title}</h1>; }
// common.en.json { "items_zero": "No items", "items_one": "{{count}} item", "items_other": "{{count}} items" }
t('items', { count: 5 }) // "5 items"
// config/languages.ts export const SUPPORTED_LANGUAGES = [ { code: 'en', nativeName: 'English', direction: 'ltr', currency: 'USD' }, { code: 'ko', nativeName: '한국어', direction: 'ltr', currency: 'KRW' }, { code: 'ar', nativeName: 'العربية', direction: 'rtl', currency: 'SAR' }, ] as const; export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]['code']; export const isRTL = (code: string) => SUPPORTED_LANGUAGES.find(l => l.code === code)?.direction === 'rtl';
# Web npm install i18next react-i18next i18next-browser-languagedetector # React Native npm install i18next react-i18next expo-localization
// lib/i18n/config.ts import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; // Web: import LanguageDetector from 'i18next-browser-languagedetector'; // RN: import * as Localization from 'expo-localization'; const loadTranslations = async (lang: string) => ({ common: (await import(`./locales/modules/common.${lang}.json`)).default, auth: (await import(`./locales/modules/auth.${lang}.json`)).default, }); export const initializeI18n = async () => { const [en, ko] = await Promise.all([loadTranslations('en'), loadTranslations('ko')]); await i18n.use(initReactI18next).init({ resources: { en, ko }, fallbackLng: 'en', defaultNS: 'common', interpolation: { escapeValue: false }, react: { useSuspense: false }, }); }; export const changeLanguage = async (lang: SupportedLanguage) => { const translations = await loadTranslations(lang); Object.entries(translations).forEach(([ns, res]) => { if (!i18n.hasResourceBundle(lang, ns)) i18n.addResourceBundle(lang, ns, res); }); await i18n.changeLanguage(lang); };
npm install next-intl
// i18n.ts import { getRequestConfig } from 'next-intl/server'; export const locales = ['en', 'ko'] as const; export default getRequestConfig(async ({ locale }) => ({ messages: { common: (await import(`./locales/modules/common.${locale}.json`)).default, auth: (await import(`./locales/modules/auth.${locale}.json`)).default, }, }));
// middleware.ts import createMiddleware from 'next-intl/middleware'; export default createMiddleware({ locales: ['en', 'ko'], defaultLocale: 'en', localePrefix: 'as-needed' }); export const config = { matcher: ['/((?!api|_next|.*\\..*).*)'] };
// Server Component const t = await getTranslations('auth'); return <h1>{t('login.title')}</h1>; // Client Component const t = useTranslations('auth'); return <button>{t('login.submit')}</button>;
// SEO - generateMetadata export async function generateMetadata({ params: { locale } }) { return { alternates: { languages: Object.fromEntries(locales.map(l => [l, `https://example.com/${l}`])), }, }; }