npm install react-i18next i18next
// locales/en.json
{
"toolbar": {
"upload": "Upload",
"grayscale": "Grayscale",
"brightness": "Brightness",
"export": "Export"
},
"gallery": {
"title": "Your Gallery",
"empty": "No images yet." +
" Upload your first!",
"imageCount":
"{{count}} image",
"imageCount_plural":
"{{count}} images"
}
}
// locales/es.json
{
"toolbar": {
"upload": "Subir",
"grayscale": "Escala de grises",
"brightness": "Brillo",
"export": "Exportar"
},
"gallery": {
"title": "Tu Galería",
"empty": "Aún no hay imágenes." +
" ¡Sube la primera!",
"imageCount":
"{{count}} imagen",
"imageCount_plural":
"{{count}} imágenes"
}
}
// locales/ja.json
{
"toolbar": {
"upload": "アップロード",
"grayscale": "グレースケール",
"brightness": "明るさ",
"export": "エクスポート"
},
"gallery": {
"title": "ギャラリー",
"empty": "まだ画像がありません",
"imageCount": "{{count}}枚の画像"
}
}
import { useTranslation } from
'react-i18next';
function Toolbar() {
const { t } = useTranslation();
return (
<nav>
<button>
{t('toolbar.upload')}
</button>
<button>
{t('toolbar.grayscale')}
</button>
<button>
{t('toolbar.brightness')}
</button>
<button>
{t('toolbar.export')}
</button>
</nav>
);
}
// Pluralization (automatic):
function ImageCount({ count }: {
count: number
}) {
const { t } = useTranslation();
return <p>
{t('gallery.imageCount',
{ count })}
</p>;
// 1 → "1 image"
// 5 → "5 images"
// (auto-selects plural form)
}
// Set document direction
<html
dir={isRTL ? 'rtl' : 'ltr'}
lang={currentLanguage}>
// CSS: logical properties instead
// of physical
.toolbar {
padding-inline-start: 8px;
/* Instead of padding-left */
margin-inline-end: 12px;
/* Instead of margin-right */
}
.sidebar {
inset-inline-start: 0;
/* Instead of left: 0 */
}
// Logical properties flip
// automatically when dir="rtl".
// No separate RTL stylesheet needed.
// Built-in Intl API — no library needed
// Dates:
new Intl.DateTimeFormat('en',
{ dateStyle: 'long' })
.format(new Date());
// → "February 18, 2026"
new Intl.DateTimeFormat('ja',
{ dateStyle: 'long' })
.format(new Date());
// → "2026年2月18日"
// Numbers:
new Intl.NumberFormat('en')
.format(1234567);
// → "1,234,567"
new Intl.NumberFormat('de')
.format(1234567);
// → "1.234.567"
// File sizes:
function formatFileSize(
bytes: number, locale: string
) {
const mb = bytes / (1024 * 1024);
return new Intl.NumberFormat(locale, {
maximumFractionDigits: 1
}).format(mb) + ' MB';
}
git switch -c feature/PIXELCRAFT-090-i18n
git add src/ locales/
git commit -m "Add i18n: en, es, ja + RTL support (PIXELCRAFT-090)"
git push origin feature/PIXELCRAFT-090-i18n
# PR → Review → Merge → Close ticket ✅
Unicode is the foundation of internationalization.
It assigns a unique code point to every character in every human script: 149,186 characters across 161 scripts.