DEV Community

Cover image for 😱 Static HTML Export with i18n compatibility in Next.js 😱

😱 Static HTML Export with i18n compatibility in Next.js 😱

Adriano Raiano on December 07, 2021

You know Next.js, right? - If not, stop reading this article and make something else. Next.js is awesome! It gives you the best developer experien...
Collapse
 
tangopj profile image
TangoPJ

Thank you so much, it worked for me, but I didn't understand anything :D

Collapse
 
kutsan profile image
Kutsan Kaplan

I implemented this to one of my own projects written in TypeScript, there is is some boilerplate you need to write but it works, even with dynamic routes, sitemap etc. Sure there is some code to adapt to your project but I think this is the way to go right now for next export. Only thing that I don't like this to have to create a second file per page, but I understand why this necessary and can't be avoided. Thanks again.

I think you should also add appWithTranslation github.com/adrai/next-language-det... section to initialize i18next, otherwise you're getting i18next is not initialized error.

Collapse
 
adrai profile image
Adriano Raiano

That should already be covered with the default: github.com/isaachinman/next-i18nex...

Collapse
 
tangopj profile image
TangoPJ

@adrai Can you help me, why I'm getting a redirection in loop in production only?
I'm opening the site example.com. It redirects me with automatically detected language for example example.com/en. All ok, but if I reload the page, it redirects in loop and looks like something like this: example.com/en/en/en/en/en/en/en/en/...
But this problem in the production only. Locally it works without problems..
Help please..

Thread Thread
 
adrai profile image
Adriano Raiano

if it is not reproducible locally, you may need to check on your prod system
if it is reproducible, please provide a minimal reproducible example repository

Thread Thread
 
emiliano_calderon_9e899b8 profile image
Emiliano Calderon

I have the same error! which is the solution?

Collapse
 
sidak profile image
gursidak_singh

I need to create dynamic routes so created something like this in pages folder
pages/[locale]/gallery/[filter].tsx
In filter.tsx, I am catching the value of query.filter using useRouter hook. So after catching value of query i need to render some data from /en/common.json so i am trying to fetch it like
const data = t(gallery.metadata.${query.filter})
As in first render query.filter returns undefined so it gives error if i reload or directly loads on /gallery/rooms or /gallery/services page , but it works fine if i simply navigate to these routes using some next Link component.

II tried many solutions , but got some issue in all the solutions i tried to avoid it from accessing t('gallery.metadata.undefined')..

kindly help me in this, thanks in advance

Collapse
 
sidak profile image
gursidak_singh

however it works fine if i am fetching data from some external url/source based upon value from query.filter

Collapse
 
adrai profile image
Adriano Raiano

you may ask on stackoverflow with a more complete example

Collapse
 
raibtoffoletto profile image
Raí B. Toffoletto

Thanks a lot for this. You are a life saviour 🎉

Collapse
 
adrai profile image
Adriano Raiano

You’re welcome…
If you want you can share this to the word 😉

Collapse
 
karrtopelka profile image
Max Plotitsyn

I am writing project with typescript, and cannot use type "any".
I want to ask you about getStatic file.
Can you please tell the type of ctx?
And also ns by default is array of strings, but in makeStaticProps you assign to ns an empty object, should I change empty object to an empty array?

Collapse
 
adrai profile image
Adriano Raiano

this is JavaScript, not TypeScript…
ns can be a string or an array of strings
ctx is coming from Next.js

Collapse
 
karrtopelka profile image
Max Plotitsyn

Made everything like in a tutorial, but now, when I try to run in development mode (also for build) I got this error:
error - ./node_modules/next-i18next/dist/commonjs/serverSideTranslations.js:78:0
Module not found: Can't resolve 'fs'
Can you help please how to resolve this issue

Collapse
 
adrai profile image
Adriano Raiano

This seems to work: github.com/adrai/next-language-det...

provide a reproducible example and open a github issue

Collapse
 
filiplusnia profile image
Filip Luśnia • Edited

I'm not sure about what causes the "fs" issue but it seems related to using getStaticProps in client components - move it to server component and pass the translation to the component which causes the error

Collapse
 
davi_dev profile image
Davi

hey can you share some demo code of this setup, i also want to setup this in Ts
Thanks in advance

Collapse
 
karrtopelka profile image
Max Plotitsyn

I switched to next-translate, because it's easier, you just keep small json config file and locales folder and that's it, nothing more

Thread Thread
 
filiplusnia profile image
Filip Luśnia

is next-translate compatible with next export?

Collapse
 
romanakhatun profile image
Romana Khatun

Wow, this is great! Thanks a million!

Collapse
 
martinkr profile image
Martin Krause • Edited

Interesting approach. I wrote about my experience with next export an i18n a few months back: next.js: i18n with static sites and created this easy to integrate npm-package: next-export-i18n.

As far as is see we took a different route for something similar. Great article!

Cheers!

Collapse
 
adrai profile image
Adriano Raiano

interesting… did not know it…

I wanted to keep the i18next compatibility…

Collapse
 
nardoshood profile image
Nardos Tilahun

I followed the same step but for some reason.
const { t } = useTranslation("common");
is not loading the contents.
eg: {t("home1")} {t("home2")} would display home1 home2 in the page.

For context. I am using
"next": "12.3.0",
"next-i18next": "13.0.0",
"next-language-detector": "1.0.2",
How do I solve that?

Collapse
 
nardoshood profile image
Nardos Tilahun

Nvm I was missing.
export default appWithTranslation(MyApp)

Collapse
 
coeneivan profile image
Ivan Coene

THANK YOU!!!!!
I've been searching for a solution for quite a while now!
Great idea!

Just added this snippet to my _app.js to make it even nicer instead of step 8

useEffect(() => {
     if (router.query.locale !== undefined ) {
       const qLng = typeof router.query.locale == 'string' ? router.query.locale : router.query.locale[0] 
       if (!i18nConfig.i18n.locales.includes(qLng)) {
          router.replace(`${language}/${qLng}`)
        }
      }
  }, [router.query.locale]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
yrobot profile image
Yrobot

Hi guys. I have just published a package for i18n next.js project SSG export, which called i18next-ssg.

With i18next-ssg, you could handle the i18n logic for next.js SSG easily.

Here is an example, if you have the pages defined like this:

Image description

After you run yarn build, the output files under the build folder will look like this:

Image description

/ will redirect to /[locale],/arya will redirect to /[locale]/arya, the locale value will be detected automaticaly.

This is the demo for this package: codesandbox.io/s/i18next-ssg-ozrpwx
This is the package github repo: github.com/Yrobot/i18next-ssg

Collapse
 
andryore profile image
Andry Orellana

Hi, thanks for taking the time to make this tutorial, I couldn't find a solution for this problem.
I am trying to implement it but I have problems with TS, I have not been able to create the Link component.
Any tutorial on this but using TS?

Collapse
 
sidak profile image
gursidak_singh

How can i define dynamic routes, it gives error Id is not defined and also getStaticPaths is already occupied, please help on this urgently

Collapse
 
adrai profile image
Adriano Raiano
Collapse
 
sjiirfan profile image
Mohammed Irfan

Hi, facing problem for [locale]/products/[productid].js
Here the productid will always be many, can be thousands, for that situation how can I handle it. Please share your knowledge on this, its urgent.

As with static path I had to pass a , but this situation I don't know which productid will be there.
Something like this:
params: {
locale: lng,
productid: product._id
}

Thread Thread
 
adrai profile image
Adriano Raiano

If you don’t know all information on build time, then SSG is probably not suitable for you.

Thread Thread
 
sjiirfan profile image
Mohammed Irfan

Is there no way on it.

Collapse
 
tangopj profile image
TangoPJ • Edited

Hello, what did you mean in the 8th point of your code? If I understand you correctly - I should create an index.tsx file in the pages directory with the Redirect code? (in my case I have a single index.tsx page)
My structure looks like something like this:

Image description

Collapse
 
adrai profile image
Adriano Raiano
Collapse
 
zecka profile image
zecka

But with this approach is the language in url path is mandatory. You can not have empty path for default language, right ?

For example to have that:

  • example.com/my-page (default-language)
  • example.com/fr/my-page

or maybe the only way is to duplicate all your page like bellow:

  • pages/[locale]/[slug].js
  • pages/[slug].js

Any way to avoid duplicating pages ?

Collapse
 
ted_dev profile image
ted_dev

Awesome! May I ask how can I use getStaticProps() to fetch data? Because it has been occupied in each pages. Thanks so much!

Collapse
 
adrai profile image
Adriano Raiano

yes, just define your normal getStaticProps function and call github.com/adrai/next-language-det...

Collapse
 
sarkis1997 profile image
sarkis1997 • Edited

Do you have an example of this? Currently I have underneath code, but I can't figure out how to use getStaticProps() to fetch data in addition to the makeStatisProps function.

import { getStaticPaths, makeStaticProps } from "../../lib/get-static";
export { getStaticPaths };

export async function getStaticProps() {
const { data: headerFooterData } = await axios.get(HEADER_FOOTER_ENDPOINT);

makeStaticProps();
return {
props: {
headerFooter: headerFooterData?.data ?? {},
},
revalidate: 1,
};
}

Thread Thread
 
adrai profile image
Adriano Raiano
Thread Thread
 
sarkis1997 profile image
sarkis1997

Thank you! Still I get the following errors after following this steps:

Error occurred prerendering page "/it/cart". Read more: nextjs.org/docs/messages/prerender...
AxiosError: Request failed with status code 508
at settle (file:///Users/±±±±/Desktop/granddeals-react-frontend/node_modules/axios/lib/core/settle.js:19:12)
at Unzip.handleStreamEnd (file:///Users/±±±±/Desktop/granddeals-react-frontend/node_modules/axios/lib/adapters/http.js:505:11)
at Unzip.emit (events.js:412:35)
at endReadableNT (internal/streams/readable.js:1317:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21)

info - Generating static pages (29/29)

Build error occurred
Error: Export encountered errors on following paths:
/[locale]/cart: /de/cart
/[locale]/cart: /es/cart
/[locale]/cart: /fr/cart
/[locale]/cart: /it/cart
/[locale]/cart: /pl/cart
/[locale]/cart: /ru/cart
/[locale]/checkout: /en/checkout
/[locale]/checkout: /es/checkout
/[locale]/checkout: /fr/checkout
/[locale]/checkout: /nl/checkout
/[locale]/checkout: /pl/checkout
/[locale]/checkout: /ru/checkout
/[locale]: /de
/[locale]: /fr

Thread Thread
 
adrai profile image
Adriano Raiano

Seems your server returns a 508 error 🤷‍♂️

Collapse
 
ted_dev profile image
ted_dev

Thanks so much!!

Collapse
 
numito profile image
Numa • Edited

Hello,
The only issue with this solution is that next router doesn't take into account the locale, so I had to override it as follow to add the locale when calling "push":

import {useRouter as _useRouter} from "next/router";
import i18nConfig from '../../next-i18next.config';
import {UrlObject} from "url";
declare type Url = UrlObject | string;
interface TransitionOptions {
  shallow?: boolean;
  locale?: string | false;
  scroll?: boolean;
}

export const useRouter = () => {
  const router = _useRouter();
  const locale = router.query.locale as string || '';
  if (!router.locale && locale)
    router.locale = locale;
  if (!router.defaultLocale && i18nConfig.i18n.defaultLocale)
    router.defaultLocale = i18nConfig.i18n.defaultLocale;
  if (!router.locales && i18nConfig.i18n.locales)
    router.locales = i18nConfig.i18n.locales;
  const oldPush = router.push;
  //we override the push method to add the current locale
  router.push = (url: Url, as?: Url, options?: TransitionOptions): Promise<boolean> => {
      if (typeof url == "string" && !url.startsWith(`/${locale}`))
        url = `/${locale}${url}`;
      else if (typeof url === 'object' && url.pathname && !url.pathname.startsWith(`/${locale}`))
        url.pathname = `/${locale}${url.pathname}`;
    return oldPush.apply(this, [url, as, options]);
  }
  return router;
}
Enter fullscreen mode Exit fullscreen mode

Anyone came with a better solution ? It seems a bit of a hack!

Collapse
 
magom001 profile image
Arkady Magomedov

I use strapi with nextjs. I simply run LOCALE=xxx yarn build && yarn export -o out/xxx for each locale that I need to support.

For i18n enabled routing an nginx proxy can be used together with custom cookies and/or browser language settings

Collapse
 
kutsan profile image
Kutsan Kaplan

Thanks a lot! But is there a way to keep current selected language something like in localStorage so that users don't have to select locale every time they visit my website?

Collapse
 
adrai profile image
Adriano Raiano

yes, by calling languageDetector.cache(locale) like in point 12

Collapse
 
kutsan profile image
Kutsan Kaplan

I'm sorry, I must have missed that part. Thanks again!

Collapse
 
davi_dev profile image
Davi • Edited

Installed /examples/basic

TypeError: Cannot set properties of undefined (setting 'reactRoot')
Getting this error on running npm run dev or build after cloning and installing npm packages
please help

Collapse
 
bluepuper profile image
Bluepuper

Can't get what duplicate file names should be on step 8? Can anyone explain me?

Collapse
 
adrai profile image
Adriano Raiano

check the code and you’ll see: github.com/i18next/next-language-d...

Collapse
 
bluepuper profile image
Bluepuper

Oh thanks, but what if i have locale/dashbord/analytics.js
What name of the file should be in pages dir? dashboard.analytics.js ?

Collapse
 
maurciobuffa profile image
maurciobuffa

Thanks for this article!!!

Do you happen to know if it's possible to redirect with query params?

Collapse
 
adrai profile image
Adriano Raiano

what do you exactly mean? you should be able to access the query params in the router: router.query

Collapse
 
marktangcd profile image
MarkTangCd

Thanks for your article. I used it in Next.js@12.x.x and it can work.

Collapse
 
eliozashvili profile image
Giorgi Eliozashvili

It was helpful thank you. But then I realised that all strings from API comes in 2 languages. So I have just created language context.