diff --git a/README.md b/README.md
index d779376..cd64b2b 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,13 @@ $ npm install --save-dev i18next-hmr
## Usage
-Add the plugin to your webpack config (or nextjs).
+Add the plugin to your webpack config (or next.config.js).
```js
// webpack.config.js
-const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
module.exports = {
...
@@ -44,12 +44,22 @@ module.exports = {
```js
// i18next.config.js
+const Backend = require('i18next-http-backend');
const i18next = require('i18next');
-i18next.init(options, callback);
+const { HMRPlugin } = require('i18next-hmr/plugin');
+
+const instance = i18next.use(Backend); // http-backend is required for client side reloading
+
if (process.env.NODE_ENV !== 'production') {
- const { applyClientHMR } = require('i18next-hmr/client');
- applyClientHMR(i18next);
+ instance.use(new HMRPlugin({
+ client: typeof window !== 'undefined', // enabled client side HMR
+ server: typeof window === 'undefined' // enabled server side HMR
+ }));
}
+
+instance.init(options, callback);
+
+module.exports = instance;
```
@@ -58,13 +68,6 @@ if (process.env.NODE_ENV !== 'production') {
// server.js
const express = require('express');
-const i18n = require('./i18n');
-
-if (process.env.NODE_ENV !== 'production') {
- const { applyServerHMR } = require('i18next-hmr/server');
- applyServerHMR(i18n);
-}
-
const port = process.env.PORT || 3000;
(async () => {
@@ -85,8 +88,8 @@ The lib will trigger [`i18n.reloadResources([lang], [ns])`](https://www.i18next.
⚠️ If your server side is bundled using Webpack, the lib will use the native HMR (if enabled), for it to work properly the lib must be **bundled**, therefore, you should specify the lib as not [external](https://webpack.js.org/configuration/externals/).
There are 2 ways to do that:
-1. if you are using [webpack-node-externals](https://github.com/liady/webpack-node-externals) specify `i18next-hmr` in the [`whitelist`](https://github.com/liady/webpack-node-externals#optionswhitelist-).
-2. use a relative path to `node_modules`, something like:
+1. If you are using [webpack-node-externals](https://github.com/liady/webpack-node-externals) specify `i18next-hmr` in the [`whitelist`](https://github.com/liady/webpack-node-externals#optionswhitelist-).
+2. (deprecated method) use a relative path to `node_modules`, something like:
```js
// server.entry.js
if (process.env.NODE_ENV !== 'production') {
diff --git a/__tests__/loader.spec.js b/__tests__/loader.spec.js
index f9b75bb..8ac8ae3 100644
--- a/__tests__/loader.spec.js
+++ b/__tests__/loader.spec.js
@@ -1,4 +1,4 @@
-const loader = require('../lib/loader');
+const loader = require('../lib/webpack/loader');
describe('loader', () => {
let context;
diff --git a/__tests__/server-hmr.spec.js b/__tests__/server-hmr.spec.js
index 53ea356..174d5ab 100644
--- a/__tests__/server-hmr.spec.js
+++ b/__tests__/server-hmr.spec.js
@@ -4,7 +4,7 @@ jest.mock('../lib/trigger.js', () => {
return changedData;
});
const applyServerHMR = require('../lib/server-hmr');
-const plugin = require('../lib/plugin');
+const plugin = require('../lib/webpack/plugin');
const applyClientHMR = require('../lib/client-hmr');
function whenNativeHMRTriggeredWith(changedFiles) {
diff --git a/examples/next-with-next-i18next-v11/pages/_app.js b/examples/next-with-next-i18next-v11/pages/_app.js
deleted file mode 100644
index 110172f..0000000
--- a/examples/next-with-next-i18next-v11/pages/_app.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { appWithTranslation, i18n } from 'next-i18next';
-import nextI18NextConfig from '../next-i18next.config';
-
-if (process.env.NODE_ENV !== 'production') {
- if (typeof window !== 'undefined') {
- const { applyClientHMR } = require('i18next-hmr/client');
- applyClientHMR(() => i18n);
- } else {
- const { applyServerHMR } = require('i18next-hmr/server');
- applyServerHMR(() => i18n);
- }
-}
-
-const MyApp = ({ Component, pageProps }) => ;
-
-// https://github.com/i18next/next-i18next#unserialisable-configs
-export default appWithTranslation(MyApp, nextI18NextConfig);
diff --git a/examples/next-with-next-i18next-v11/pages/index.js b/examples/next-with-next-i18next-v11/pages/index.js
deleted file mode 100644
index 4e68b9f..0000000
--- a/examples/next-with-next-i18next-v11/pages/index.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import Link from 'next/link'
-import { useRouter } from 'next/router'
-
-import { useTranslation, Trans } from 'next-i18next'
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
-
-import { Header } from '../components/Header'
-import { Footer } from '../components/Footer'
-
-const Homepage = () => {
-
- const router = useRouter()
- const { t } = useTranslation('common')
-
- return (
- <>
-
-
-
-
-
{t('blog.optimized.question')}
-
-
- Then you may have a look at this blog post.
-
-
-
-
-
-
-
-
{t('blog.ssg.question')}
-
-
- Then you may have a look at this blog post.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )
-}
-
-export const getStaticProps = async ({ locale }) => ({
- props: {
- ...await serverSideTranslations(locale, ['common', 'footer']),
- },
-})
-
-export default Homepage
diff --git a/examples/next-with-next-i18next-v11/pages/second-page.js b/examples/next-with-next-i18next-v11/pages/second-page.js
deleted file mode 100644
index 8a7a251..0000000
--- a/examples/next-with-next-i18next-v11/pages/second-page.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import Link from 'next/link'
-
-import { useTranslation } from 'next-i18next'
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
-
-import { Header } from '../components/Header'
-import { Footer } from '../components/Footer'
-
-const SecondPage = () => {
-
- const { t } = useTranslation('second-page')
-
- return (
- <>
-
-
-
-
-
-
-
- >
- )
-}
-
-export const getStaticProps = async ({ locale }) => ({
- props: {
- ...await serverSideTranslations(locale, ['second-page', 'footer']),
- },
-})
-
-export default SecondPage
diff --git a/examples/next-with-next-i18next-v11/components/Footer.js b/examples/next-with-next-i18next-v13/components/Footer.js
similarity index 100%
rename from examples/next-with-next-i18next-v11/components/Footer.js
rename to examples/next-with-next-i18next-v13/components/Footer.js
diff --git a/examples/next-with-next-i18next-v11/components/Header.js b/examples/next-with-next-i18next-v13/components/Header.js
similarity index 100%
rename from examples/next-with-next-i18next-v11/components/Header.js
rename to examples/next-with-next-i18next-v13/components/Header.js
diff --git a/examples/next-with-next-i18next-v11/next-i18next.config.js b/examples/next-with-next-i18next-v13/next-i18next.config.js
similarity index 56%
rename from examples/next-with-next-i18next-v11/next-i18next.config.js
rename to examples/next-with-next-i18next-v13/next-i18next.config.js
index 81d8626..69f9f29 100644
--- a/examples/next-with-next-i18next-v11/next-i18next.config.js
+++ b/examples/next-with-next-i18next-v13/next-i18next.config.js
@@ -1,4 +1,5 @@
const HttpBackend = require('i18next-http-backend/cjs');
+const { HMRPlugin } = require('i18next-hmr/plugin');
module.exports = {
// https://www.i18next.com/overview/configuration-options#logging
@@ -13,5 +14,11 @@ module.exports = {
},
}
: {}),
- use: typeof window !== 'undefined' ? [HttpBackend] : [],
+ serializeConfig: false,
+ use:
+ process.env.NODE_ENV !== 'production'
+ ? typeof window !== 'undefined'
+ ? [HttpBackend, new HMRPlugin({ client: true })]
+ : [new HMRPlugin({ server: true })]
+ : [],
};
diff --git a/examples/next-with-next-i18next-v11/next.config.js b/examples/next-with-next-i18next-v13/next.config.js
similarity index 72%
rename from examples/next-with-next-i18next-v11/next.config.js
rename to examples/next-with-next-i18next-v13/next.config.js
index 0a10ea5..a89d9fb 100644
--- a/examples/next-with-next-i18next-v11/next.config.js
+++ b/examples/next-with-next-i18next-v13/next.config.js
@@ -4,8 +4,8 @@ const path = require('path');
module.exports = {
i18n,
webpack(config, { isServer }) {
- if (config.mode === 'development') {
- const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+ if (!isServer && config.mode === 'development') {
+ const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
config.plugins.push(
new I18NextHMRPlugin({
localesDir: path.resolve(__dirname, 'public/locales'),
diff --git a/examples/next-with-next-i18next-v11/package.json b/examples/next-with-next-i18next-v13/package.json
similarity index 71%
rename from examples/next-with-next-i18next-v11/package.json
rename to examples/next-with-next-i18next-v13/package.json
index c755b7b..28b8825 100644
--- a/examples/next-with-next-i18next-v11/package.json
+++ b/examples/next-with-next-i18next-v13/package.json
@@ -9,14 +9,16 @@
"start": "next start -p ${PORT:=3000}"
},
"dependencies": {
+ "i18next": "^22.5.0",
"i18next-http-backend": "^1.4.1",
"next": "12.2.2",
- "next-i18next": "11.2.2",
+ "next-i18next": "13.2.2",
"prop-types": "15.8.1",
"react": "18.2.0",
- "react-dom": "18.2.0"
+ "react-dom": "18.2.0",
+ "react-i18next": "^12.3.1"
},
"devDependencies": {
- "i18next-hmr": "^1.8.0"
+ "i18next-hmr": "^2.0.0"
}
}
diff --git a/examples/next-with-next-i18next-v13/pages/_app.js b/examples/next-with-next-i18next-v13/pages/_app.js
new file mode 100644
index 0000000..847f0a3
--- /dev/null
+++ b/examples/next-with-next-i18next-v13/pages/_app.js
@@ -0,0 +1,7 @@
+import { appWithTranslation } from 'next-i18next';
+import nextI18NextConfig from '../next-i18next.config';
+
+const MyApp = ({ Component, pageProps }) => ;
+
+// https://github.com/i18next/next-i18next#unserialisable-configs
+export default appWithTranslation(MyApp, nextI18NextConfig);
diff --git a/examples/next-with-next-i18next-v11/pages/_document.js b/examples/next-with-next-i18next-v13/pages/_document.js
similarity index 100%
rename from examples/next-with-next-i18next-v11/pages/_document.js
rename to examples/next-with-next-i18next-v13/pages/_document.js
diff --git a/examples/next-with-next-i18next-v13/pages/index.js b/examples/next-with-next-i18next-v13/pages/index.js
new file mode 100644
index 0000000..dc7f744
--- /dev/null
+++ b/examples/next-with-next-i18next-v13/pages/index.js
@@ -0,0 +1,73 @@
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+
+import { useTranslation, Trans } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+
+import { Header } from '../components/Header';
+import { Footer } from '../components/Footer';
+
+const Homepage = () => {
+ const router = useRouter();
+ const { t } = useTranslation('common');
+
+ return (
+ <>
+
+
+
+
+
{t('blog.optimized.question')}
+
+
+ Then you may have a look at{' '}
+ this blog post.
+
+
+
+
+
+
+
+
{t('blog.ssg.question')}
+
+
+ Then you may have a look at{' '}
+ this blog post.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const getStaticProps = async ({ locale }) => ({
+ props: {
+ ...(await serverSideTranslations(locale, ['common', 'footer'])),
+ },
+});
+
+export default Homepage;
diff --git a/examples/next-with-next-i18next-v13/pages/second-page.js b/examples/next-with-next-i18next-v13/pages/second-page.js
new file mode 100644
index 0000000..86f1cba
--- /dev/null
+++ b/examples/next-with-next-i18next-v13/pages/second-page.js
@@ -0,0 +1,31 @@
+import Link from 'next/link';
+
+import { useTranslation } from 'next-i18next';
+import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
+
+import { Header } from '../components/Header';
+import { Footer } from '../components/Footer';
+
+const SecondPage = () => {
+ const { t } = useTranslation('second-page');
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const getStaticProps = async ({ locale }) => ({
+ props: {
+ ...(await serverSideTranslations(locale, ['second-page', 'footer'])),
+ },
+});
+
+export default SecondPage;
diff --git a/examples/next-with-next-i18next-v11/public/app.css b/examples/next-with-next-i18next-v13/public/app.css
similarity index 100%
rename from examples/next-with-next-i18next-v11/public/app.css
rename to examples/next-with-next-i18next-v13/public/app.css
diff --git a/examples/next-with-next-i18next-v11/public/locales/de/common.json b/examples/next-with-next-i18next-v13/public/locales/de/common.json
similarity index 100%
rename from examples/next-with-next-i18next-v11/public/locales/de/common.json
rename to examples/next-with-next-i18next-v13/public/locales/de/common.json
diff --git a/examples/next-with-next-i18next-v11/public/locales/de/footer.json b/examples/next-with-next-i18next-v13/public/locales/de/footer.json
similarity index 100%
rename from examples/next-with-next-i18next-v11/public/locales/de/footer.json
rename to examples/next-with-next-i18next-v13/public/locales/de/footer.json
diff --git a/examples/next-with-next-i18next-v11/public/locales/de/second-page.json b/examples/next-with-next-i18next-v13/public/locales/de/second-page.json
similarity index 100%
rename from examples/next-with-next-i18next-v11/public/locales/de/second-page.json
rename to examples/next-with-next-i18next-v13/public/locales/de/second-page.json
diff --git a/examples/next-with-next-i18next-v11/public/locales/en/common.json b/examples/next-with-next-i18next-v13/public/locales/en/common.json
similarity index 100%
rename from examples/next-with-next-i18next-v11/public/locales/en/common.json
rename to examples/next-with-next-i18next-v13/public/locales/en/common.json
diff --git a/examples/next-with-next-i18next-v11/public/locales/en/footer.json b/examples/next-with-next-i18next-v13/public/locales/en/footer.json
similarity index 97%
rename from examples/next-with-next-i18next-v11/public/locales/en/footer.json
rename to examples/next-with-next-i18next-v13/public/locales/en/footer.json
index acc65b3..4ea151b 100644
--- a/examples/next-with-next-i18next-v11/public/locales/en/footer.json
+++ b/examples/next-with-next-i18next-v13/public/locales/en/footer.json
@@ -1,3 +1,3 @@
{
"description": "This is a non-page component that requires its own namespace"
-}
\ No newline at end of file
+}
diff --git a/examples/next-with-next-i18next-v11/public/locales/en/second-page.json b/examples/next-with-next-i18next-v13/public/locales/en/second-page.json
similarity index 100%
rename from examples/next-with-next-i18next-v11/public/locales/en/second-page.json
rename to examples/next-with-next-i18next-v13/public/locales/en/second-page.json
diff --git a/examples/next-with-next-i18next-v11/yarn.lock b/examples/next-with-next-i18next-v13/yarn.lock
similarity index 90%
rename from examples/next-with-next-i18next-v11/yarn.lock
rename to examples/next-with-next-i18next-v13/yarn.lock
index de8f78f..4842e85 100644
--- a/examples/next-with-next-i18next-v11/yarn.lock
+++ b/examples/next-with-next-i18next-v13/yarn.lock
@@ -2,12 +2,12 @@
# yarn lockfile v1
-"@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.6":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
- integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
+"@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200"
+ integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==
dependencies:
- regenerator-runtime "^0.13.4"
+ regenerator-runtime "^0.13.11"
"@next/env@12.2.2":
version "12.2.2"
@@ -149,10 +149,10 @@ html-parse-stringify@^3.0.1:
dependencies:
void-elements "3.1.0"
-i18next-fs-backend@^1.1.4:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-1.1.4.tgz#d0e9b9ed2fa7a0f11002d82b9fa69c3c3d6482da"
- integrity sha512-/MfAGMP0jHonV966uFf9PkWWuDjPYLIcsipnSO3NxpNtAgRUKLTwvm85fEmsF6hGeu0zbZiCQ3W74jwO6K9uXA==
+i18next-fs-backend@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.1.2.tgz#1431d30385eae5bcc8a5bb91e2276f8c74e37fcc"
+ integrity sha512-y9vl8HC8b1ayqZELzKvaKgnphrxgbaGGSNQjPU0JoTVP1M3NI6C69SwiAAXi6xuF1FSySJG52EdQZdMUETlwRA==
i18next-hmr@^1.8.0:
version "1.8.0"
@@ -166,12 +166,12 @@ i18next-http-backend@^1.4.1:
dependencies:
cross-fetch "3.1.5"
-i18next@^21.8.13:
- version "21.8.14"
- resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.8.14.tgz#03a3a669ef4520aadd9d152c80596f600e287c6a"
- integrity sha512-4Yi+DtexvMm/Yw3Q9fllzY12SgLk+Mcmar+rCAccsOPul/2UmnBzoHbTGn/L48IPkFcmrNaH7xTLboBWIbH6pw==
+i18next@^22.5.0:
+ version "22.5.0"
+ resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.5.0.tgz#16d98eba7c748ab183a36505046b5b91f87e989b"
+ integrity sha512-sqWuJFj+wJAKQP2qBQ+b7STzxZNUmnSxrehBCCj9vDOW9RDYPfqCaK1Hbh2frNYQuPziz6O2CGoJPwtzY3vAYA==
dependencies:
- "@babel/runtime" "^7.17.2"
+ "@babel/runtime" "^7.20.6"
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
@@ -190,18 +190,16 @@ nanoid@^3.1.30:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
-next-i18next@11.2.2:
- version "11.2.2"
- resolved "https://registry.yarnpkg.com/next-i18next/-/next-i18next-11.2.2.tgz#5d5536b21aa30c6ebe933fd44bd31d08dc00e30f"
- integrity sha512-ylZBtZnNxI6WBt/BMG1caCwzM/ZS6lp2ZepzoMZxB45eTsnuRV+NyNz4ALqybWbuktwlQ85sT0uoT36hLTv4oQ==
+next-i18next@13.2.2:
+ version "13.2.2"
+ resolved "https://registry.yarnpkg.com/next-i18next/-/next-i18next-13.2.2.tgz#9609546fab1d1d5f9b227e86c5ca23d0cbbbddb4"
+ integrity sha512-t0WU6K+HJoq2nVQ0n6OiiEZja9GyMqtDSU74FmOafgk4ljns+iZ18bsNJiI8rOUXfFfkW96ea1N7D5kbMyT+PA==
dependencies:
- "@babel/runtime" "^7.18.6"
+ "@babel/runtime" "^7.20.13"
"@types/hoist-non-react-statics" "^3.3.1"
core-js "^3"
hoist-non-react-statics "^3.3.2"
- i18next "^21.8.13"
- i18next-fs-backend "^1.1.4"
- react-i18next "^11.18.0"
+ i18next-fs-backend "^2.1.1"
next@12.2.2:
version "12.2.2"
@@ -272,12 +270,12 @@ react-dom@18.2.0:
loose-envify "^1.1.0"
scheduler "^0.23.0"
-react-i18next@^11.18.0:
- version "11.18.1"
- resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.18.1.tgz#ba86ed09069e129b8623a28f2b9a03d7f105ea6f"
- integrity sha512-S8cl4mvIOSA7OQCE5jNy2yhv705Vwi+7PinpqKIYcBmX/trJtHKqrf6CL67WJSA8crr2JU+oxE9jn9DQIrQezg==
+react-i18next@^12.3.1:
+ version "12.3.1"
+ resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.3.1.tgz#30134a41a2a71c61dc69c6383504929aed1c99e7"
+ integrity sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==
dependencies:
- "@babel/runtime" "^7.14.5"
+ "@babel/runtime" "^7.20.6"
html-parse-stringify "^3.0.1"
react-is@^16.13.1, react-is@^16.7.0:
@@ -292,10 +290,10 @@ react@18.2.0:
dependencies:
loose-envify "^1.1.0"
-regenerator-runtime@^0.13.4:
- version "0.13.9"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
- integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+regenerator-runtime@^0.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
scheduler@^0.23.0:
version "0.23.0"
diff --git a/examples/next-with-next-i18next/i18n.js b/examples/next-with-next-i18next/i18n.js
index d8237fc..8b1d9e1 100644
--- a/examples/next-with-next-i18next/i18n.js
+++ b/examples/next-with-next-i18next/i18n.js
@@ -5,7 +5,7 @@
const NextI18Next = require('next-i18next').default;
const { localeSubpaths } = require('next/config').default().publicRuntimeConfig;
-
+const { HMRPlugin } = require('i18next-hmr/plugin');
const localeSubpathVariations = {
none: {},
foreign: {
@@ -20,11 +20,15 @@ const localeSubpathVariations = {
const nextI18Next = new NextI18Next({
otherLanguages: ['de'],
localeSubpaths: localeSubpathVariations[localeSubpaths],
+ use:
+ process.env.NODE_ENV !== 'production'
+ ? [
+ new HMRPlugin({
+ client: typeof window !== 'undefined',
+ server: typeof window === 'undefined',
+ }),
+ ]
+ : undefined,
});
-if (process.env.NODE_ENV !== 'production') {
- const { applyClientHMR } = require('i18next-hmr');
- applyClientHMR(nextI18Next.i18n);
-}
-
module.exports = nextI18Next;
diff --git a/examples/next-with-next-i18next/next.config.js b/examples/next-with-next-i18next/next.config.js
index 2533a26..60dc4c1 100644
--- a/examples/next-with-next-i18next/next.config.js
+++ b/examples/next-with-next-i18next/next.config.js
@@ -2,21 +2,19 @@ const path = require('path');
module.exports = {
publicRuntimeConfig: {
- localeSubpaths: typeof process.env.LOCALE_SUBPATHS === 'string'
- ? process.env.LOCALE_SUBPATHS
- : 'none',
+ localeSubpaths:
+ typeof process.env.LOCALE_SUBPATHS === 'string' ? process.env.LOCALE_SUBPATHS : 'none',
},
webpack(config, options) {
if (!options.isServer && config.mode === 'development') {
- const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+ const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
config.plugins.push(
new I18NextHMRPlugin({
- localesDir: path.resolve(__dirname, 'public/static/locales')
+ localesDir: path.resolve(__dirname, 'public/static/locales'),
})
);
}
return config;
- }
+ },
};
-
diff --git a/examples/next-with-next-i18next/package.json b/examples/next-with-next-i18next/package.json
index 3cc1022..f09b67f 100644
--- a/examples/next-with-next-i18next/package.json
+++ b/examples/next-with-next-i18next/package.json
@@ -12,7 +12,7 @@
"start:prod": "NODE_ENV=production node index.js"
},
"devDependencies": {
- "i18next-hmr": "^1.7.8"
+ "i18next-hmr": "^2.0.0"
},
"dependencies": {
"express": "^4.17.1",
diff --git a/examples/next-with-next-i18next/server.js b/examples/next-with-next-i18next/server.js
index df51201..72cf08d 100644
--- a/examples/next-with-next-i18next/server.js
+++ b/examples/next-with-next-i18next/server.js
@@ -4,11 +4,6 @@ const nextI18NextMiddleware = require('next-i18next/middleware').default;
const nextI18next = require('./i18n');
-if (process.env.NODE_ENV !== 'production') {
- const { applyServerHMR } = require('i18next-hmr/server');
- applyServerHMR(nextI18next.i18n);
-}
-
const port = process.env.PORT || 3000;
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const handle = app.getRequestHandler();
@@ -20,4 +15,4 @@ const handle = app.getRequestHandler();
await server.listen(port);
console.log(`> Ready on http://localhost:${port}`); // eslint-disable-line no-console
-})();
+})().catch((e) => console.error(e));
diff --git a/examples/razzle-ssr/package.json b/examples/razzle-ssr/package.json
index b774959..269f781 100644
--- a/examples/razzle-ssr/package.json
+++ b/examples/razzle-ssr/package.json
@@ -23,7 +23,7 @@
"devDependencies": {
"babel-preset-razzle": "^4.0.4",
"html-webpack-plugin": "^5.3.1",
- "i18next-hmr": "^1.6.3",
+ "i18next-hmr": "^2.0.0",
"mini-css-extract-plugin": "^1.6.0",
"razzle-dev-utils": "^4.0.4",
"webpack": "^5.39.0",
diff --git a/examples/razzle-ssr/razzle.config.js b/examples/razzle-ssr/razzle.config.js
index 6fd7af1..0cd980b 100644
--- a/examples/razzle-ssr/razzle.config.js
+++ b/examples/razzle-ssr/razzle.config.js
@@ -3,7 +3,7 @@ const path = require('path');
module.exports = {
modifyWebpackConfig: ({ webpackConfig, env: { dev } }) => {
if (dev) {
- const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+ const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
webpackConfig.plugins.push(
new I18NextHMRPlugin({
localesDir: path.resolve(__dirname, 'src/locales'),
diff --git a/examples/razzle-ssr/src/client.js b/examples/razzle-ssr/src/client.js
index f8bc922..81b94c0 100644
--- a/examples/razzle-ssr/src/client.js
+++ b/examples/razzle-ssr/src/client.js
@@ -1,5 +1,5 @@
import App from './App';
-import BrowserRouter from 'react-router-dom/BrowserRouter';
+import { BrowserRouter } from 'react-router-dom';
import React, { Suspense } from 'react';
import { hydrate } from 'react-dom';
import { useSSR } from 'react-i18next';
@@ -14,12 +14,9 @@ const BaseApp = () => {
);
-}
+};
-hydrate(
- ,
- document.getElementById('root'),
-);
+hydrate(, document.getElementById('root'));
if (module.hot) {
module.hot.accept();
diff --git a/examples/razzle-ssr/src/i18n.js b/examples/razzle-ssr/src/i18n.js
index 270849d..bf1ca0f 100644
--- a/examples/razzle-ssr/src/i18n.js
+++ b/examples/razzle-ssr/src/i18n.js
@@ -2,6 +2,7 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import XHR from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
+import { HMRPlugin } from 'i18next-hmr/plugin';
const options = {
fallbackLng: 'en',
@@ -27,20 +28,16 @@ const options = {
// for browser use xhr backend to load translations and browser lng detector
if (process && !process.release) {
- i18n
- .use(XHR)
- .use(initReactI18next)
- .use(LanguageDetector);
+ i18n.use(XHR).use(initReactI18next).use(LanguageDetector);
}
// initialize if not already initialized
if (!i18n.isInitialized) {
- i18n.init(options);
-
if (process.env.NODE_ENV !== 'production') {
- const { applyClientHMR } = require('i18next-hmr/client');
- applyClientHMR(i18n);
+ i18n.use(new HMRPlugin({ client: true }));
}
+
+ i18n.init(options);
}
export default i18n;
diff --git a/examples/razzle-ssr/src/server.js b/examples/razzle-ssr/src/server.js
index 05de772..ad669ac 100644
--- a/examples/razzle-ssr/src/server.js
+++ b/examples/razzle-ssr/src/server.js
@@ -11,6 +11,7 @@ import { I18nextProvider } from 'react-i18next';
import Backend from 'i18next-node-fs-backend';
import App from './App';
import i18n from './i18n';
+import { HMRPlugin } from 'i18next-hmr/plugin';
// Make sure any symlinks in the project folder are resolved:
// https://github.com/facebookincubator/create-react-app/issues/637
@@ -28,49 +29,52 @@ if (process.env.NODE_ENV !== 'production') {
applyServerHMR(i18n);
}
-i18n
- .use(Backend)
- .use(i18nextMiddleware.LanguageDetector)
- .init(
- {
- debug: false,
- preload: ['en', 'de'],
- ns: ['translations'],
- defaultNS: 'translations',
- backend: {
- loadPath: `${appSrc}/locales/{{lng}}/{{ns}}.json`,
- addPath: `${appSrc}/locales/{{lng}}/{{ns}}.missing.json`,
- },
+const instance = i18n.use(Backend).use(i18nextMiddleware.LanguageDetector);
+
+if (process.env.NODE_ENV !== 'production') {
+ instance.use(new HMRPlugin({ server: true }));
+}
+
+instance.init(
+ {
+ debug: false,
+ preload: ['en', 'de'],
+ ns: ['translations'],
+ defaultNS: 'translations',
+ backend: {
+ loadPath: `${appSrc}/locales/{{lng}}/{{ns}}.json`,
+ addPath: `${appSrc}/locales/{{lng}}/{{ns}}.missing.json`,
},
- () => {
- server
- .disable('x-powered-by')
- .use(i18nextMiddleware.handle(i18n))
- .use('/locales', express.static(`${appSrc}/locales`))
- .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
- .get('/*', (req, res) => {
- const context = {};
- const markup = renderToString(
-
-
-
-
-
- );
- // This line must be placed after renderToString method
- // otherwise context won't be populated by App
- const { url } = context;
- if (url) {
- res.redirect(url);
- } else {
- const initialI18nStore = {};
- req.i18n.languages.forEach((l) => {
- initialI18nStore[l] = req.i18n.services.resourceStore.data[l];
- });
- const initialLanguage = req.i18n.language;
+ },
+ () => {
+ server
+ .disable('x-powered-by')
+ .use(i18nextMiddleware.handle(i18n))
+ .use('/locales', express.static(`${appSrc}/locales`))
+ .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
+ .get('/*', (req, res) => {
+ const context = {};
+ const markup = renderToString(
+
+
+
+
+
+ );
+ // This line must be placed after renderToString method
+ // otherwise context won't be populated by App
+ const { url } = context;
+ if (url) {
+ res.redirect(url);
+ } else {
+ const initialI18nStore = {};
+ req.i18n.languages.forEach((l) => {
+ initialI18nStore[l] = req.i18n.services.resourceStore.data[l];
+ });
+ const initialLanguage = req.i18n.language;
- res.status(200).send(
- `
+ res.status(200).send(
+ `
@@ -88,10 +92,10 @@ i18n
${markup}
`
- );
- }
- });
- }
- );
+ );
+ }
+ });
+ }
+);
export default server;
diff --git a/examples/react-i18next/config-overrides.js b/examples/react-i18next/config-overrides.js
index 095d02f..0c63fb8 100644
--- a/examples/react-i18next/config-overrides.js
+++ b/examples/react-i18next/config-overrides.js
@@ -1,4 +1,4 @@
-const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
const path = require('path');
module.exports = {
diff --git a/examples/react-i18next/package.json b/examples/react-i18next/package.json
index a6dcafb..aa60210 100644
--- a/examples/react-i18next/package.json
+++ b/examples/react-i18next/package.json
@@ -37,7 +37,7 @@
]
},
"devDependencies": {
- "i18next-hmr": "^1.7.8",
+ "i18next-hmr": "^2.0.0",
"react-app-rewired": "^2.2.1"
}
}
diff --git a/examples/react-i18next/src/i18n.js b/examples/react-i18next/src/i18n.js
index 56a2037..9a43858 100644
--- a/examples/react-i18next/src/i18n.js
+++ b/examples/react-i18next/src/i18n.js
@@ -2,8 +2,9 @@ import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
+import { HMRPlugin } from 'i18next-hmr/plugin';
-i18n
+const instance = i18n
// load translation using http -> see /public/locales
// learn more: https://github.com/i18next/i18next-http-backend
.use(Backend)
@@ -11,20 +12,18 @@ i18n
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
- .use(initReactI18next)
- // init i18next
- // for all options read: https://www.i18next.com/overview/configuration-options
- .init({
- fallbackLng: 'en',
-
- interpolation: {
- escapeValue: false, // not needed for react as it escapes by default
- },
- });
+ .use(initReactI18next);
if (process.env.NODE_ENV !== 'production') {
- const { applyClientHMR } = require('i18next-hmr/client');
- applyClientHMR(i18n);
+ instance.use(new HMRPlugin({ client: true }));
}
+instance.init({
+ fallbackLng: 'en',
+
+ interpolation: {
+ escapeValue: false, // not needed for react as it escapes by default
+ },
+});
+
export default i18n;
diff --git a/examples/vue-i18next/package.json b/examples/vue-i18next/package.json
index 027358e..520ba63 100644
--- a/examples/vue-i18next/package.json
+++ b/examples/vue-i18next/package.json
@@ -20,7 +20,7 @@
"cross-env": "^7.0.2",
"css-loader": "^5.2.6",
"file-loader": "^6.0.0",
- "i18next-hmr": "^1.7.8",
+ "i18next-hmr": "^2.0.0",
"vue-loader": "^15.9.3",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.7.7",
diff --git a/examples/vue-i18next/src/main.js b/examples/vue-i18next/src/main.js
index ce43889..a157912 100644
--- a/examples/vue-i18next/src/main.js
+++ b/examples/vue-i18next/src/main.js
@@ -3,27 +3,25 @@ import App from './App.vue';
import VueI18Next from '@panter/vue-i18next';
import i18next from 'i18next';
import Backend from 'i18next-xhr-backend';
-
+import { HMRPlugin } from 'i18next-hmr/plugin';
Vue.use(VueI18Next);
-i18next
- .use(Backend)
- .init({
- lng: 'en',
- });
+const instance = i18next.use(Backend);
if (process.env.NODE_ENV !== 'production') {
- const { applyClientHMR } = require('i18next-hmr');
- applyClientHMR(i18next);
+ instance.use(new HMRPlugin({ client: true }));
}
-const i18n = new VueI18Next(i18next);
+instance.init({
+ lng: 'en',
+});
+const i18n = new VueI18Next(instance);
Vue.config.productionTip = false;
new Vue({
i18n,
- render: h => h(App),
+ render: (h) => h(App),
}).$mount('#app');
diff --git a/examples/vue-i18next/webpack.config.js b/examples/vue-i18next/webpack.config.js
index 79139a3..13f1281 100644
--- a/examples/vue-i18next/webpack.config.js
+++ b/examples/vue-i18next/webpack.config.js
@@ -1,6 +1,6 @@
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
-const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
module.exports = {
entry: './src/main.js',
diff --git a/lib/plugin.js b/lib/plugin.js
index a7b3e43..e90db1f 100644
--- a/lib/plugin.js
+++ b/lib/plugin.js
@@ -1,96 +1,29 @@
-const path = require('path');
-const fs = require('fs');
-
-const pluginName = 'I18nextHMRPlugin';
-
-const DEFAULT_OPTIONS = {
- localesDir: '',
- localesDirs: [],
-};
-
-class I18nextHMRPlugin {
- constructor(options) {
- this.options = { ...DEFAULT_OPTIONS, ...options };
- this.options.localesDirs = []
- .concat(this.options.localesDirs, this.options.localesDir)
- .filter(Boolean);
- this.lastUpdate = { changedFiles: [] };
- }
-
- apply(compiler) {
- const isWebpack5 = compiler.webpack
- ? +compiler.webpack.version.split('.').reverse().pop() === 5
- : false;
-
- compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {
- const noneExistsDirs = this.options.localesDirs.filter((dir) => !fs.existsSync(dir));
-
- if (noneExistsDirs.length === 0) {
- return callback();
- }
- throw new Error(
- `i18next-hmr: \n'${noneExistsDirs.join(`',\n'`)}'${
- noneExistsDirs.length > 1 ? '\nare' : ''
- } not found`
- );
- });
-
- compiler.hooks.watchRun.tap(pluginName, (compiler) => {
- const watcher = (compiler.watchFileSystem.wfs || compiler.watchFileSystem).watcher;
- const changedTimes = isWebpack5 ? watcher.getTimes() : watcher.mtimes;
-
- const { startTime = 0 } = watcher || {};
-
- const files = Object.keys(changedTimes).filter((file) => {
- const fileExt = path.extname(file);
-
- return (
- this.options.localesDirs.some((dir) => file.startsWith(dir)) &&
- !!fileExt &&
- changedTimes[file] > startTime
- );
+class HMRPlugin {
+ constructor(hmrOptions = {}) {
+ this.type = '3rdParty';
+
+ if (hmrOptions.client && typeof window !== 'undefined') {
+ const applyClientHMR = require('./client-hmr');
+ applyClientHMR(() => this.i18nInstance);
+ } else if (hmrOptions.server && typeof window === 'undefined') {
+ const applyServerHMR = require('./server-hmr');
+ applyServerHMR(() => {
+ return this.i18nInstance;
});
+ }
+ }
- if (!files.length) {
- return;
- }
-
- const changedFiles = files.map((file) => {
- const fileExt = path.extname(file);
- const dir = this.options.localesDirs.find((dir) => file.startsWith(dir));
-
- return path.relative(dir, file).slice(0, -1 * fileExt.length || undefined); // keep all when fileExt.length === 0
- });
-
- this.lastUpdate = { changedFiles };
-
- I18nextHMRPlugin.callbacks.forEach((cb) => cb({ changedFiles }));
- });
+ init(i18nInstance) {
+ this.i18nInstance = i18nInstance;
+ }
- compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => {
- nmf.hooks.createModule.tap(pluginName, (module) => {
- const triggerPath = path.resolve(__dirname, 'trigger.js');
- if (module.resource !== triggerPath) {
- return;
- }
+ toJSON() {
+ return null;
+ }
- module.loaders.push({
- loader: path.resolve(__dirname, 'loader.js'), // Path to loader
- options: {
- localesDirs: this.options.localesDirs,
- getChangedLang: () => ({ ...this.lastUpdate }),
- },
- });
- });
- });
+ toString() {
+ return 'HMRPlugin';
}
}
-I18nextHMRPlugin.callbacks = [];
-
-I18nextHMRPlugin.addListener = function (cb) {
- I18nextHMRPlugin.callbacks.length = 0;
- I18nextHMRPlugin.callbacks.push(cb);
-};
-
-module.exports = I18nextHMRPlugin;
+module.exports.HMRPlugin = HMRPlugin;
diff --git a/lib/server-hmr.js b/lib/server-hmr.js
index dc5700a..25ca600 100644
--- a/lib/server-hmr.js
+++ b/lib/server-hmr.js
@@ -47,7 +47,7 @@ module.exports = function applyServerHMR(i18nOrGetter) {
} else {
logOnce(`Server HMR has started - callback mode`);
- const HMRPlugin = require('./plugin');
+ const HMRPlugin = require('./webpack/plugin');
HMRPlugin.addListener(reloadServerTranslation);
}
};
diff --git a/lib/loader.js b/lib/webpack/loader.js
similarity index 100%
rename from lib/loader.js
rename to lib/webpack/loader.js
diff --git a/lib/webpack/plugin.js b/lib/webpack/plugin.js
new file mode 100644
index 0000000..a7236b6
--- /dev/null
+++ b/lib/webpack/plugin.js
@@ -0,0 +1,97 @@
+const path = require('path');
+const fs = require('fs');
+
+const pluginName = 'I18nextHMRPlugin';
+
+const DEFAULT_OPTIONS = {
+ localesDir: '',
+ localesDirs: [],
+};
+
+class I18nextHMRPlugin {
+ constructor(options) {
+ this.options = { ...DEFAULT_OPTIONS, ...options };
+ this.options.localesDirs = []
+ .concat(this.options.localesDirs, this.options.localesDir)
+ .filter(Boolean);
+ this.lastUpdate = { changedFiles: [] };
+ }
+
+ apply(compiler) {
+ const isWebpack5 = compiler.webpack
+ ? +compiler.webpack.version.split('.').reverse().pop() === 5
+ : false;
+
+ compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {
+ const noneExistsDirs = this.options.localesDirs.filter((dir) => !fs.existsSync(dir));
+
+ if (noneExistsDirs.length === 0) {
+ return callback();
+ }
+ throw new Error(
+ `i18next-hmr: \n'${noneExistsDirs.join(`',\n'`)}'${
+ noneExistsDirs.length > 1 ? '\nare' : ''
+ } not found`
+ );
+ });
+
+ compiler.hooks.watchRun.tap(pluginName, (compiler) => {
+ const watcher = (compiler.watchFileSystem.wfs || compiler.watchFileSystem).watcher;
+ const changedTimes = isWebpack5 ? watcher.getTimes() : watcher.mtimes;
+
+ const { startTime = 0 } = watcher || {};
+
+ const files = Object.keys(changedTimes).filter((file) => {
+ const fileExt = path.extname(file);
+
+ return (
+ this.options.localesDirs.some((dir) => file.startsWith(dir)) &&
+ !!fileExt &&
+ changedTimes[file] > startTime
+ );
+ });
+
+ if (!files.length) {
+ return;
+ }
+
+ const changedFiles = files.map((file) => {
+ const fileExt = path.extname(file);
+ const dir = this.options.localesDirs.find((dir) => file.startsWith(dir));
+
+ return path.relative(dir, file).slice(0, -1 * fileExt.length || undefined); // keep all when fileExt.length === 0
+ });
+
+ this.lastUpdate = { changedFiles };
+
+ I18nextHMRPlugin.callbacks.forEach((cb) => cb({ changedFiles }));
+ });
+
+ compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => {
+ nmf.hooks.createModule.tap(pluginName, (module) => {
+ const triggerPath = path.resolve(__dirname, '../trigger.js');
+
+ if (module.resource !== triggerPath) {
+ return;
+ }
+
+ module.loaders.push({
+ loader: path.resolve(__dirname, 'loader.js'), // Path to loader
+ options: {
+ localesDirs: this.options.localesDirs,
+ getChangedLang: () => ({ ...this.lastUpdate }),
+ },
+ });
+ });
+ });
+ }
+}
+
+I18nextHMRPlugin.callbacks = [];
+
+I18nextHMRPlugin.addListener = function (cb) {
+ I18nextHMRPlugin.callbacks.length = 0;
+ I18nextHMRPlugin.callbacks.push(cb);
+};
+
+module.exports = I18nextHMRPlugin;
diff --git a/plugin.d.ts b/plugin.d.ts
index 9442ce9..d130862 100644
--- a/plugin.d.ts
+++ b/plugin.d.ts
@@ -1,5 +1,4 @@
-export class I18NextHMRPlugin {
- static addListener(cb: (data: { lang: string; ns: string }) => void): void;
-
- constructor(options: { localesDir: string; } | { localesDirs: string[] });
+export class HMRPlugin {
+ constructor(options: Partial<{ client: boolean; server: boolean }>);
+ init(i18n);
}
diff --git a/plugin.js b/plugin.js
index a3d102d..49df780 100644
--- a/plugin.js
+++ b/plugin.js
@@ -1,3 +1 @@
-module.exports = {
- I18NextHMRPlugin: require('./lib/plugin'),
-};
+module.exports = require('./lib/plugin');
diff --git a/webpack.d.ts b/webpack.d.ts
new file mode 100644
index 0000000..9442ce9
--- /dev/null
+++ b/webpack.d.ts
@@ -0,0 +1,5 @@
+export class I18NextHMRPlugin {
+ static addListener(cb: (data: { lang: string; ns: string }) => void): void;
+
+ constructor(options: { localesDir: string; } | { localesDirs: string[] });
+}
diff --git a/webpack.js b/webpack.js
new file mode 100644
index 0000000..b8dac7c
--- /dev/null
+++ b/webpack.js
@@ -0,0 +1,3 @@
+module.exports = {
+ I18NextHMRPlugin: require('./lib/webpack/plugin'),
+};