i18next / i18next-http-backend

i18next-http-backend is a backend layer for i18next using in Node.js, in the browser and for Deno.
MIT License
453 stars 70 forks source link

Use with nextjs not watching changes in json files on development #92

Closed bryanltobing closed 2 years ago

bryanltobing commented 2 years ago

🐛 Bug Report

I want to watch changes in the translation JSON files like reloadOnPrerender in next-18next does https://github.com/i18next/next-i18next#reloading-resources-in-development

But every time I make changes to JSON files it doesn't read the changes automatically. In order for the changes to be applied, I need to:

To Reproduce

I am following example from i18next-http-backend with nextjs https://github.com/i18next/i18next-http-backend/tree/master/example/next

this is my configuration

const path = require("path");
const HttpBackend = require("i18next-http-backend/cjs");
const ChainedBackend = require("i18next-chained-backend").default;
const LocalStorageBackend = require("i18next-localstorage-backend").default;

module.exports = {
  backend: {
    backendOptions: [{ expirationTime: 60 * 60 * 1000 }, {}], // 1 hour
    backends:
      typeof window !== "undefined" ? [LocalStorageBackend, HttpBackend] : [],
  },
  i18n: {
    defaultLocale: "en",
    locales: ["en", "id"],
    reloadOnPrerender: process.env.NODE_ENV === "development",
    localePath: path.resolve("./public/locales"),
  },
  serializeConfig: false,
  use: typeof window !== "undefined" ? [ChainedBackend] : [],
  react: {
    useSuspense: true,
  },
};

Expected behavior

Every time I make changes to JSON files and refresh the page the changes will apply, (check at runtime) similar to reloadOnPrerender in next-i18next when using serverSideTranslations

Your Environment

I also created a discussion in next-i18next https://github.com/i18next/next-i18next/discussions/1914

adrai commented 2 years ago

A couple of inputs:


Regarding localstorage during dev... you could change your config with something like:

const HttpBackend = require('i18next-http-backend/cjs')
const ChainedBackend= require('i18next-chained-backend').default
const LocalStorageBackend = require('i18next-localstorage-backend').default

const isBrowser = typeof window !== 'undefined'
const isDev = process.env.NODE_ENV === 'development'

module.exports = {
  debug: isDev,
  backend: isDev ? {} : {
    backendOptions: [{ expirationTime: 60 * 60 * 1000 }, {}], // 1 hour
    backends: isBrowser ? [LocalStorageBackend, HttpBackend]: [],
  },
  // react: { // used only for the lazy reload
  //   bindI18n: 'languageChanged loaded',
  //   useSuspense: false
  // },
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'de'],
  },
  serializeConfig: false,
  use: isBrowser ? [isDev ? HttpBackend : ChainedBackend] : [],
}

Regarding hot reload, you may check out i18next-hmr, I never used it in combination with next.js, maybe @felixmosh can help?


Regarding the hydration warning:

If client render and server renders outputs different html this may occur: https://github.com/i18next/i18next-http-backend/issues/91

There's an alternative usage, you can try: https://locize.com/blog/next-i18next/#alternative-usage https://github.com/i18next/i18next-http-backend/tree/master/example/next#alternative-usage

bryanltobing commented 2 years ago

Thank you for the response @adrai

adrai commented 2 years ago

So basically in the development, we are not saving the translation to the localStorage to prevent such errors?

yes

I haven't checked i18next-hmr yet. but is this really something that is not supported by default?

no

I have checked the alternative usage, but it doesn't apply to my case at all. because it still requires the use of getStaticProps. The reason I use the client-side translation for all my next.js pages in the first place is to prevent the use of serverSideTranslation

then you have to live with that warning, sorry (that's the nature of it)

bryanltobing commented 2 years ago

Not sure if this is the right place to ask or related to this. But I'm curious how did you guys handle dynamic routes using next-i18next then, if in the case that we can't prefetch all our dynamic paths in getStaticPaths. I think this is common cases.

for example, if we have a page to list all users /users/[id].tsx, if we want to use serverSideTranslations we need to use getStaticProps, and because it is dynamic page we need to use getStaticPaths as well to list our ids, and locales.

if we fetch all user's ids from the backend I think it would be a very expensive request to make at build time, for example, if we have millions ids of users.

And also become a hassle to add fetch for every features users, transactions, orders, carts, etc

This has been asked many times. but there's no proper explanation for this I think for such common cases. Instead of answering with a solution, people just provide a hack

This is why I switch to full client-side translation using next-i18next and i18next-http-backend

adrai commented 2 years ago

Sorry, but I've not such a use case like you describe. Mostly static stuff... Also I'm relatively new to next.js https://github.com/i18next/next-i18next/issues/1897 So, if you know of a way for an alternative approach, feel free to contribute to next-i18next with a PR.

felixmosh commented 2 years ago

Regarding hot reload, you may check out i18next-hmr, example folder which contains an example setup for popular frameworks.

It works perfectly with next.js on a daily basis :]

bryanltobing commented 2 years ago

Actually what I need here is not a hot reload. I just want the changes made to be applied at runtime (browser refresh) without the need to restart the server reloadOnPrerender does. not when saving files which I believe is what hmr does.

The browser is actually able to read the changes and apply them in page refresh, but I got this error when refreshing the page, and there's flickering between the old translation to the new translation

old translation

// common.json
{
  "button": {
     "register": "Register"
  }
}

new translation

// common.json
{
  "button": {
     "register": "Register Here"
  }
}

https://user-images.githubusercontent.com/46083126/179746541-687d59e5-2d6b-4c3b-adb5-d385ad4c3e95.mp4

next-dev.js?3515:24 Warning: Text content did not match. Server: "Register" Client: "Register Here"
    at button
    at eval 

when I restart the dev server it works fine.

I presume this is again because of the hydration things since I only use client-side translation.

adrai commented 2 years ago

I suspect you need to move the reloadOnPrerender option out of the i18n option in your config

i18n: {
    defaultLocale: "en",
    locales: ["en", "id"],
-    reloadOnPrerender: process.env.NODE_ENV === "development",
    localePath: path.resolve("./public/locales"),
  },
+reloadOnPrerender: process.env.NODE_ENV === "development",
felixmosh commented 2 years ago

This is exactly the problem that i18next-hmr solves, when the translations are change, your browser (& server) will reflect the new translation (without reloading dev server or the browser state).

https://user-images.githubusercontent.com/9304194/71188474-b1f97100-2289-11ea-9363-257f8a2124b1.gif

bryanltobing commented 2 years ago

I suspect you need to move the reloadOnPrerender option out of the i18n option in your config

i18n: {
    defaultLocale: "en",
    locales: ["en", "id"],
-    reloadOnPrerender: process.env.NODE_ENV === "development",
    localePath: path.resolve("./public/locales"),
  },
+reloadOnPrerender: process.env.NODE_ENV === "development",

I have always used it inside i18n obj and it still works in my static site, just tried to move it out, and still works. both work in my static site.

But in my case here for client-side translation, neither work

My current updated i18n configuration next-i18next.config.js

const path = require("path");
const HttpBackend = require("i18next-http-backend/cjs");
const ChainedBackend = require("i18next-chained-backend").default;
const LocalStorageBackend = require("i18next-localstorage-backend").default;

const isDev = process.env.NODE_ENV === "development";
const isBrowser = typeof window !== "undefined";

module.exports = {
  backend: isDev
    ? {}
    : {
        backendOptions: [{ expirationTime: 60 * 60 * 1000 }, {}], // 1 hour
        backends: isBrowser ? [LocalStorageBackend, HttpBackend] : [],
      },
  i18n: {
    defaultLocale: "en",
    locales: ["en", "id"],
    localePath: path.resolve("./public/locales"),
  },
  serializeConfig: false,
  use: isBrowser ? [isDev ? HttpBackend : ChainedBackend] : [],
  react: {
    useSuspense: true,
  },
  reloadOnPrerender: process.env.NODE_ENV === "development",
};
bryanltobing commented 2 years ago

This is exactly the problem that i18next-hmr solves, when the translations are change, your browser (& server) will reflect the new translation (without reloading dev server or the browser state).

Question. Why is the example here https://github.com/felixmosh/i18next-hmr/blob/master/examples/next-with-next-i18next/pages/second-page.js using getInitialProps, is it just to prove that It works in client and server? or it's required? and not using useTranslation hooks and passing t in page props instead?

felixmosh commented 2 years ago

Why is the example here

getInitialProps is not mandatory, for client side HMR you will need i18next-http-backend (this lib), it works with Webpack's HMR mechanisem.

bryanltobing commented 2 years ago

getInitialProps is not mandatory, for client side HMR you will need i18next-http-backend (this lib), it works with Webpack's HMR mechanism.

I see that the example uses the very old version of next-i18next. is there any example using the current version ? i try to upgrade to the latest version and there are some breaking changes like next-i18next API that is not exported anymore

felixmosh commented 2 years ago

Check the new example of the latest next-i18next

felixmosh commented 2 years ago

If you have any issue, I will be more than happy to help you, just open an issue on the i18next-hmr repo :]

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.