@inlang/paraglide-js-adapter-next
v3.3.0
Published
![Dead Simple i18n. Typesafe, Small Footprint, SEO-Friendly and IDE Integration.](https://cdn.jsdelivr.net/gh/opral/monorepo@latest/inlang/source-code/paraglide/paraglide-js-adapter-next/assets/header.png)
Downloads
213
Readme
Getting Started
Get started instantly with the Paraglide-Next CLI.
npx @inlang/paraglide-js-adapter-next init
npm install
The CLI will ask you which languages you want to support. This can be changed later.
It will:
- Create an Inlang Project
- Create translation files for each of your languages
- Create a middleware file
- Create
lib/i18n.ts
file - Update your
next.config.js
file to use the Paraglide-Next Plugin. - Add the
<LanguageProvider>
wrapper to yourapp/layout.tsx
component.
You can now start your dev-server and visit /de
, /ar
, or whatever languages you've set up.
Adding Messages
Your messages live in messages/{languageTag}.json
files. You can add messages in these files as key-value pairs of the message ID and the translations.
Use curly braces to add parameters.
// messages/en.json
{
// The $schema key is automatically ignored
"$schema": "https://inlang.com/schema/inlang-message-format",
"hello_world" : "Hello World!",
"greetings": "Greetings {name}."
}
Learn more about the format in the Inlang Message Format Documentation.
## Using Messages in Code
Use messages by importing them from @/paraglide/messages.js
. By convention, we do a wildcard import as m
.
import * as m from "@/paraglide/messages.js"
export function Home() {
return (
<>
<h1>{m.homepage_title()}</h1>
<p>{m.homepage_subtitle({ some: "param" })}</p>
</>
)
}
Only messages used in client components are sent to the client. Messages in Server Components don't impact bundle size.
Localized navigation APIs
While you can now visit /de/some-page
you still need to add the language-prefix to every single link. Wouldn't it be nice if that happened automatically?
For this the package provides Localised Navigation APIs. These are exported from @/lib/i18n.js
.
To get localized <Link>
s you need to replace the ones from next/link
with the ones from @/lib/i18n.js
. Just find & replace the imports.
- import Link from "next/link"
+ import { Link } from "@/lib/i18n"
// This now links to /de/about depending on the current language
<Link href="/about">
You can do the same for the other navigation APIs.
- import { usePathname, useRouter, redirect, permanentRedirect} from "next/navigation"
+ import { usePathname, useRouter, redirect, permanentRedirect} from "@/lib/i18n"
Advanced Usage
Translated Metadata
To return different metadata for each language, we will need to use generateMetadata
.
export async function generateMetadata() {
return {
title: m.home_metadata_title(),
description: m.home_metadata_description(),
}
}
If you were to use
export const metadata
your metadata would always end up in the source language.
Linking to Pages in Other Languages
If you want a Link to be in a specific language you can use the locale
prop.
<Link href="/about" locale="de">
This is convenient for constructing language switchers.
If you are using router.push
to navigate you can pass locale
as an option.
function Component() {
const router = useRouter()
return (
<button onClick={() => router.push("/about", { locale: "de" })}>Go to German About page</button>
)
}
Excluding certain routes from i18n
You can exclude certain routes from i18n using the exclude
option on createI18n
. You can either pass a string or a regex.
export const { ... } =
createI18n<AvailableLanguageTag>({
//array of routes to exclude
exclude: [
/^\/api(\/.*)?$/ //excludes all routes starting with /api
"/admin" //excludes /admin, but not /admin/anything - globs are not supported
],
})
Excluded routes won't be prefixed with the language tag & the middleware will not add Link
headers to them.
Tip: LLMs are really good at writing regexes.
Changing the default language
By default, the default language is the sourceLanguageTag
defined in project.inlang/settings.json
. You can change it with the defaultLanguage
option.
export const { ... } =
createI18n<AvailableLanguageTag>({
defaultLanguage: "de"
})
How language-detection works
The adapter follows these steps to determine the language.
- First, the adapter will try to determine the language based on the URL.
- If that fails, it will look for a
NEXT_LOCALE
cookie. - If that isn't available either, it will try to negotiate the language based on the
Accept-Language
header. - Finally, it will fall back to the default language.
If a language has been determined once, it will set the NEXT_LOCALE
cookie so that future ambiguities don't result in random language switches.
Translated Pathnames
You can use different pathnames for each language with the pathname
option.
Pathnames should not include a language prefix or the base path.
export const { ... } =
createI18n<AvailableLanguageTag>({
pathname: {
"/about": {
en: "/about",
de: "/ueber-uns"
}
}
})
You can use parameters in pathnames with square brackets. You have to use an identical set of parameters in both the canonical and translated pathnames.
You can use double-square brackets for optional parameters and the spread operator to make it a match-all parameter.
pathname: {
"/articles/[slug]": {
en: "/articles/[slug]",
de: "/artikel/[slug]"
},
"/admin/[...rest]": {
en: "/administration/[...rest]",
de: "/admin/[...rest]"
},
}
You can also use a message as a pathname. The translation will be used as the pathname. You can use parameters here too.
// messages/en.json
{
"about_pathname": "/about"
}
// messages/de.json
{
"about_pathname": "/ueber-uns"
}
export const { ... } =
createI18n<AvailableLanguageTag>({
pathname: {
"/about": m.about_pathname //pass as reference
}
})
Be careful when using translated pathnames in combination with prefix: "never"
. Links may not work if they are shared between people with different languages.
Getting a localized Pathname
There are situations where you need to know the localized version of a pathname. You can use the localizePathname
function for that.
import { localizePathname } from "@/lib/i18n"
localizePathname("/about", "de") // de/ueber-uns
This does not include the basePath
.
Alternate Links
Search engines like Google expect you to tell them about translated versions of your pages. The adapter does this by default by adding the Link
Header to requests.
You don't need to add the translated versions of your site to your sitemap, although it doesn't hurt if you do.
Right-to-Left Support
Define a map of all languages to their text-direction & index into it.
import { languageTag, type AvailableLanguageTag } from "@/paraglide/runtime.js"
// This is fully type-safe & forces you to keep it up-to-date
const direction: Record<AvailableLanguageTag, "rtl" | "ltr"> = {
en: "ltr",
ar: "rtl",
}
// src/app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<LanguageProvider>
<html lang={languageTag()} dir={direction[languageTag()]}>
//...
We discourage using the
Intl.Locale
API for text-direction as it's still poorly supported
(Legacy) Setup With the Pages Router
The Pages router already comes with i18n support out of the box. Thus, Paraglide doesn't need to provide routing. All the Adapter does in the Pages router is react to the language change & run the compiler.
In next.config.js
, add the paraglide
plugin.
const { paraglide } = require("@inlang/paraglide-js-adapter-next/plugin")
module.exports = paraglide({
paraglide: {
project: "./project.inlang",
outdir: "./src/paraglide",
}
})
Then add an i18n
object and specify the locales you want to support. Make sure these match the ones in your project.inlang/settings.json
file.
module.exports = paraglide({
paraglide: {
project: "./project.inlang",
outdir: "./src/paraglide",
},
i18n: {
locales: ["en", "de"],
defaultLocale: "en",
},
})
NextJS will automatically prefix all routes with the locale. For example, the route /about
will become /en/about
for the English locale and /de/about
for the German locale. Only the default locale won't be prefixed.
Finally, wrap your _app.js
file with the ParaglideJS
component.
import { ParaglideJS } from "@inlang/paraglide-js-adapter-next/pages"
export default function App({ Component, pageProps }: AppProps) {
return (
<ParaglideJS>
<Component {...pageProps} />
</ParaglideJS>
)
}
You can now use Paraglide's messages in your components.
import * as m from "@/paraglide/messages.js"
export default function Home() {
return (
<div>
<h1>{m.hello_world()}</h1>
</div>
)
}
Don't forget to set the lang
attribute on the html
element in src/pages/_document.js
.
import { languageTag } from "@/paraglide/runtime"
import { Html, Head, Main, NextScript } from "next/document"
export default function Document() {
return <Html lang={languageTag()}>...</Html>
}
Known Limitations
There are some known limitations with this adapter:
output: static
isn't supported yet.- Evaluating messages in module scope always renders the source language.
- Server actions that aren't inside a
.tsx
file will always read the default language unlesssetLanguageTag(()=>headers().get("x-language-tag"))
is called at the top of the file.
Roadmap to 1.0
- Static Export support
- Simplify Setup
- Cookie & Domain Routing Strategies
Examples
You can find example projects in our GitHub repository, or try them on StackBlitz: