DEV Community

Cover image for How to manage Internationalization with NextJS SSG
Emmanuel Gautier
Emmanuel Gautier

Posted on • Originally published at emmanuelgautier.com on

How to manage Internationalization with NextJS SSG

Statically generating a website with the NextJS framework in different languages is not so obvious. The framework does not bring clear support for this use case and the NextJS documentation explains that i18n routing is not supported for SSG.

Bootstrap the project

First of all, let's create a new next project from the with-react-intl template

npx create-next-app -e with-react-intl
Enter fullscreen mode Exit fullscreen mode

If don't need anymore to manage any localization client side, you can remove the getInitialProps function and the part for localization in the render function.

The SSR server is useless if you only need SSG as well. So you can remove server tsconfig, server.ts file and change your package.json file script part as follow :

  "scripts": {
    "dev": "next dev",
    "build": "npm run extract:i18n && npm run compile:i18n && next build",
    "export": "next export",
    "extract:i18n": "formatjs extract '{pages,components}/*.{js,ts,tsx}' --format simple --id-interpolation-pattern '[sha512:contenthash:base64:6]' --out-file lang/en.json",
    "compile:i18n": "formatjs compile-folder --ast --format simple lang compiled-lang",
    "start": "next start"
  },
Enter fullscreen mode Exit fullscreen mode

Static Site Generation (aka SSG) with NextJS

When you generate your website statically, it is not possible to use browser request header or any other information from the browser to know which language to use.

We need to introduce a new environment variable NEXT_LOCALE which will contain the locale of the website generated during the export process.

Example of content in .env.* file

NEXT_LOCALE=en
Enter fullscreen mode Exit fullscreen mode

You can now use the NEXT_LOCALE variable in your _app.tsx file in the getInitialProps function to define the locale.

const getInitialProps: typeof App.getInitialProps = async appContext => {
  const locale = appContext.router.locale || process.env.NEXT_LOCALE
  const [supportedLocale, messagePromise] = getMessages(locale)

  const [, messages, appProps] = await Promise.all([
    polyfill(supportedLocale),
    messagePromise,
    App.getInitialProps(appContext),
  ])

  return {
    ...(appProps as any),
    locale: supportedLocale,
    messages: messages.default,
  }
}
Enter fullscreen mode Exit fullscreen mode

Thanks to this variable and the change done, react-intl will now use as locale the content from the env variable. The translated messages taken are now from the right locale.

Now you have a website available for multiple languages. You can build your website for multiple domains as well dealing with multiple build processes, one for each locale. Feel free to implement it with the service you want like Netlify, Vercel, ... etc

The showcase generated for two languages deployed with Vercel :

If you want to know more, have a look into the Source Code

Oldest comments (3)

Collapse
 
bigdog1400 profile image
BigDog1400

thanks for sharing, i was struggling with the compile-folder script.

but now, i am wondering if there is any way to automatically generate lang/.json files for others languages that i want to support. Or the workflow should be send the en.json (or any default languague) file to a third party and then just add the return {lang}.json that they return into the lang/ folder?

Collapse
 
emmanuelgautier profile image
Emmanuel Gautier

You can change the out file to target another language for the default message won't be the good ones. A simple manual way is to target one language and update the other file.

If you want another more industrialized way, you should maybe more integrate with a SaaS platform like Crowdin, PhraseApp, Lokalise, ...

Collapse
 
bigdog1400 profile image
BigDog1400

hi! i already did it. the way that i decided to handle this was extracting always new messages into my main file (in my case es.json), and then with a funtion i compared all the JSON file with the others

function filter(obj1, obj2) {
var result = {};
for(key in obj1) {
if(typeof obj2[key] != 'object') result[key] = obj1[key];
}
return result;
}

it returns all the missing messages in the other file.