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

Foto oleh Unsplash

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.