next-intl: ICU messages, typed keys and localized routing for the Next.js App Router
🌐 Internationalization (i18n) for Next.js
At a glance
- What is it?
- next-intl is an MIT-licensed i18n library for Next.js, built around ICU message syntax, a hooks-based API and per-language pathnames. It fits App Router projects that want translations typed at compile time, and it is a poor fit if you need a framework-agnostic catalog.
- Who is it for?
- Adopt next-intl if your app runs on the Next.js App Router or Pages Router and you want ICU messages, typed keys and per-language pathnames in one package. Do not adopt it if you need a framework-agnostic catalog that also serves a non-Next frontend, or if your team cannot accept a message catalog that is typed only after you configure it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem next-intl solves for Next.js teams
Next.js ships routing, rendering and data fetching, but it does not ship a translation layer. Teams that add one usually end up gluing a general-purpose i18n library onto the App Router, then discovering that Server Components, static rendering and client hooks each need a different wiring. next-intl is aimed squarely at that gap. The README frames it as "Internationalization for Next.js" and lists the parts it covers: ICU message syntax, date, time and number formatting, type-safe message keys, a hooks-based API, and internationalized routing with unique pathnames per language.
The intended audience is a team already committed to Next.js. The feature list names App Router, Server Components and static rendering explicitly, so the library is designed around the rendering model of a specific framework rather than around React in general. If your frontend is Next.js and your problem is that translations, date formatting and language-prefixed URLs are three separate concerns, this is the package that tries to make them one.
ICU messages, a hooks API and the data flow behind them
The mechanism is a message catalog plus a translation function. Messages live in JSON files such as en.json, keyed by namespace. A component calls useTranslations with a namespace name and receives a function, conventionally named t, which looks up a key and interpolates values. The README example shows a UserProfile namespace with a title key, a membership key and a followers key.
The catalog is not plain string substitution. It uses ICU message syntax, so a key can carry a date format, a plural form or a select form. In the README example, membership is written as "Member since {memberSince, date, short}", and followers uses a plural block with =0, =1 and other branches. Formatting is therefore declared in the message file rather than in component code, which is why the README claims dates and numbers can be formatted "without worrying about server/client differences like time zones."
Type safety is the second half of the design. The README says the library gives autocompletion for message keys and catches typos with compile-time checks. That only pays off once your message shape is known to the type system, so the setup work is what buys the autocompletion. Routing is the third piece: next-intl can give each language its own pathnames and optionally localize those pathnames for search engines, which means the URL segment and the message key are configured together rather than derived from each other at runtime.
Installing next-intl and rendering a first translated component
The README points readers to the documentation site at next-intl.dev for setup, and the repository carries runnable examples under examples/, including example-app-router, example-app-router-without-i18n-routing and example-pages-router. The README itself does not print an install command, so treat the package name as the one thing to confirm against the docs before you run anything. The package is published to npm under the name next-intl.
npm install next-intlAfter installation the library is consumed through imports from the package root. The README's own component example imports useTranslations and calls it with a namespace string, then uses the returned function with interpolation values:
// UserProfile.tsx
import {useTranslations} from 'next-intl';
export default function UserProfile({user}) {
const t = useTranslations('UserProfile');
return (
<section>
<h1>{t('title', {firstName: user.firstName})}</h1>
<p>{t('membership', {memberSince: user.memberSince})}</p>
<p>{t('followers', {count: user.numFollowers})}</p>
</section>
);
}The matching catalog file supplies those keys. Note that the plural block in the README is written across several lines with continuation marks, so keep the ICU structure intact when you paste it:
{
"UserProfile": {
"title": "{firstName}'s profile",
"membership": "Member since {memberSince, date, short}",
"followers": "{count, plural, =0 {No followers yet} =1 {One follower} other {# followers}}"
}
}What you should see is the heading rendering the user's first name, the membership line formatted as a short date, and the follower count switching between the zero, one and other branches. If the date renders in the wrong language or the plural picks the wrong branch, the catalog is the place to look first, not the component.
Message drift is real, and next-intl does not catch it alone
The README is unusually direct about a failure mode that every growing app hits: "As an app grows, messages may drift and become inconsistent." A key renamed in code but not in the German file, or a plural argument dropped during a manual edit, will not fail the build on its own. next-intl's type checking covers the keys your code calls; it does not verify that every locale file is complete or that ICU arguments match across locales.
For that the README points to a separate tool, eloqnt/cli, described as a companion that analyzes source code and messages statically. Its sample output shows two distinct problems: an inconsistent-args error where a German title is missing the {firstName} argument, and a missing-translation warning where a key exists in one locale but not another. This is a real boundary of the library. If you adopt next-intl without a linting step, nothing in the package stops a half-translated release, and the README's own framing suggests the maintainers expect you to bring the checker.
There is a second limitation worth naming. The feature list is Next.js-specific by design. Server Components, static rendering and App Router support are the selling points, and they are also the constraint: the package does not present itself as usable in a plain React app or in a non-Next bundler setup. If you share a component library across a Next app and a Vite app, one of the two will need a different solution.
next-intl compared with i18next and react-i18next
The most common alternative for React teams is i18next with react-i18next. The difference is one of scope. i18next is a framework-agnostic translation core with a plugin ecosystem, and react-i18next binds it to React. It does not assume a router, a rendering model or a file layout, so it can serve a Next app, a Vite app and a React Native app from the same catalog.
next-intl makes the opposite trade. It assumes Next.js and buys integration with it: internationalized routing with per-language pathnames, behavior tuned for Server Components and static rendering, and a hooks API documented as a single interface across the codebase. The cost is portability. A team that needs one catalog across several frontends will find i18next's neutrality more valuable than next-intl's framework fit, while a team that is all-in on Next.js gets routing and rendering handled without assembling plugins.
The choice is not about which library translates strings better. Both use message catalogs and interpolation. It is about whether you want the router and the renderer inside the i18n layer or outside it.
Maintenance, licence and what an upgrade actually costs
The repository is not archived, and the last push was on 2026-09-21. Releases are frequent and small: v4.14.6 landed on 2026-09-21, v4.14.5 on 2026-09-14 and v4.14.4 on 2026-09-11. That cadence is patch-level, which suggests incremental fixes rather than a migration treadmill, but it also means the version number moves often and lockfiles will churn. The project is a pnpm and Turborepo monorepo, with lerna publish driving releases and a build-packages script that filters ./packages/** while excluding packages/swc-plugin-extractor. If you contribute rather than consume, that is the layout you are working in.
The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive licence, not a copyleft one, so it does not impose source-disclosure obligations on your application. This is a description of the licence text, not legal advice; if your organisation has a policy on third-party dependencies, route it through the people who own that policy.
The practical upgrade cost sits in your message files, not in the package. Because ICU arguments are declared inside the JSON, a change to how a key is structured touches every locale at once. The repository's CHANGELOG.md is the place to read before bumping, and a lint run over your catalogs is the cheapest way to find out whether a version change broke an argument.
Editorial conclusion
Adopt next-intl if your app runs on the Next.js App Router or Pages Router and you want ICU messages, typed keys and per-language pathnames in one package. Do not adopt it if you need a framework-agnostic catalog that also serves a non-Next frontend, or if your team cannot accept a message catalog that is typed only after you configure it. Before committing, verify three things: that your installed Next.js version is supported, that your message files pass a lint run, and that your localized pathnames behave correctly under static rendering.
Frequently asked questions
How do I install next-intl?
The README does not print an install command and points readers to next-intl.dev for setup. The package is published on npm under the name next-intl, and the repository includes runnable examples under examples/ for the App Router and Pages Router.
What is next-intl?
It is an internationalization library for Next.js, licensed under MIT. The README lists ICU message syntax, date, time and number formatting, type-safe message keys, a hooks-based API and internationalized routing as its features.
How do I use next-intl in a component?
Import useTranslations from next-intl, call it with a namespace name such as 'UserProfile', and use the returned function with interpolation values. The README example renders t('title', {firstName: user.firstName}) against a matching key in en.json.
How does next-intl compare with i18next and react-i18next?
i18next is framework-agnostic with react-i18next binding it to React, so one catalog can serve several frontends. next-intl assumes Next.js and handles internationalized routing, Server Components and static rendering as part of the package.
How do I set up next-intl in a Next.js project?
The README defers setup to next-intl.dev and ships working configurations under examples/, including example-app-router, example-app-router-without-i18n-routing and example-pages-router. Pick the example matching your router and rendering mode rather than assembling the wiring from scratch.
How does next-intl compare with react-i18next?
react-i18next binds the framework-agnostic i18next core to React and assumes no router or rendering model. next-intl is built for Next.js specifically, with internationalized routing and support for Server Components and static rendering.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/amannn-next-intl)