Next.js i18n dengan next-intl: Panduan Lengkap dari Seseorang yang Benar-Benar Menggunakannya

Foto oleh Unsplash
next-i18next tidak kompatibel dengan App Router Next.js, sementara membuat solusi sendiri dengan next/navigation terbukti terlalu menyulitkan. next-intl dipilih karena mendukung server component dengan benar, menyediakan tipe TypeScript yang baik, dan bekerja dalam konvensi App Router tanpa perlu keluar dari setup standar.
getTranslations() adalah fungsi async yang digunakan di server component dan mengembalikan fungsi t yang diketik sesuai namespace. useTranslations() adalah hook sinkron yang digunakan di client component dan membaca terjemahan dari konteks NextIntlClientProvider. Batasan pentingnya adalah client component hanya bisa mengakses terjemahan yang secara eksplisit diteruskan ke NextIntlClientProvider melalui prop messages.
Secara default, next-intl diam-diam merender string kosong — artinya pengguna Indonesia akan melihat ruang kosong alih-alih fallback ke nilai bahasa Inggris. Untuk mencegah ini, Anda perlu mengonfigurasi opsi onError dan getMessageFallback di NextIntlClientProvider agar mencatat kunci yang hilang dan menggunakan terjemahan bahasa Inggris sebagai cadangan.
next-intl membungkus API Intl JavaScript asli untuk pemformatan tanggal, angka, dan plural. Dalam Bahasa Indonesia, angka menggunakan titik sebagai pemisah ribuan dan koma sebagai pemisah desimal — kebalikan dari bahasa Inggris. Saat Anda menggunakan format.number() dan format.dateTime() dengan konteks lokal, next-intl menangani perbedaan ini secara otomatis tanpa perlu menulis kode pemformatan khusus lokal.
next-intl berfungsi dengan semua target deployment Next.js, termasuk Vercel, Node.js self-hosted, dan container Docker. Di Vercel, middleware berjalan di Edge sehingga deteksi lokal hampir tidak menambah latensi. Satu gotcha penting: saat menggunakan ISR dengan i18n, setiap lokal menghasilkan halaman yang di-cache secara terpisah, sehingga strategi revalidasi Anda harus memperhitungkan ini agar semua versi lokal tetap terkini.

Foto oleh Unsplash
Portofolio yang Anda baca sekarang sepenuhnya diinternasionalisasi dalam bahasa Inggris dan Bahasa Indonesia menggunakan next-intl. Saya tidak memilih next-intl karena postingan blog menyuruh saya — saya memilihnya setelah mencoba next-i18next (tidak kompatibel dengan App Router), mencoba membuat solusi sendiri dengan next/navigation (menyakitkan), dan akhirnya mendarat di next-intl.
Lanskap i18n untuk Next.js App Router membingungkan. Dokumen Next.js resmi menunjukkan cara mengimplementasikan i18n tanpa pustaka apa pun. Pendekatan itu berhasil, tetapi mendorong banyak boilerplate ke Anda. next-intl menangani pemformatan tanggal, pluralisasi, dan kunci terjemahan yang diketik dengan API yang bersih.
next-intl menggunakan segmen dinamis locale di direktori App Router. Semua halaman Anda berada di bawah app/locale/. Fungsi defineRouting di i18n/routing.ts adalah sumber kebenaran tunggal untuk lokal yang didukung dan lokal default.
Di server component, Anda memanggil getTranslations() — fungsi async yang mengembalikan fungsi t yang diketik. Di client component, Anda memanggil useTranslations() — hook sinkron yang membaca terjemahan dari konteks NextIntlClientProvider.
Browser Request: /id/about
│
▼
┌──────────────────────────────────────────────┐
│ middleware.ts (next-intl) │
│ 1. Read Accept-Language / cookie │
│ 2. Match locale: "id" │
│ 3. Rewrite → /id/about (already correct) │
└─────────────────────┬────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ app/[locale]/layout.tsx │
│ getMessages({ locale: "id" }) │
│ → loads messages/id.json │
│ → NextIntlClientProvider wraps children │
└─────────────────────┬────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ Server Component │
│ const t = await getTranslations("about") │
│ t("hero.title") → "Tentang Kami" │
└──────────────────────────────────────────────┘Gunakan createTranslator next-intl dalam fungsi utilitas dan kode lapisan layanan yang perlu menerjemahkan string tetapi bukan komponen React. Ini berguna untuk menghasilkan subjek email yang diterjemahkan, pesan notifikasi, atau pesan kesalahan dalam kode sisi server.
File pesan datar menjadi tidak dapat dikelola dengan cepat. Saya menyusunnya secara hierarkis: common.* untuk string bersama, home.* untuk halaman beranda, blog.* untuk bagian blog termasuk blog.posts.* bersarang untuk konten postingan individual.
next-intl memiliki dukungan TypeScript yang sangat baik saat dikonfigurasi dengan benar. Buat file global.d.ts yang mengimpor tipe pesan Anda. Setelah dikonfigurasi, fungsi t() sepenuhnya diketik — autocomplete menampilkan kunci yang tersedia, dan TypeScript menghasilkan kesalahan jika kunci tidak ada.
// 1. middleware.ts — locale detection
import createMiddleware from "next-intl/middleware"
import { routing } from "./i18n/routing"
export default createMiddleware(routing)
export const config = {
matcher: ["/((?!api|_next|_vercel|.*\..*).*)"],
}
// 2. i18n/routing.ts — define supported locales
import { defineRouting } from "next-intl/routing"
export const routing = defineRouting({
locales: ["en", "id"],
defaultLocale: "en",
localePrefix: "always", // /en/... and /id/...
})
// 3. i18n/navigation.ts — locale-aware Link & redirect
import { createNavigation } from "next-intl/navigation"
import { routing } from "./routing"
export const { Link, redirect, usePathname, useRouter } =
createNavigation(routing)
// 4. app/[locale]/layout.tsx
import { getMessages } from "next-intl/server"
import { NextIntlClientProvider } from "next-intl"
export default async function LocaleLayout({
children,
params: { locale },
}: {
children: React.ReactNode
params: { locale: string }
}) {
const messages = await getMessages()
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
// 5. Server component usage
import { getTranslations } from "next-intl/server"
export default async function AboutPage() {
const t = await getTranslations("about")
return <h1>{t("hero.title")}</h1>
}
// 6. Client component usage
"use client"
import { useTranslations } from "next-intl"
export function NavBar() {
const t = useTranslations("nav")
return <nav>{t("home")}</nav>
}next-intl membungkus API Intl asli untuk pemformatan tanggal, angka, dan plural. Dalam Bahasa Indonesia, angka menggunakan titik sebagai pemisah ribuan dan koma sebagai pemisah desimal — kebalikan dari bahasa Inggris. next-intl menangani ini secara otomatis.
Secara default, next-intl merender string kosong ketika kunci terjemahan hilang. Dalam produksi, terjemahan Indonesia yang hilang diam-diam menampilkan tidak ada apa-apa kepada pengguna Indonesia. Konfigurasi onError dan getMessageFallback di NextIntlClientProvider Anda untuk mencatat kunci yang hilang dan kembali ke nilai bahasa Inggris.
Untuk SEO, setiap halaman memerlukan tag link rel alternate hreflang yang menunjuk ke versi lokal lainnya. Di Next.js App Router, ini dilakukan di generateMetadata menggunakan bidang metadata alternates.languages.
next-intl berfungsi dengan semua target deployment Next.js. Middleware berjalan di Edge secara default di Vercel. Satu gotcha produksi: jika Anda menggunakan ISR dengan i18n, setiap lokal menghasilkan halaman yang di-cache secara terpisah. Pastikan strategi revalidasi Anda memperhitungkan ini.