28. Internationalization (i18n)
Dalam pengembangan aplikasi berskala global atau regional (seperti di Indonesia yang sering kali membutuhkan pilihan bahasa Indonesia dan Inggris), kemampuan aplikasi untuk beradaptasi dengan bahasa lokal pengguna sangatlah krusial. Proses ini dibagi menjadi dua konsep utama:
- Internationalization (i18n): Proses merancang dan membangun aplikasi agar dapat diadaptasi ke berbagai bahasa dan wilayah dengan mudah tanpa perlu menulis ulang kode program.
- Localization (l10n): Proses menerjemahkan teks, menyesuaikan format tanggal, angka, mata uang, dan aspek kultural lainnya untuk wilayah tertentu.
Dokumentasi ini menjelaskan standar implementasi i18n menggunakan next-intl untuk Next.js (App Router) dan react-i18next untuk React Single Page Application (SPA).
A. Perbandingan Library i18n Populer
| Kriteria | next-intl (Direkomendasikan untuk Next.js) | react-i18next (Direkomendasikan untuk React SPA) |
|---|---|---|
| Integrasi Router | Sangat erat dengan Next.js App Router (Routing, Middleware, Link, Redirect). | Independen dari router, biasanya dikelola secara manual dengan state/URL parameter. |
| Kinerja SSR & RSC | Terintegrasi penuh dengan React Server Components (RSC) tanpa overhead JavaScript di sisi klien. | Membutuhkan setup khusus agar bekerja baik di SSR, cenderung berpusat di client-side. |
| Struktur File | File terjemahan JSON terpusat di direktori utama (misal /messages). | File terjemahan JSON di folder /public/locales atau di dalam bundel source code. |
| Tipe Data (Type-Safety) | Mendukung validasi tipe data kunci terjemahan berbasis TypeScript bawaan. | Membutuhkan konfigurasi manual untuk mendukung type-safe translation keys. |
B. Alur Kerja Deteksi & Rendering Bahasa (Next.js)
Mermaid diagram di bawah mengilustrasikan alur pemrosesan dari request pengguna hingga halaman disajikan dalam bahasa yang sesuai:
graph TD
A[Pengguna mengakses URL] --> B{Apakah URL memiliki awalan bahasa? /id/ atau /en/}
B -- Tidak --> C[Middleware mendeteksi bahasa dari Cookie / Accept-Language Header]
C --> D[Redirect ke URL dengan awalan yang sesuai, misal: /id/dashboard]
B -- Ya --> E[Middleware memvalidasi kecocokan locale yang didukung]
E --> F[Next.js memuat file terjemahan JSON yang relevan, misal: messages/id.json]
F --> G[Render halaman di Server dengan Server Components]
G --> H[Kirim HTML terjemahan ke Browser]
H --> I[Hydration di Client-side untuk elemen interaktif]
C. Implementasi Praktis: next-intl di Next.js (App Router)
Berikut adalah arsitektur direktori proyek Next.js yang mendukung multibahasa secara terstruktur:
├── messages/ # Direktori penyimpanan file JSON terjemahan
│ ├── en.json
│ └── id.json
├── src/
│ ├── middleware.ts # Interseptor untuk mendeteksi bahasa di URL/Cookie
│ ├── i18n/
│ │ ├── routing.ts # Definisi rute dan bahasa yang didukung
│ │ └── request.ts # Konfigurasi Next-intl request
│ └── app/
│ └── [locale]/ # Dynamic route segment untuk membungkus seluruh halaman
│ ├── layout.tsx
│ └── page.tsx
Langkah 1: Setup File Konfigurasi Rute (src/i18n/routing.ts)
Buat file untuk mengelola daftar bahasa yang didukung dan utilitas navigasi:
import {defineRouting} from 'next-intl/routing';
import {createNavigation} from 'next-intl/navigation';
export const routing = defineRouting({
// Daftar bahasa yang didukung oleh aplikasi
locales: ['en', 'id'],
// Bahasa default jika tidak ada bahasa yang cocok atau tertera di URL
defaultLocale: 'id',
// Menghilangkan prefix locale di URL untuk default locale (opsional)
localePrefix: 'as-needed'
});
// Ekspor utilitas navigasi yang sadar-bahasa (locale-aware)
export const {Link, redirect, usePathname, useRouter} = createNavigation(routing);
Langkah 2: Setup Request Configuration (src/i18n/request.ts)
Next-intl membutuhkan konfigurasi ini untuk memuat pesan JSON secara dinamis saat proses render server-side berjalan:
import {getRequestConfig} from 'next-intl/server';
import {routing} from './routing';
export default getRequestConfig(async ({locale}) => {
// Validasi jika locale tidak didukung
if (!routing.locales.includes(locale as any)) {
return {
messages: (await import('../../messages/id.json')).default
};
}
return {
messages: (await import(`../../messages/${locale}.json`)).default
};
});
Langkah 3: Setup Next Config (next.config.mjs)
Integrasikan plug-in next-intl pada bundler webpack Next.js Anda:
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
/** @type {import('next').NextConfig} */
const nextConfig = {
// Konfigurasi Next.js Anda di sini
reactStrictMode: true,
};
export default withNextIntl(nextConfig);
Langkah 4: Setup Middleware (src/middleware.ts)
Konfigurasikan middleware untuk mengarahkan pengguna secara otomatis ke bahasa yang sesuai:
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// Hanya jalankan middleware pada rute aplikasi, abaikan file statis & API
matcher: ['/', '/(id|en)/:path*', '/((?!api|_next|_vercel|.*\\..*).*)']
};
Langkah 5: Layout Utama dengan [locale] Wrapper (src/app/[locale]/layout.tsx)
Bungkus aplikasi Anda untuk menyediakan payload terjemahan ke seluruh komponen:
import {NextIntlClientProvider} from 'next-intl';
import {getMessages, setRequestLocale} from 'next-intl/server';
import {routing} from '@/i18n/routing';
import {notFound} from 'next/navigation';
export function generateStaticParams() {
return routing.locales.map((locale) => ({locale}));
}
export default async function LocaleLayout({
children,
params: {locale}
}: {
children: React.ReactNode;
params: {locale: string};
}) {
// Validasi parameter rute
if (!routing.locales.includes(locale as any)) {
notFound();
}
// Mengatur locale secara sinkron untuk Server Components
setRequestLocale(locale);
// Memuat pesan terjemahan di server
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
D. Struktur Berkas Terjemahan JSON (Best Practices)
Terapkan standarisasi penamaan berkas JSON dan buatlah struktur berjenjang (nested) berdasarkan domain atau nama komponen agar tidak menumpuk.
Contoh File: messages/id.json
{
"Common": {
"buttons": {
"save": "Simpan Perubahan",
"cancel": "Batal",
"delete": "Hapus"
},
"alerts": {
"success": "Aksi berhasil diselesaikan."
}
},
"Dashboard": {
"title": "Dasbor Utama",
"welcome": "Selamat datang kembali, {name}!",
"status": "{count, plural, =0 {Tidak ada tugas pending} one {Ada 1 tugas pending} other {Ada # tugas pending}}"
}
}
Contoh File: messages/en.json
{
"Common": {
"buttons": {
"save": "Save Changes",
"cancel": "Cancel",
"delete": "Delete"
},
"alerts": {
"success": "Action completed successfully."
}
},
"Dashboard": {
"title": "Main Dashboard",
"welcome": "Welcome back, {name}!",
"status": "{count, plural, =0 {No pending tasks} one {You have 1 pending task} other {You have # pending tasks}}"
}
}
[!NOTE]
- Interpolasi: Gunakan kurung kurawal
{name}untuk memasukkan nilai dinamis.- Pluralisasi: Gunakan format sintaks ICU Message (
{count, plural, ...}) untuk menangani variasi jumlah barang secara tata bahasa.
E. Cara Penggunaan Translation Hooks & Utilities
1. Di React Server Components (RSC)
Untuk menjaga performa dan kecepatan render, gunakan utilitas server-side yang bersifat asinkronous:
// src/app/[locale]/page.tsx
import {getTranslations} from 'next-intl/server';
export default async function Page() {
// Memuat namespace secara spesifik
const t = await getTranslations('Dashboard');
return (
<main>
<h1>{t('title')}</h1>
{/* Hasil dengan Interpolasi: */}
<p>{t('welcome', {name: 'Alex'})}</p>
</main>
);
}
2. Di React Client Components
Untuk komponen interaktif (dengan direktif 'use client'), gunakan hook standar:
'use client';
import {useTranslations} from 'next-intl';
import {Link} from '@/i18n/routing';
export default function Counter() {
const t = useTranslations('Common.buttons');
return (
<div>
<button onClick={() => alert('Saved')}>{t('save')}</button>
<Link href="/about">{t('cancel')}</Link>
</div>
);
}
[!WARNING] Selalu pastikan menggunakan
Linkyang diekspor dari@/i18n/routing(bukan darinext/linkbawaan).Linkkhusus ini akan secara otomatis menyisipkan prefix bahasa aktif ke URL tujuan (misal dari/aboutmenjadi/id/about).
F. Penanganan Kasalahan & Gotchas (Troubleshooting)
1. Masalah Hydration Mismatch (Server vs Client)
Kesalahan hydration mismatch sering terjadi apabila nilai waktu, tanggal, atau mata uang diformat di server menggunakan locale default server Node.js, tetapi ketika sampai di browser diterjemahkan ulang menggunakan locale browser klien.
// ❌ HINDARI (Rentan memicu hydration mismatch):
<div>{new Date().toLocaleDateString()}</div>
Solusi Terstruktur:
Gunakan utilitas format bawaan dari next-intl yang membaca konfigurasi locale dari context terpusat:
import {useFormatter} from 'next-intl';
export default function DateTimeComponent() {
const format = useFormatter();
const dateTime = new Date('2026-06-26T12:00:00Z');
return (
<div>
{/* Output id: 26 Juni 2026 | Output en: June 26, 2026 */}
<p>{format.dateTime(dateTime, {dateStyle: 'long'})}</p>
{/* Output id: Rp150.000,00 | Output en: $150,000.00 */}
<p>{format.number(150000, {style: 'currency', currency: 'IDR'})}</p>
</div>
);
}
2. Menghindari Hardcoded String dengan ESLint
Dalam tim besar, developer sering kali lupa dan tidak sengaja menulis teks langsung (hardcoded string) di dalam JSX. Untuk mencegah hal ini, Anda dapat memasang plugin ESLint:
npm install -D eslint-plugin-i18next
Tambahkan pengaturan di berkas konfigurasi .eslintrc.json:
{
"plugins": ["i18next"],
"rules": {
"i18next/no-literal-string": ["warn", {
"markupOnly": true,
"ignoreAttribute": ["className", "style", "type", "id", "href", "src"]
}]
}
}
Aturan ini akan memberikan peringatan jika ada teks literal yang ditulis di JSX tanpa menggunakan pemanggilan fungsi t().
3. Penanganan Kunci Terjemahan yang Hilang (Fallback)
Jika kunci terjemahan tidak ditemukan pada file JSON, secara default next-intl akan mengembalikan nama kunci itu sendiri (misal Dashboard.welcome). Anda dapat mengonfigurasi fallback khusus di src/i18n/request.ts jika ingin menampilkan teks default atau pesan tertentu agar UI tidak rusak.
G. Optimasi SEO untuk Website Multibahasa
Untuk memastikan robot perayap mesin pencari (seperti Googlebot) dapat memahami variasi bahasa di situs Anda dengan benar:
- Gunakan Tag
hreflang: Informasikan kepada mesin pencari semua URL alternatif yang memiliki konten serupa dalam bahasa berbeda. - Gunakan Tag
<html lang="...">: Pastikan elemen HTML memiliki atribut bahasa yang akurat sesuai bahasa aktif di halaman saat ini. - Hindari Redireksi Berdasarkan IP secara Agresif: Jangan langsung memaksa pengguna berpindah bahasa secara otomatis berdasarkan alamat IP mereka tanpa memberikan opsi kembali. Tindakan ini menyulitkan Googlebot untuk mengindeks halaman dengan variasi bahasa non-default.
Di Next.js, Anda dapat mengimplementasikan konfigurasi metadata bahasa secara dinamis:
// src/app/[locale]/layout.tsx
import {Metadata} from 'next';
type Props = {
params: {locale: string};
};
export async function generateMetadata({params: {locale}}: Props): Promise<Metadata> {
return {
title: locale === 'id' ? 'Beranda' : 'Home',
alternates: {
languages: {
'id-ID': '/id',
'en-US': '/en'
}
}
};
}
H. Referensi Tambahan
- Next-intl Official Documentation: Dokumentasi terlengkap untuk penerapan i18n di Next.js App Router.
- React.i18next Documentation: Dokumentasi utama untuk setup i18n di React standar / Single Page Application.
- ICU Message Format Guidelines: Standar sintaksis penulisan dynamic interpolation dan pluralisasi pada file JSON terjemahan.
- Google Search Central - Multiregional and Multilingual Sites: Praktik terbaik dari Google untuk manajemen SEO pada website multibahasa.