Palu Cursoril või Claude Code'il lisada Next.js rakendusele i18n täna ja saad väga tõenäoliselt middleware.ts, setRequestLocale igas layout'is ja useTranslations kutsutud async lehelt – seadistuse, mis oli korrektne Next.js 15 jaoks ning on Next.js 16 puhul ühe põlvkonna võrra vananenud. globalize.now on tehisintellektil põhinev lokaliseerimisinfrastruktuur, seega just seda kihti me jälgime: marsruutimise juhtmestik muutus, kataloog, mida see teenindab, mitte. Allpool on kirjas, mis muutus, kuidas tuvastada, millisest ajastust su genereeritud kood pärineb, ning kolm parandust, mis võtavad aega pärastlõuna, mitte ümberkirjutuse.

Mitte ükski neist ei ole katkev muudatus. Just seepärast on neid kerge kahe silma vahele jätta.

Mis muutus Next.js 16-s i18n jaoks?

Fail, mis läbirääkib sinu locale, muutis nime. Next.js dokumenteerib middleware faili konventsiooni kui vananenud ja ümbernimetatud proxy-ks, mis jõustus versioonis v16.0.0, ning nende endi selgitus puudutab sõnavara – „middleware'i“ loeti pidevalt Expressi middleware'ina, seega nimetati funktsioon ümber, et kirjeldada võrgupiiri, mis see tegelikult on.

Kaks käitumuslikku märkust kaasnevad v16 ümbernimetamisega. Proxy vaikimisi kasutab Node.js runtime't ning failipõhine runtime konfiguratsioonivalik pole seal enam saadaval – kui selle määrad, viskab Next.js vea. Proxy pole toetatud ka staatilise ekspordi puhul, mis on oluline, kui su locale läbirääkimine on ainus põhjus, miks sa ei ekspordi.

Konkreetselt locale-marsruutimise puhul näitab next-intl seadistusjuhend nüüd faili kui src/proxy.ts ning märgib selgelt, et seda kutsuti middleware.ts kuni Next.js 16-ni. Import selle sees on muutumatu:

// 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|.*\\..*).*)'
};

Pane tähele asümmeetriat, sest see ajab inimesi segadusse: fail on nüüd proxy.ts, samas kui impordi tee on endiselt next-intl/middleware. Impordi ümbernimetamine on tavaline üleparandus.

Miks tuleb tehisintellekti genereeritud i18n kood välja ühe versiooni võrra maas?

Sest koodiagent ennustab kõige enam esindatud mustrit ning kõige enam esindatud muster on see, millel oli aastaid aega koguneda. Iga blogipostitus, Stack Overflow vastus ja GitHubi näide App Router locale-marsruutimise kohta, mis on kirjutatud enne 2026. aasta lõppu, ütleb middleware.ts. Mudel, kes kaalub seda korpust vastu paari nädala vanuse v16 dokumentatsiooniga, valib enesekindlalt vana kuju, ilma hoiatuseta, et ümbernimetamine toimus.

See on sama mehhanism, mis on aluseks probleemile, millest oleme varem kirjutanud – kus Cursor jätkab kõvakoodiga stringide lisamist pärast i18n seadistust – agent taasloob statistiliselt tavapärase koodibaasi, mitte sinu oma. Samuti seepärast instruktsioonifailid parandavad seda GitHub Copiloti puhul vaid osaliselt: reeglid, mida vestlusliides loeb, ei pruugi olla samad, mida inline-täiendamine loeb.

Praktiline tagajärg on kitsas, kuid reaalne. Sinu genereeritud seadistus töötab, seega miski ei ebaõnnestu valjult. Seejärel kohtad viga, otsid selle kohta infot ning iga tänane vastus kirjeldab faile, mida sul pole.

Kuidas tuvastada, millisest ajastust minu genereeritud seadistus pärineb?

Neli grep-käsku. Käivita need projekti juurikaustast ja tead umbes minutiga.

# 1. Pre-16 locale negotiation file
ls middleware.ts src/middleware.ts 2>/dev/null

# 2. Legacy static-rendering API
grep -rn "setRequestLocale" app src 2>/dev/null

# 3. Hooks called inside async components
grep -rn -B3 "useTranslations" app | grep -n "async function"

# 4. Which next-intl era the config reads from
grep -rn "root-params\|await params" src/i18n/request.ts 2>/dev/null

Vasted 1-le ja 2-le tähendavad enne-16 seadistust. Vaste 3-le on ehtne runtime-viga, mis ootab esimest päringut, mis seda komponenti renderdab. Kui 4-le pole vastet, loeb su päringukonfiguratsioon locale't vanaviisi.

Kas ma pean nimetama middleware.ts ümber proxy.ts-iks?

Mitte kiiresti, ja seda ei tohiks käsitsi teha. Next.js pakub codemod'i, mis nimetab ümber nii faili kui ka eksporditud funktsiooni:

npx @next/codemod@canary middleware-to-proxy .

Ümbernimetamine on aegunud märgistus, mitte eemaldamine, seega olemasolev middleware.ts töötab endiselt. Põhjus, miks seda ikkagi käivitada, on hoolduskulu: kui su failinimed vastavad praegusele dokumentatsioonile, kehtib iga tulevane otsingutulemus su hoidlas. Jäta see vananenuks ning maksad väikest maksu igal silumisseansil, igavesti.

Kas setRequestLocale on next-intl's aegunud?

See on märgistatud kui legacy, mis on pehmem väide kui aegunud ning väärib täpset lugemist. next-intl dokumentatsioon kirjeldab setRequestLocale kui API-t, mis eksisteeris kuni next/root-params kasutuselevõtuni, ütleb, et see jääb toetatuks tagasiühilduvuse huvides, ning soovitab selle asemel next/root-params.

Uuem kuju loeb sobitatud locale su päringukonfiguratsioonis, selle asemel, et seda käsitsi läbi iga layout'i ja lehe niiditada:

// src/i18n/request.ts
import * as rootParams from 'next/root-params';
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';

export default getRequestConfig(async ({locale}) => {
  if (!locale) {
    const paramValue = await rootParams.locale();
    if (hasLocale(routing.locales, paramValue)) {
      locale = paramValue;
    } else {
      notFound();
    }
  }

  return {locale};
});

next/root-params on Next.js 16.3 ja uuemates versioonides vaikimisi saadaval; varasemates versioonides tuleb see experimental.rootParams kaudu lubada. Järgi seadistust sel viisil ning staatiline renderdamine tuleb kaasa tasuta, kui su ekspordid endiselt generateStaticParams [locale] segmendi jaoks.

Vana lähenemine nõudis setRequestLocale kutsumist igas lehel ja layout'is, mida soovisid staatiliselt renderdada, enne ükskõik millist teist next-intl kutset, sest Next.js renderdab layout'e ja lehti sõltumatult. See on reegel, mille tehisintellekti agent unustab viiendal faili kirjutamisel. Nõude kustutamine kustutab kogu veaklassi.

Miks useTranslations krahhib minu asünkroonses serverikomponendis?

Sest hook'e ei saa kutsuda async komponentidest ning useTranslations ongi hook. See on React'i serverikomponentide piirang, mitte next-intl iseärasus, ning next-intl vastus on paralleelne rida awaitable funktsioone:

// Async component — await the server API
import {getTranslations} from 'next-intl/server';

export default async function ProfilePage() {
  const user = await fetchUser();
  const t = await getTranslations('ProfilePage');
  return <h1>{t('title', {username: user.name})}</h1>;
}
// Non-async component — the hook is correct here
import {useTranslations} from 'next-intl';

export default function UserDetails({user}) {
  const t = useTranslations('UserProfile');
  return <h2>{t('title')}</h2>;
}

getFormatter, getNow, getTimeZone, getMessages ja getLocale järgivad sama mustrit. Teine näide väärib peatumist: mitteasünkroonne komponent ilma interaktiivsete omadusteta on jagatud komponent ning next-intl valib sobiva rakenduse olenevalt sellest, kas see renderdatakse serveris või kliendipoolel. Seega useTranslations kasutamine serverikomponendis pole viga – seda async komponendist kutsumine on.

Miks ma saan NextIntlClientProvider konteksti vea?

next-intl tõrkeotsingu märkmed toovad välja kaks põhjust ning need nõuavad vastupidiseid parandusi. Kas komponent töötab tegelikult kliendipoolel ilma ülalpool asuva provider'ita, mille puhul ümbritse see ja edasta vajalikud sõnumid, või sattus see kliendimoodulite graafi, kui ootasid serveripoolset renderdust, mille puhul edasta see children kaudu serverikomponendist, mitte ära impordi seda selle sees.

Teine juhtum on see, mida tehisintellekti genereeritud rakendused sagedasti kohtavad, sest agendid rakendavad 'use client' heldelt, et interaktiivsus toimiks, ning direktiiv on nakkav mööda impordigraafi. Eelistatud muster on tõlkida serveris ja edastada valmis stringid üle piiri:

import {useTranslations} from 'next-intl';
import Expandable from './Expandable'; // 'use client'

export default function FAQEntry() {
  const t = useTranslations('FAQEntry');
  return <Expandable title={t('title')}>{t('description')}</Expandable>;
}

Kui mõni komponent tõesti vajab sõnumeid kliendipoolel, saad piirata provider'i just selle alampuuga, selle asemel et saata iga sõnum brauserisse – messages={null} juurprovider'il edastab hoopis mitte midagi.

Mida see kõik ei muuda?

Sinu locale-faile. Iga ülaltoodud parandus on marsruutimise ja renderdamise juhtmestik; JSON, mida su rakendus laadib, jääb sellest kõigest puutumata. See ongi asi, mida tasub meeles pidada, sest juhtmestik on ühepäevane töö, mis muutub kord aastas, samal ajal kui kataloog on see, mis mädaneb iga nädal, kui uus liides ilmub.

Selline jaotus on see, mille jaoks me ehitame. next-intl ja tema naabrid teenindavad tõlkeid käitusajal – me ei konkureeri nendega ning kui sa alles valid nende vahel, siis next-intl vs react-i18next vs Lingui toob välja kompromissid. globalize.now asub ühe kihi kõrgemal, luues võtmed ja locale-failid, mida need runtime'id loevad – seepärast on Next.js integratsioon ükskõikne selle suhtes, kas su läbirääkimine asub middleware.ts või proxy.ts sees.

Teisendus on ühekordne ja toimub rakenduses: ühendad hoidla, globalize.now teisendab koodibaasi ühekordselt ja avab pull request'i koos katalooogiga. Pärast seda tõlgivad push-tööd uued katalooogiüksused, kui need ilmuvad – see ongi tõrke muster, mida kirjeldab miks tõlkefailid pidevalt sünkroonist väljas käivad, ning põhjus, miks seda lõhet tasub sulgeda enne kolmandat lokaali, mitte pärast.

Kui su projektil on juba käsitsi kirjutatud next-intl seadistus, siis oleme kirjutanud täpselt sellest, mida me puudutame ja mida mitte. Kui pole, siis Cursori juhend on lühim tee sisse – hoiatusega, et selle genereeritud fail on nimetatud vanema konventsiooni järgi, seega käivita pärast seda codemod.

Kust alustada

Käivita neli grep-käsku. Kui saad vasted esimesele kahele, käivita codemod, vii oma päringukonfiguratsioon üle next/root-params peale ning oled ajakohane. Seejärel vaata locale-faile, sest see on osa, mis lahkneb ka järgmisel kuul. Kui ehitad tehisintellektiga loodud rakendust ja soovid, et kataloog oleks hallatud, mitte lihtsalt hooldatud, siis vibe-koodijate lehekülg on ülevaade ja hinnastus on eraldi lehel.

globalize.now muudab rakenduse koodis olevad tekstid tõlkeks valmis lokaale failideks ja hoiab neid ajakohasena, kui sa uusi versioone välja lasid.

Proovi globalize.now tasuta