Skip to main content
BlogWeb Dev

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

Panduan langsung dari pengalaman tentang menyiapkan next-intl di proyek Next.js App Router — mulai routing, server component, client component, hingga jebakan yang saya temui di produksi.

Matthews Wong

Software Engineer

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

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.

Mengapa next-intl dan Bukan Alternatif Lain

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.

Pengaturan Routing: Segmen Dinamis locale

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.

Server Component vs Client Component

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.

Struktur File Pesan yang Dapat Diskalakan

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.

Integrasi TypeScript: Kunci Terjemahan yang Diketik

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>
}

Pemformatan Tanggal, Angka, dan Plural

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.

SEO: Tag hreflang Alternatif dan generateMetadata

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.

Deployment: Yang Perlu Diketahui

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.

Sumber & Bacaan Lanjutan

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Mengapa Anda memilih next-intl dibandingkan next-i18next atau membuat solusi i18n sendiri?

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.

Apa perbedaan antara getTranslations() dan useTranslations() di next-intl?

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.

Apa yang terjadi jika kunci terjemahan tidak ada di file pesan lokal yang aktif?

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.

Bagaimana next-intl menangani pemformatan angka dan tanggal yang spesifik per lokal?

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.

Apa yang perlu diperhatikan saat men-deploy aplikasi next-intl dengan ISR (Incremental Static Regeneration)?

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.