Ako želite doprijeti do ljudi diljem svijeta, morate napraviti web stranicu koja radi na više jezika. Dodavanje sadržaja na više jezika vašoj poslovnoj stranici, e-commerce platformi ili SaaS aplikaciji značajno poboljšava korisničko iskustvo i pomaže vam dosegnuti više ljudi. Ovaj detaljan vodič će vas provesti kroz implementaciju internacionalizacije (i18n) u Next.js koristeći next-intl, biblioteku koja je napravljena upravo za App Router arhitekturu.
Instalacija
Prvo instalirajte next-intl paket koristeći vaš preferirani package manager:
npm install next-intl
Ili s Yarnom:
yarn add next-intl
Ili s pnpm-om:
pnpm add next-intl
Zašto next-intl?
next-i18next je napravljen za Pages Router. Radi s App Routerom, ali integracija djeluje forsirano. react-intl nema ugrađenu podršku za routing.
Next-intl podržava:
- Nativne Server Components.
- TypeScript autocomplete za ključeve prijevoda.
- Ugrađenu URL lokalizaciju (npr. /about postaje /hr/o-nama).
- Automatsku detekciju jezika na temelju postavki preglednika.
Struktura projekta
Vaš projekt bi trebao izgledati ovako:
your-project/
├── messages/
│ ├── en/
│ │ └── common.json
│ └── hr/
│ └── common.json
└── src/
├── i18n/
│ ├── routing.ts
│ ├── request.ts
│ └── navigation.ts
└── app/
└── [locale]/
├── layout.tsx
└── page.tsx
└── middleware.ts
Mapa messages sadrži JSON datoteke za prijevode sortirane po jeziku. Konfiguracijske datoteke se nalaze u mapi src/i18n. Mapa [locale] je dinamički segment rute koji dohvaća trenutni jezik iz URL-a.
Datoteke prijevoda
JSON datoteke organizirane u namespace-ove čine datoteke prijevoda. Svaki namespace ima zasebnu datoteku koja grupira povezane prijevode.
messages/en/common.json:
{
"navigation": {
"home": "Home",
"about": "About Us"
},
"buttons": {
"submit": "Submit",
"cancel": "Cancel"
}
}
messages/hr/common.json:
{
"navigation": {
"home": "Pocetna",
"about": "O nama"
},
"buttons": {
"submit": "Posalji",
"cancel": "Odustani"
}
}
Struktura ključeva u obje datoteke mora biti ista. Upozorenja se pojavljuju u konzoli kada ključevi nedostaju.
Konfiguracija routinga
src/i18n/routing.ts:
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en', 'hr'],
defaultLocale: 'en',
localePrefix: 'as-needed',
pathnames: {
'/': '/',
'/about': { hr: '/o-nama' },
'/services': { hr: '/usluge' },
'/blog/[slug]': { hr: '/blog/[slug]' },
},
});
localePrefix: "as-needed" čini URL-ove ljepšima (engleski prikazuje /about, a hrvatski prikazuje /hr/o-nama). Objekt pathnames povezuje interne putanje s lokaliziranim URL-ovima.
Postavljanje Middlewarea
middleware.ts (korijen projekta):
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
};
Middleware prvo gleda URL prefiks za pronalazak jezika, zatim gleda kolačiće, i na kraju gleda Accept-Language header preglednika. Preusmjerava korisnika na ispravan URL uzorak i sprema njegovu preferenciju u kolačić.
Postavljanje Layouta
src/app/[locale]/layout.tsx:
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { hasLocale } from 'next-intl';
import { routing } from '@/i18n/routing';
export default async function RootLayout({ children, params }) {
const { locale } = params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
const messages = await getMessages();
return (
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
);
}
Korištenje prijevoda
Server Components
src/app/[locale]/about/page.tsx:
import { getTranslations } from 'next-intl/server';
export default async function AboutPage() {
const t = await getTranslations('common');
return (
<>
<h1>{t('navigation.about')}</h1>
<button>{t('buttons.submit')}</button>
</>
);
}
Client Components
'use client';
import { useTranslations } from 'next-intl';
export default function ContactForm() {
const t = useTranslations('common');
return <button>{t('buttons.submit')}</button>;
}
U server komponentama koristite
getTranslations; u client komponentama koristiteuseTranslations. Prijevodi server komponenti uopće ne utječu na veličinu client bundlea.
Dinamičke vrijednosti
// JSON: { "greeting": "Hello, {name}!" }
t('greeting', { name: 'John' });
// Output: "Hello, John!"
Prekidač jezika
'use client';
import { useLocale } from 'next-intl';
import { useRouter, usePathname } from '@/i18n/navigation';
import { useParams } from 'next/navigation';
export default function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const switchLanguage = (newLocale: string) => {
const slug = params.slug as string | undefined;
if (slug) {
router.replace(
{ pathname: pathname as any, params: { slug } },
{ locale: newLocale }
);
} else {
router.replace(pathname, { locale: newLocale });
}
};
return (
<div>
<button onClick={() => switchLanguage('en')} disabled={locale === 'en'}>
EN
</button>
<button onClick={() => switchLanguage('hr')} disabled={locale === 'hr'}>
HR
</button>
</div>
);
}
Za korištenje dinamičkih ruta, morate proslijediti
paramsobjekt promjeni routera. Bez toga URL prikazuje doslovce[slug]zagrade.
Dodavanje više prijevoda
- Napravite JSON datoteke u svakoj jezičnoj mapi (
messages/en/new-ns.json,messages/hr/new-ns.json). - U
next.config.js, dodajte englesku datoteku ucreateMessagesDeclaration. - U
src/i18n/request.ts, importajte namespace. - U
src/i18n/global.d.ts, dodajte deklaraciju tipa. - Restartajte dev server.
Najčešći problemi i rješenja
Konzola prikazuje "Message is missing" upozorenje
Ako vidite upozorenje da poruka nedostaje, provjerite da je ključ prisutan u svim jezičnim datotekama. Imajte na umu da su ključevi osjetljivi na velika i mala slova.
[slug] je doslovno u URL-u
Kada se [slug] prikazuje doslovno u URL-u umjesto stvarne vrijednosti, morate proslijediti params objekt promjeni routera na dinamičkim rutama. Pogledajte primjer Prekidača jezika iznad za ispravnu implementaciju.
Nema TypeScript autocompletea
Kada autocomplete ne radi, restartajte dev server. Također provjerite da je datoteka dodana u createMessagesDeclaration konfiguraciju.
URL-ovi ne rade
Ako lokalizirani URL-ovi ne rade ispravno, dodajte pathname mapiranje u routing.ts. Također očistite cache preglednika jer stare rute mogu biti spremljene.








