Implementasi i18n dengan Claude Code: panduan praktis Next.js
Workflow praktis untuk i18n Next.js dengan Claude Code, next-intl, pemeriksaan terjemahan, use case, dan jebakan umum.
i18n adalah keputusan arsitektur
i18n berarti internationalization, yaitu menyiapkan aplikasi agar bisa berjalan dalam beberapa bahasa dan wilayah. Di production, i18n bukan sekadar menambah file JSON terjemahan. Kamu juga perlu menentukan struktur URL, deteksi bahasa, metadata SEO, format tanggal dan mata uang, plural rules, internal link, serta pemeriksaan ketika terjemahan hilang. Claude Code cocok untuk pekerjaan ini karena bisa membaca codebase, mengubah banyak file, menjalankan command, dan merangkum risiko. Namun hasilnya akan rapuh jika prompt hanya berbunyi “translate this app”.
Dalam pengalaman Masa mengelola content site dan halaman SaaS, pola paling aman adalah meminta Claude Code membuat satu alur lengkap: dasar routing, message files, migrasi halaman, script validasi, dan checklist review. Dasar ini membuat penambahan bahasa baru menjadi pekerjaan konten, bukan perubahan arsitektur setiap kali ada locale baru.
Panduan ini memakai Next.js App Router dengan next-intl. Saat mereview hasil Claude Code, buka referensi resmi: Claude Code docs, next-intl routing setup, Next.js internationalization guide, dan Intl reference dari MDN. Referensi ini penting karena Next.js versi baru menggunakan proxy.ts, sedangkan project lama bisa saja masih memakai middleware.ts.
flowchart LR
A[Kebutuhan] --> B[Routing]
B --> C[Message JSON]
C --> D[Halaman dan metadata]
D --> E[Pemeriksaan]
E --> F[SEO dan rilis]
Setup dasar Next.js dan next-intl
Contoh ini memakai ja sebagai bahasa default, lalu menambahkan en dan de. Minta Claude Code memeriksa versi Next.js sebelum membuat file, karena proxy.ts dan middleware.ts bergantung pada versi project.
src/
app/
[locale]/
layout.tsx
page.tsx
i18n/
navigation.ts
request.ts
routing.ts
messages/
ja.json
en.json
de.json
proxy.ts
routing.ts menyimpan locale dan path lokal di satu tempat. Ini lebih mudah direview daripada menyebar string /ja atau /en di banyak component.
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['ja', 'en', 'de'],
defaultLocale: 'ja',
pathnames: {
'/': '/',
'/pricing': {
ja: '/pricing',
en: '/pricing',
de: '/preise',
},
'/docs': {
ja: '/docs',
en: '/docs',
de: '/dokumentation',
},
},
});
export type Locale = (typeof routing.locales)[number];
Gunakan wrapper navigation dari next-intl agar link, redirect, dan pergantian bahasa mengikuti konfigurasi yang sama.
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing);
request.ts membaca locale dari request dan memuat file message yang tepat. Jika locale tidak didukung, gunakan bahasa default.
// src/i18n/request.ts
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
Pakai key terjemahan di halaman nyata
Jangan masukkan semua teks ke common. File itu sebaiknya hanya berisi navigation, language selector, dan tombol yang benar-benar dipakai bersama. Teks per halaman lebih baik berada di namespace seperti HomePage, PricingPage, atau DocsPage. Namespace berarti kelompok key yang punya konteks sama.
{
"common": {
"language": {
"label": "Bahasa tampilan",
"ja": "日本語",
"en": "English",
"de": "Deutsch"
},
"nav": {
"docs": "Dokumentasi",
"pricing": "Harga"
}
},
"HomePage": {
"title": "Bagikan pengetahuan tim dalam banyak bahasa",
"lead": "Tampilkan {count} artikel sesuai bahasa pembaca.",
"cta": "Jadwalkan konsultasi"
}
}
Di halaman App Router, getTranslations cocok untuk Server Component dan juga memudahkan terjemahan metadata.
// src/app/[locale]/page.tsx
import { getTranslations, setRequestLocale } from 'next-intl/server';
type Props = {
params: Promise<{ locale: string }>;
};
export default async function HomePage({ params }: Props) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: 'HomePage' });
return (
<main>
<h1>{t('title')}</h1>
<p>{t('lead', { count: 42 })}</p>
<a href={`/${locale}/pricing`}>{t('cta')}</a>
</main>
);
}
Language switcher harus mempertahankan route saat ini dan hanya mengganti locale. Hindari replace string manual, karena path seperti /enquiry bisa ikut berubah.
Cegah terjemahan hilang di CI
Saat Claude Code membuat message files, reviewer sering melihat gaya bahasa, tetapi key yang hilang lebih mudah terlewat. Script berikut membandingkan bahasa dasar dengan locale lain.
// scripts/check-translations.mjs
import { readdir, readFile } from 'node:fs/promises';
const messagesDir = new URL('../src/messages/', import.meta.url);
const baseLocale = 'ja';
function flattenKeys(value, prefix = '') {
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
return [prefix];
}
return Object.entries(value).flatMap(([key, child]) => {
const nextPrefix = prefix ? `${prefix}.${key}` : key;
return flattenKeys(child, nextPrefix);
});
}
async function readMessages(locale) {
const file = new URL(`${locale}.json`, messagesDir);
return JSON.parse(await readFile(file, 'utf8'));
}
const files = await readdir(messagesDir);
const locales = files.filter((file) => file.endsWith('.json')).map((file) => file.replace(/\.json$/, ''));
const baseKeys = new Set(flattenKeys(await readMessages(baseLocale)));
let hasError = false;
for (const locale of locales.filter((item) => item !== baseLocale)) {
const targetKeys = new Set(flattenKeys(await readMessages(locale)));
const missing = [...baseKeys].filter((key) => !targetKeys.has(key));
const extra = [...targetKeys].filter((key) => !baseKeys.has(key));
if (missing.length || extra.length) {
hasError = true;
console.error(`\n${locale}.json has translation key drift`);
if (missing.length) console.error('Missing:', missing.join(', '));
if (extra.length) console.error('Extra:', extra.join(', '));
}
}
if (hasError) process.exit(1);
console.log(`Translation keys are aligned for ${locales.length} locales.`);
Script ini tidak menilai apakah terjemahan terdengar alami. Tugasnya adalah memastikan kontrak key lengkap. Itu sudah cukup untuk mencegah button atau metadata hilang di satu locale.
Tiga use case nyata
Use case pertama adalah halaman harga SaaS. Harga melibatkan mata uang, pajak, periode billing, dan nama paket. Minta Claude Code memakai Intl.NumberFormat atau formatter next-intl, bukan string concatenation. Setelah itu, manusia tetap harus membaca apakah kalimatnya alami untuk pasar lokal.
Use case kedua adalah dokumentasi teknis. Istilah seperti middleware, proxy, locale, dan namespace tidak selalu perlu diterjemahkan. Jelaskan dengan bahasa sederhana pada kemunculan pertama, lalu pertahankan istilah asli supaya pembaca mudah mencari referensi resmi.
Use case ketiga adalah admin dashboard. Risiko utamanya adalah salah operasi. Label “hapus”, “nonaktifkan”, dan “cabut undangan” harus presisi. Button, modal konfirmasi, toast, dan audit log sebaiknya berada dalam sistem key yang sama.
Untuk blog multibahasa, body artikel saja tidak cukup. Title, description, canonical, alternate links, OGP image, dan internal link perlu diperiksa bersama. Tulis aturan ini di CLAUDE.md best practices agar Claude Code konsisten setiap kali update.
Jebakan umum dan hasil verifikasi
| Jebakan | Dampak | Solusi |
|---|---|---|
| Desain URL ditunda | Redirect dan index search menjadi kacau | Pilih prefix, subdomain, atau domain sejak awal |
| Nama key terlalu umum | title2 dan text3 sulit direview | Gunakan nama layar dan makna |
| Terlalu bergantung fallback | Terjemahan hilang tidak terlihat | Buat CI gagal saat key hilang |
| Menggabungkan tanggal dan uang manual | Urutan dan separator salah | Gunakan Intl atau formatter |
| Metadata lupa diterjemahkan | Search result tetap bahasa default | Terjemahkan generateMetadata |
Saya mencoba script ini pada message set kecil dan menghapus HomePage.cta dari file Inggris. Script gagal dan menampilkan key yang hilang. Namun script tidak bisa menentukan apakah “Jadwalkan konsultasi” adalah CTA terbaik untuk semua pasar. Pembagian kerja yang masuk akal adalah: Claude Code dan CI menangkap celah mekanis, manusia mereview tone, harga, legal copy, dan konteks budaya. Untuk langkah berikutnya, gabungkan dengan Claude Code review workflow checklist atau mulai dari konsultasi ClaudeCodeLab.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.