├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── __tests__
├── utils
│ └── preprocessor.js
├── vite
│ └── client-hmr.spec.js
└── webpack
│ ├── client-hmr.spec.js
│ ├── loader.spec.js
│ └── server-hmr.spec.js
├── examples
├── next-with-next-i18next-v13
│ ├── components
│ │ ├── Footer.js
│ │ └── Header.js
│ ├── next-i18next.config.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── index.js
│ │ └── second-page.js
│ ├── public
│ │ ├── app.css
│ │ └── locales
│ │ │ ├── de
│ │ │ ├── common.json
│ │ │ ├── footer.json
│ │ │ └── second-page.json
│ │ │ └── en
│ │ │ ├── common.json
│ │ │ ├── footer.json
│ │ │ └── second-page.json
│ └── yarn.lock
├── next-with-next-i18next
│ ├── .gitignore
│ ├── README.md
│ ├── components
│ │ ├── Footer.js
│ │ └── Header.js
│ ├── i18n.js
│ ├── index.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ │ ├── _app.js
│ │ ├── _error.js
│ │ ├── index.js
│ │ └── second-page.js
│ ├── public
│ │ └── static
│ │ │ ├── app.css
│ │ │ └── locales
│ │ │ ├── de
│ │ │ ├── common.json
│ │ │ ├── footer.json
│ │ │ └── second-page.json
│ │ │ └── en
│ │ │ ├── common.json
│ │ │ ├── footer.json
│ │ │ └── second-page.json
│ ├── server.js
│ └── yarn.lock
├── razzle-ssr
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ └── robots.txt
│ ├── razzle.config.js
│ └── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── Home.css
│ │ ├── Home.js
│ │ ├── client.js
│ │ ├── i18n.js
│ │ ├── index.js
│ │ ├── locales
│ │ ├── de
│ │ │ └── translations.json
│ │ └── en
│ │ │ └── translations.json
│ │ ├── react.svg
│ │ └── server.js
├── react-i18next
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── config-overrides.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── locales
│ │ │ ├── de
│ │ │ │ └── translation.json
│ │ │ └── en
│ │ │ │ └── translation.json
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── i18n.js
│ │ ├── index.css
│ │ ├── index.js
│ │ └── logo.svg
│ └── yarn.lock
├── rspack-react
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ ├── locales
│ │ │ ├── de
│ │ │ │ └── translation.json
│ │ │ └── en
│ │ │ │ └── translation.json
│ │ └── react.svg
│ ├── rspack.config.js
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── i18n.ts
│ │ ├── index.css
│ │ ├── main.tsx
│ │ └── react-env.d.ts
│ ├── tsconfig.json
│ └── yarn.lock
├── vite-react-i18next
│ ├── .eslintrc.cjs
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ ├── locales
│ │ │ ├── de
│ │ │ │ └── translation.json
│ │ │ └── en
│ │ │ │ └── translation.json
│ │ └── vite.svg
│ ├── src
│ │ ├── App.css
│ │ ├── App.jsx
│ │ ├── assets
│ │ │ └── react.svg
│ │ ├── i18n.js
│ │ ├── index.css
│ │ └── main.jsx
│ ├── vite.config.js
│ └── yarn.lock
└── vue-i18next
│ ├── .babelrc
│ ├── .gitignore
│ ├── README.md
│ ├── index.html
│ ├── locales
│ └── en
│ │ └── translation.json
│ ├── package.json
│ ├── src
│ ├── App.vue
│ ├── components
│ │ └── HelloWorld.vue
│ └── main.js
│ ├── webpack.config.js
│ └── yarn.lock
├── lib
├── plugin.d.ts
├── plugin.js
├── utils.js
├── vite
│ ├── client-hmr.js
│ ├── plugin.d.ts
│ └── plugin.js
└── webpack
│ ├── client-hmr.js
│ ├── loader.js
│ ├── plugin.d.ts
│ ├── plugin.js
│ ├── server-hmr.js
│ └── trigger.js
├── package.json
├── prettier.config.js
└── yarn.lock
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [felixmosh]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **please complete the following information:**
24 | - OS: [e.g. iOS]
25 | - Version: [e.g. 1.x]
26 |
27 | **Additional context**
28 | Add any other context about the problem here.
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | name: Test on Node ${{ matrix.node }} and ${{ matrix.os }}
6 |
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node: ['16.x', '18.x']
11 | os: [windows-latest, ubuntu-latest]
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v3
16 |
17 | - name: Use Node ${{ matrix.node }}
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: ${{ matrix.node }}
21 | cache: 'yarn'
22 |
23 | - name: Install Packages
24 | run: yarn install --frozen-lockfile
25 | env:
26 | CI: true
27 |
28 | - name: Test
29 | run: yarn test --ci --coverage --maxWorkers=2
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | .next
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | examples
2 | .idea
3 | __tests__
4 | .circleci
5 | prettier.config.js
6 | .github
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Changelog
2 |
3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4 |
5 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6 |
7 | #### [v3.1.4](https://github.com/felixmosh/i18next-hmr/compare/v3.1.3...v3.1.4)
8 |
9 | - fix: vite example [`4982c02`](https://github.com/felixmosh/i18next-hmr/commit/4982c0281d76bee11186061382c7721a6e61fd91)
10 | - build(deps-dev): bump webpack in /examples/vue-i18next [`44ea573`](https://github.com/felixmosh/i18next-hmr/commit/44ea57357a6067fe91184925a321d88021534877)
11 | - bump deps [`1fe020e`](https://github.com/felixmosh/i18next-hmr/commit/1fe020e629b47298e4e2b4a8cb074486a6e77989)
12 |
13 | #### [v3.1.3](https://github.com/felixmosh/i18next-hmr/compare/v3.1.2...v3.1.3)
14 |
15 | > 22 August 2024
16 |
17 | - build(deps): bump braces from 3.0.2 to 3.0.3 in /examples/react-i18next [`adc08ca`](https://github.com/felixmosh/i18next-hmr/commit/adc08ca90fba514520876bad80056de571f004b9)
18 | - build(deps): bump braces from 3.0.2 to 3.0.3 [`84b4871`](https://github.com/felixmosh/i18next-hmr/commit/84b48719feb235f11ad12d6163413b227679903d)
19 | - fix: move modification of queryString to try block [`5ee107f`](https://github.com/felixmosh/i18next-hmr/commit/5ee107f7e0a9f684d20dd145e1defd312e81ec71)
20 |
21 | #### [v3.1.2](https://github.com/felixmosh/i18next-hmr/compare/v3.1.1...v3.1.2)
22 |
23 | > 4 April 2024
24 |
25 | - build(deps): bump express from 4.18.2 to 4.19.2 in /examples/vue-i18next [`ef51cbb`](https://github.com/felixmosh/i18next-hmr/commit/ef51cbbdeec6e9ee8f77ce2857b7d8b46ceca4c2)
26 | - fix: typescript types defs [`3c0dd84`](https://github.com/felixmosh/i18next-hmr/commit/3c0dd84879eb20aed4dc8dcb3c44ce5f8ab1a8da)
27 | - Release 3.1.2 [`c4f1872`](https://github.com/felixmosh/i18next-hmr/commit/c4f187271cc08b5bd3ccc85833a690dd6df16dec)
28 |
29 | #### [v3.1.1](https://github.com/felixmosh/i18next-hmr/compare/v3.1.0...v3.1.1)
30 |
31 | > 3 April 2024
32 |
33 | - fix: remove usage of options.backend object since it may not contain queryStringParams [`dafd1e8`](https://github.com/felixmosh/i18next-hmr/commit/dafd1e8a18d205699209c429ef64f3b96fbf22f2)
34 | - Release 3.1.1 [`87c8e01`](https://github.com/felixmosh/i18next-hmr/commit/87c8e0148febd2ff3914eb96ebf42e7fa3491f21)
35 |
36 | #### [v3.1.0](https://github.com/felixmosh/i18next-hmr/compare/v3.0.4...v3.1.0)
37 |
38 | > 3 April 2024
39 |
40 | - feat: simplify webpack plugin to just inject a loader to user config instead of using NMF, to add support for RSPack [`da36693`](https://github.com/felixmosh/i18next-hmr/commit/da36693af853b37d58c086c4882776e34d864ff4)
41 | - build(deps): bump express in /examples/next-with-next-i18next [`d1ebe30`](https://github.com/felixmosh/i18next-hmr/commit/d1ebe3021c204e7023e07e5f24fd559d0709b1ed)
42 | - build(deps): bump express in /examples/react-i18next [`cfea2a8`](https://github.com/felixmosh/i18next-hmr/commit/cfea2a83bee90394962ce4b3a7b7b66176c4c296)
43 |
44 | #### [v3.0.4](https://github.com/felixmosh/i18next-hmr/compare/v3.0.3...v3.0.4)
45 |
46 | > 14 December 2023
47 |
48 | - Revert "build(deps): bump next in /examples/next-with-next-i18next" [`00d3afc`](https://github.com/felixmosh/i18next-hmr/commit/00d3afc3f23bc8c3bf6e17d898d815c383fd75fa)
49 | - build(deps): bump next in /examples/next-with-next-i18next [`01045b0`](https://github.com/felixmosh/i18next-hmr/commit/01045b0737db156b2823189f68d43bad218c3fcd)
50 | - build(deps): bump @babel/traverse in /examples/vue-i18next [`25c8d9a`](https://github.com/felixmosh/i18next-hmr/commit/25c8d9ac89e842cafaae4ae2c326cdce1ed9b417)
51 |
52 | #### [v3.0.3](https://github.com/felixmosh/i18next-hmr/compare/v3.0.2...v3.0.3)
53 |
54 | > 21 September 2023
55 |
56 | - fix: align trigger new location, closes #148 [`#148`](https://github.com/felixmosh/i18next-hmr/issues/148)
57 | - Release 3.0.3 [`faa1754`](https://github.com/felixmosh/i18next-hmr/commit/faa175462399a80b309fcceaadadceb143d4c8fe)
58 |
59 | #### [v3.0.2](https://github.com/felixmosh/i18next-hmr/compare/v3.0.1...v3.0.2)
60 |
61 | > 18 September 2023
62 |
63 | - fix: align named export with v2 [`c931611`](https://github.com/felixmosh/i18next-hmr/commit/c9316118c6616403672279e5393c089e82acba80)
64 | - Release 3.0.2 [`0c1322b`](https://github.com/felixmosh/i18next-hmr/commit/0c1322b663b5847c55086e09aaf1b864e961899a)
65 |
66 | #### [v3.0.1](https://github.com/felixmosh/i18next-hmr/compare/v3.0.0...v3.0.1)
67 |
68 | > 13 September 2023
69 |
70 | - feat: add vite example [`b1332c1`](https://github.com/felixmosh/i18next-hmr/commit/b1332c18bfbd17cce28252b86a06390a2da01686)
71 | - build(deps): bump tough-cookie in /examples/react-i18next [`e300f5b`](https://github.com/felixmosh/i18next-hmr/commit/e300f5b1ed6fb39f824d30b3f96ac1677c9b36db)
72 | - build(deps): bump semver from 5.7.1 to 5.7.2 in /examples/vue-i18next [`1af7150`](https://github.com/felixmosh/i18next-hmr/commit/1af71506a96307fb06c190a9b59abcfdb851f8ff)
73 |
74 | ### [v3.0.0](https://github.com/felixmosh/i18next-hmr/compare/v2.0.1...v3.0.0)
75 |
76 | > 12 September 2023
77 |
78 | - Bump deps [`0b74d59`](https://github.com/felixmosh/i18next-hmr/commit/0b74d595a0fabf128767a33be776301f311bba84)
79 | - Add Vite tests [`62afadd`](https://github.com/felixmosh/i18next-hmr/commit/62afaddb2a24b9f848a858df45de8d6488c2136e)
80 | - Initial support for Vite [`d86229f`](https://github.com/felixmosh/i18next-hmr/commit/d86229faa5a879d290ba7bae872cd2644f8e3cbb)
81 |
82 | #### [v2.0.1](https://github.com/felixmosh/i18next-hmr/compare/v2.0.0...v2.0.1)
83 |
84 | > 22 May 2023
85 |
86 | - fix: make d.ts files express the correct type [`92d0b19`](https://github.com/felixmosh/i18next-hmr/commit/92d0b19addcc7220c04f3e7bb28180013d9d58fe)
87 | - fix: ignore if passed an empty i18n instance [`7221a35`](https://github.com/felixmosh/i18next-hmr/commit/7221a3528c7771751b1ecf0988338114495093dd)
88 | - Release 2.0.1 [`4bff183`](https://github.com/felixmosh/i18next-hmr/commit/4bff183bb3037be2d193b0f677b91d661dcbb6de)
89 |
90 | ### [v2.0.0](https://github.com/felixmosh/i18next-hmr/compare/v1.11.4...v2.0.0)
91 |
92 | > 22 May 2023
93 |
94 | - feat: use i18next plugin system to get the i18next instance. [`2c60c1f`](https://github.com/felixmosh/i18next-hmr/commit/2c60c1f5779e38de687304aaf5cf483c974aa47d)
95 | - Release 2.0.0 [`7b0dc21`](https://github.com/felixmosh/i18next-hmr/commit/7b0dc21d448be079a6a8215406dfbc490b9cb034)
96 |
97 | #### [v1.11.4](https://github.com/felixmosh/i18next-hmr/compare/v1.11.3...v1.11.4)
98 |
99 | > 22 May 2023
100 |
101 | - Release 1.11.4 [`d3863d9`](https://github.com/felixmosh/i18next-hmr/commit/d3863d9d739c56d7be5537a53216fbae8e484ef5)
102 | - fix: fix plugin options type [`6fe639b`](https://github.com/felixmosh/i18next-hmr/commit/6fe639b144921d7e7b3e67422e0592b053c9e31a)
103 |
104 | #### [v1.11.3](https://github.com/felixmosh/i18next-hmr/compare/v1.11.2...v1.11.3)
105 |
106 | > 22 May 2023
107 |
108 | - Release 1.11.3 [`ad67539`](https://github.com/felixmosh/i18next-hmr/commit/ad67539d70abfb0da0dae657c79828a5ceac27fc)
109 | - fix: make localesDirs plugin config an optional [`3de3b0c`](https://github.com/felixmosh/i18next-hmr/commit/3de3b0cacd9a869d7a2b44f20f522cd7c340603d)
110 |
111 | #### [v1.11.2](https://github.com/felixmosh/i18next-hmr/compare/v1.11.1...v1.11.2)
112 |
113 | > 18 May 2023
114 |
115 | - fix: add defaultNS as an optional ns value [`3a20106`](https://github.com/felixmosh/i18next-hmr/commit/3a2010607a54b2a0bd888a1d52a95101078b1fbc)
116 | - Bump webpack from 5.73.0 to 5.76.1 in /examples/react-i18next [`290d870`](https://github.com/felixmosh/i18next-hmr/commit/290d870a52d31a59b34aeae4bb6901ecf649b271)
117 | - Bump json5 from 1.0.1 to 1.0.2 in /examples/vue-i18next [`e3b4d67`](https://github.com/felixmosh/i18next-hmr/commit/e3b4d674aee778ea4e8d083eaeb2fc46d6643461)
118 |
119 | #### [v1.11.1](https://github.com/felixmosh/i18next-hmr/compare/v1.11.0...v1.11.1)
120 |
121 | > 9 December 2022
122 |
123 | - Bump express from 4.17.1 to 4.18.2 in /examples/vue-i18next [`f571dc1`](https://github.com/felixmosh/i18next-hmr/commit/f571dc16dd076546a7e7895a6a0ca8f1d88555d8)
124 | - Bump loader-utils from 1.4.0 to 1.4.1 in /examples/vue-i18next [`4cf7127`](https://github.com/felixmosh/i18next-hmr/commit/4cf7127d35123347ced7e650d4b09ec1bbea2374)
125 | - Bump vm2 from 3.9.10 to 3.9.11 [`187b33c`](https://github.com/felixmosh/i18next-hmr/commit/187b33c239012bac65e0a84f4957d268f969e560)
126 |
127 | #### [v1.11.0](https://github.com/felixmosh/i18next-hmr/compare/v1.10.0...v1.11.0)
128 |
129 | > 16 August 2022
130 |
131 | - feat: pass changed files list to i18next getter, #118 [`0253f0a`](https://github.com/felixmosh/i18next-hmr/commit/0253f0a2f31db4d9caf096f8c1db7daf59f3399a)
132 | - Release 1.11.0 [`f845375`](https://github.com/felixmosh/i18next-hmr/commit/f845375fb4218e72b3574a135871a2a366cb5fad)
133 |
134 | #### [v1.10.0](https://github.com/felixmosh/i18next-hmr/compare/v1.9.0...v1.10.0)
135 |
136 | > 11 August 2022
137 |
138 | - feat: Add support for watching multiple locales dirs, #116 [`5302b06`](https://github.com/felixmosh/i18next-hmr/commit/5302b06010725b0c280ef7fa5f679fd295634f5b)
139 | - Release 1.10.0 [`ed603da`](https://github.com/felixmosh/i18next-hmr/commit/ed603da15d3496cf08a979f6585a5581ec72e21d)
140 |
141 | #### [v1.9.0](https://github.com/felixmosh/i18next-hmr/compare/v1.8.1...v1.9.0)
142 |
143 | > 11 August 2022
144 |
145 | - feat: add support for options.lng, options.fallbackLng, options.supportedLngs as a language source [`fae8a57`](https://github.com/felixmosh/i18next-hmr/commit/fae8a57132350dd56e176a18ecb379adf063999f)
146 | - feat: add support for strict language detection [`ea6a877`](https://github.com/felixmosh/i18next-hmr/commit/ea6a877bd1a4047784cd45a819bbcce43ea5e306)
147 | - feat: add support for fallbackNS [`3cd4326`](https://github.com/felixmosh/i18next-hmr/commit/3cd432663591e73cc4166225e14a4436eabcdc81)
148 |
149 | #### [v1.8.1](https://github.com/felixmosh/i18next-hmr/compare/v1.8.0...v1.8.1)
150 |
151 | > 20 July 2022
152 |
153 | - Bump deps [`844a4ae`](https://github.com/felixmosh/i18next-hmr/commit/844a4ae945050cbceb61227fe3af5b48227d1482)
154 | - Bump terser from 4.8.0 to 4.8.1 in /examples/vue-i18next [`e88c511`](https://github.com/felixmosh/i18next-hmr/commit/e88c511b8b77c47abd27e830beea0f4ec9e5597e)
155 | - Release 1.8.1 [`99f1de0`](https://github.com/felixmosh/i18next-hmr/commit/99f1de0781f6a3e3fb2a3893afcc4a70219a6bb4)
156 |
157 | #### [v1.8.0](https://github.com/felixmosh/i18next-hmr/compare/v1.7.8...v1.8.0)
158 |
159 | > 19 July 2022
160 |
161 | - Add next-i18next v11 example, closes #32 [`#32`](https://github.com/felixmosh/i18next-hmr/issues/32)
162 | - Update examples [`e171edc`](https://github.com/felixmosh/i18next-hmr/commit/e171edcc4c0288fb0c6c4ffa84303e7f6111b513)
163 | - Remove webpack 5 example [`0c02c24`](https://github.com/felixmosh/i18next-hmr/commit/0c02c246e483ccb4fcccc2be1269989b0db69f41)
164 | - feat: add the ability to pass a getter for i18n instance [`574deab`](https://github.com/felixmosh/i18next-hmr/commit/574deabdd45d129ec76858cd443e9da16b46401d)
165 |
166 | #### [v1.7.8](https://github.com/felixmosh/i18next-hmr/compare/v1.7.7...v1.7.8)
167 |
168 | > 19 July 2022
169 |
170 | - Bump deps [`ed7c5eb`](https://github.com/felixmosh/i18next-hmr/commit/ed7c5eb5a92ad1651b3e9b59ca60d5da7ea64c11)
171 | - feat: Add support for WatchIgnorePlugin [`1d9ce13`](https://github.com/felixmosh/i18next-hmr/commit/1d9ce13906e4af20e89c362b1ed6a03fdd621098)
172 | - Release 1.7.8 [`8949044`](https://github.com/felixmosh/i18next-hmr/commit/8949044cfe083ddb65790dfc22d9bc858cafe3ae)
173 |
174 | #### [v1.7.7](https://github.com/felixmosh/i18next-hmr/compare/v1.7.6...v1.7.7)
175 |
176 | > 28 April 2022
177 |
178 | - Bump next from 11.1.3 to 12.1.0 in /examples/next-with-next-i18next [`f3ec2a1`](https://github.com/felixmosh/i18next-hmr/commit/f3ec2a18b7801f16abfa397f38f5ce5302daebea)
179 | - Bump next in /examples/next-with-next-i18next-without-custom-server [`796982d`](https://github.com/felixmosh/i18next-hmr/commit/796982d1d2c50abd1c3f12a1a6def14e451016a9)
180 | - Deps bump [`ecb85f5`](https://github.com/felixmosh/i18next-hmr/commit/ecb85f58d7abc3b5bf38ed96c5fec5ae7e40a458)
181 |
182 | #### [v1.7.6](https://github.com/felixmosh/i18next-hmr/compare/v1.7.5...v1.7.6)
183 |
184 | > 28 April 2022
185 |
186 | - Deps bump [`923f55f`](https://github.com/felixmosh/i18next-hmr/commit/923f55f3cfd9967e038d10d92ab68eaa5e947941)
187 | - Release 1.7.6 [`42e1cb9`](https://github.com/felixmosh/i18next-hmr/commit/42e1cb9d29247ee41bf965483e79ce91afe1eae2)
188 |
189 | #### [v1.7.5](https://github.com/felixmosh/i18next-hmr/compare/v1.7.4...v1.7.5)
190 |
191 | > 17 August 2021
192 |
193 | - Bump dev deps [`50e8672`](https://github.com/felixmosh/i18next-hmr/commit/50e8672cbad8a8b397251704f4462cc203159006)
194 | - Bump next in /examples/next-with-next-i18next-without-custom-server [`f85c2ba`](https://github.com/felixmosh/i18next-hmr/commit/f85c2ba5a3b48dc27cbe542febb3af6a07724dfa)
195 | - Bump next from 10.2.3 to 11.1.0 in /examples/next-with-next-i18next [`2ca15a1`](https://github.com/felixmosh/i18next-hmr/commit/2ca15a142a941df9fc3875c497dab52890b7611e)
196 |
197 | #### [v1.7.4](https://github.com/felixmosh/i18next-hmr/compare/v1.7.3...v1.7.4)
198 |
199 | > 21 June 2021
200 |
201 | - Bump examples deps [`5010041`](https://github.com/felixmosh/i18next-hmr/commit/50100413345f1ac9949e28e980a9ee9ad225266e)
202 | - Bump browserslist [`8eb92e8`](https://github.com/felixmosh/i18next-hmr/commit/8eb92e848f5c8eb03a3d7a1f55ed1479d4e54833)
203 | - Release 1.7.4 [`c8ab995`](https://github.com/felixmosh/i18next-hmr/commit/c8ab995d183bf840931c1baf0edea1645f444c17)
204 |
205 | #### [v1.7.3](https://github.com/felixmosh/i18next-hmr/compare/v1.7.2...v1.7.3)
206 |
207 | > 15 June 2021
208 |
209 | - Bump deps [`c8a67df`](https://github.com/felixmosh/i18next-hmr/commit/c8a67df0188e711eccd190a134cbd3cefe5935fd)
210 | - Release 1.7.3 [`b572fe0`](https://github.com/felixmosh/i18next-hmr/commit/b572fe0b65135720410824eb8a15fcc68794ede2)
211 |
212 | #### [v1.7.2](https://github.com/felixmosh/i18next-hmr/compare/v1.7.1...v1.7.2)
213 |
214 | > 2 June 2021
215 |
216 | - Bump y18n in /examples/next-with-next-i18next-without-custom-server [`#36`](https://github.com/felixmosh/i18next-hmr/pull/36)
217 | - Bump y18n from 4.0.0 to 4.0.1 in /examples/vue-i18next [`#35`](https://github.com/felixmosh/i18next-hmr/pull/35)
218 | - Bump react-dev-utils from 9.1.0 to 11.0.4 in /examples/react-i18next [`#34`](https://github.com/felixmosh/i18next-hmr/pull/34)
219 | - Bump y18n from 4.0.0 to 4.0.1 in /examples/next-with-next-i18next [`#33`](https://github.com/felixmosh/i18next-hmr/pull/33)
220 | - Bump elliptic from 6.5.3 to 6.5.4 in /examples/razzle-ssr [`#31`](https://github.com/felixmosh/i18next-hmr/pull/31)
221 | - Bump elliptic in /examples/next-with-next-i18next-without-custom-server [`#30`](https://github.com/felixmosh/i18next-hmr/pull/30)
222 | - Bump elliptic from 6.5.3 to 6.5.4 in /examples/vue-i18next [`#29`](https://github.com/felixmosh/i18next-hmr/pull/29)
223 | - Bump elliptic from 6.5.3 to 6.5.4 in /examples/next-with-next-i18next [`#28`](https://github.com/felixmosh/i18next-hmr/pull/28)
224 | - Change to yarn.lock [`53dc071`](https://github.com/felixmosh/i18next-hmr/commit/53dc07190f18a2b631ca54785f77fd3cdef53813)
225 | - Bump browserslist from 4.13.0 to 4.16.6 in /examples/vue-i18next [`be25c2d`](https://github.com/felixmosh/i18next-hmr/commit/be25c2d938c4e41a898b5b1bd8fbaf2a1a34a0a0)
226 | - Sort package.json [`eace68d`](https://github.com/felixmosh/i18next-hmr/commit/eace68d0a2950ad82f27e8825b0b465b70e85ba7)
227 |
228 | #### [v1.7.1](https://github.com/felixmosh/i18next-hmr/compare/v1.7.0...v1.7.1)
229 |
230 | > 18 February 2021
231 |
232 | - Move from circleCi to github actions [`0959cba`](https://github.com/felixmosh/i18next-hmr/commit/0959cba2b2d06d9372a3c853538a897b2f0ab2b4)
233 | - Release 1.7.1 [`6428bee`](https://github.com/felixmosh/i18next-hmr/commit/6428beea974ca27af2c9b60b1c680d4e0652842e)
234 | - Update Readme [`bef118e`](https://github.com/felixmosh/i18next-hmr/commit/bef118e01b926b57f682502a735ad9c6833e9dd9)
235 |
236 | #### [v1.7.0](https://github.com/felixmosh/i18next-hmr/compare/v1.6.3...v1.7.0)
237 |
238 | > 15 January 2021
239 |
240 | - Bump ini from 1.3.5 to 1.3.8 in /examples/razzle-ssr [`#24`](https://github.com/felixmosh/i18next-hmr/pull/24)
241 | - Bump ini from 1.3.5 to 1.3.8 in /examples/next-with-next-i18next-without-custom-server [`#25`](https://github.com/felixmosh/i18next-hmr/pull/25)
242 | - Add webpack 5 support, closes #26 [`#26`](https://github.com/felixmosh/i18next-hmr/issues/26)
243 | - Bump deps [`55d8773`](https://github.com/felixmosh/i18next-hmr/commit/55d87739f4b3c1cdb7fa34f62764a5688ab416ac)
244 | - Bump dev-deps [`d4604c9`](https://github.com/felixmosh/i18next-hmr/commit/d4604c9f96db9fe50e85b68ad74d16b2174dfa4e)
245 | - Release 1.7.0 [`c3f397b`](https://github.com/felixmosh/i18next-hmr/commit/c3f397b0df4ee86b169dd8999b448232fc77c2a2)
246 |
247 | #### [v1.6.3](https://github.com/felixmosh/i18next-hmr/compare/v1.6.2...v1.6.3)
248 |
249 | > 12 December 2020
250 |
251 | - Bump ini from 1.3.5 to 1.3.8 [`#23`](https://github.com/felixmosh/i18next-hmr/pull/23)
252 | - Bump ini from 1.3.5 to 1.3.8 in /examples/vue-i18next [`#22`](https://github.com/felixmosh/i18next-hmr/pull/22)
253 | - Bump deps [`6173642`](https://github.com/felixmosh/i18next-hmr/commit/61736425c3ce094dc39112942c0d7ee45243d57a)
254 | - Update issue templates [`e983cb8`](https://github.com/felixmosh/i18next-hmr/commit/e983cb86aa0329b4745623c85c5d8f7493dd52c2)
255 | - Release 1.6.3 [`448c2ee`](https://github.com/felixmosh/i18next-hmr/commit/448c2ee0cb8ce5af6bf0fa4074a506377e9f3190)
256 |
257 | #### [v1.6.2](https://github.com/felixmosh/i18next-hmr/compare/v1.6.1...v1.6.2)
258 |
259 | > 16 October 2020
260 |
261 | - Bump next from 9.5.0 to 9.5.4 in /examples/next-with-next-i18next-without-custom-server [`#21`](https://github.com/felixmosh/i18next-hmr/pull/21)
262 | - Bump next in /examples/next-with-next-i18next-without-custom-server [`4ee84c8`](https://github.com/felixmosh/i18next-hmr/commit/4ee84c806ef28db9be17e46d34703c1c61e77a7f)
263 | - Release 1.6.2 [`46818c5`](https://github.com/felixmosh/i18next-hmr/commit/46818c5fa29ca046b2b828cd9c5d3bcd8c6eab11)
264 | - fix: Webpack 5 doesn't have mtimes at start [`e7a16e8`](https://github.com/felixmosh/i18next-hmr/commit/e7a16e8d0ea1ba58274a8832cfd9f01ab73dce59)
265 |
266 | #### [v1.6.1](https://github.com/felixmosh/i18next-hmr/compare/v1.6.0...v1.6.1)
267 |
268 | > 2 October 2020
269 |
270 | - Bump node-fetch from 2.6.0 to 2.6.1 [`#18`](https://github.com/felixmosh/i18next-hmr/pull/18)
271 | - Reload translation when using lng-country combination, closes #19 [`#19`](https://github.com/felixmosh/i18next-hmr/issues/19)
272 | - Release 1.6.1 [`ac57a5d`](https://github.com/felixmosh/i18next-hmr/commit/ac57a5dd0f6d9c485f754980a23f50b072f693a4)
273 |
274 | #### [v1.6.0](https://github.com/felixmosh/i18next-hmr/compare/v1.5.6...v1.6.0)
275 |
276 | > 6 September 2020
277 |
278 | - Add support for change in multiple files at once, closes #17 [`#17`](https://github.com/felixmosh/i18next-hmr/issues/17)
279 | - Add an example which uses next-18next without custom server. [`fc8525f`](https://github.com/felixmosh/i18next-hmr/commit/fc8525fd0757d6a69f47b80b517e4cb47ccb8fba)
280 | - Fix security warning [`bae4b8f`](https://github.com/felixmosh/i18next-hmr/commit/bae4b8f199a818d96a312e4411d8a28e828e27d0)
281 | - Release 1.6.0 [`64c95df`](https://github.com/felixmosh/i18next-hmr/commit/64c95dfe3faffd4b9ef1a245e7478e678a26f477)
282 |
283 | #### [v1.5.6](https://github.com/felixmosh/i18next-hmr/compare/v1.5.5...v1.5.6)
284 |
285 | > 26 July 2020
286 |
287 | - Bump lodash from 4.17.15 to 4.17.19 [`#13`](https://github.com/felixmosh/i18next-hmr/pull/13)
288 | - Bump lodash from 4.17.15 to 4.17.19 in /examples/vue-i18next [`#15`](https://github.com/felixmosh/i18next-hmr/pull/15)
289 | - Bump lodash from 4.17.15 to 4.17.19 in /examples/next-with-next-i18next [`#14`](https://github.com/felixmosh/i18next-hmr/pull/14)
290 | - Update examples deps [`8dc0caa`](https://github.com/felixmosh/i18next-hmr/commit/8dc0caa5fe967ec025b235060547a0665228c795)
291 | - Add usage of release it [`077944d`](https://github.com/felixmosh/i18next-hmr/commit/077944da3e642108c842d5631a5a006fd3996dbf)
292 | - Release 1.5.6 [`9325398`](https://github.com/felixmosh/i18next-hmr/commit/9325398d93238953e4abc6e2b94702f3f94f08ca)
293 |
294 | #### [v1.5.5](https://github.com/felixmosh/i18next-hmr/compare/v1.5.4...v1.5.5)
295 |
296 | > 24 June 2020
297 |
298 | - Add support for backslashes paths, closes #12 [`#12`](https://github.com/felixmosh/i18next-hmr/issues/12)
299 | - Reformat the code based on prettier [`4960885`](https://github.com/felixmosh/i18next-hmr/commit/4960885161523ef9d21bcd3924e529376d43e821)
300 | - Bump v1.5.5 [`7ee8ea1`](https://github.com/felixmosh/i18next-hmr/commit/7ee8ea1c52f2cc411a7aa71620bd1a6e882e3a7d)
301 | - Create dependabot.yml [`2303562`](https://github.com/felixmosh/i18next-hmr/commit/23035627585b21353b5f23d48a792be6e49aab61)
302 |
303 | #### [v1.5.4](https://github.com/felixmosh/i18next-hmr/compare/v1.5.3...v1.5.4)
304 |
305 | > 18 June 2020
306 |
307 | - Fix types [`383cc6f`](https://github.com/felixmosh/i18next-hmr/commit/383cc6fc036580f492fbe4d08c8b91149772f13e)
308 |
309 | #### [v1.5.3](https://github.com/felixmosh/i18next-hmr/compare/v1.5.2...v1.5.3)
310 |
311 | > 11 June 2020
312 |
313 | - Bump v1.5.3 [`6a090c9`](https://github.com/felixmosh/i18next-hmr/commit/6a090c99a43b752e1384980d56d30ed5c10fcb70)
314 | - Fix client side logs styles [`7fb9836`](https://github.com/felixmosh/i18next-hmr/commit/7fb983677a33a71a36670ccc2990efb5a301795c)
315 |
316 | #### [v1.5.2](https://github.com/felixmosh/i18next-hmr/compare/v1.5.1...v1.5.2)
317 |
318 | > 10 June 2020
319 |
320 | - Bump websocket-extensions from 0.1.3 to 0.1.4 in /examples/vue-i18next [`#7`](https://github.com/felixmosh/i18next-hmr/pull/7)
321 | - Ignore changes in none loaded namespace, closes #8 [`#8`](https://github.com/felixmosh/i18next-hmr/issues/8)
322 | - Bump dev deps [`9f76a85`](https://github.com/felixmosh/i18next-hmr/commit/9f76a85665f6f029a0991c5854d5833d5561c71f)
323 | - Distinguish between name-spaces that containing each other name [`d8763ff`](https://github.com/felixmosh/i18next-hmr/commit/d8763ffe7ca2dc392e8cde7e1d61836db9cfd213)
324 | - Bump v1.5.2 [`1a86f9d`](https://github.com/felixmosh/i18next-hmr/commit/1a86f9d6f7c7ebf26e8162a42ea56f34ecc0d85f)
325 |
326 | #### [v1.5.1](https://github.com/felixmosh/i18next-hmr/compare/v1.5.0...v1.5.1)
327 |
328 | > 31 May 2020
329 |
330 | - Bump v1.5.1 [`7783303`](https://github.com/felixmosh/i18next-hmr/commit/778330352f3e7af851feee9e781a074f16e6d711)
331 | - Remove console.log [`4e89bd0`](https://github.com/felixmosh/i18next-hmr/commit/4e89bd0f00b59e34a6e4c23cb0c1067bc5488932)
332 | - Ignore prettier.config from npm [`2df230b`](https://github.com/felixmosh/i18next-hmr/commit/2df230bf08cdc8291d2cab5be1f904513071f649)
333 |
334 | #### [v1.5.0](https://github.com/felixmosh/i18next-hmr/compare/v1.4.1...v1.5.0)
335 |
336 | > 25 May 2020
337 |
338 | - Bump next from 9.3.0 to 9.3.2 in /examples/next-with-next-i18next [`#4`](https://github.com/felixmosh/i18next-hmr/pull/4)
339 | - Add support for nested namespaces, closes #6 [`#6`](https://github.com/felixmosh/i18next-hmr/issues/6)
340 | - Add razzle example [`1ea77f3`](https://github.com/felixmosh/i18next-hmr/commit/1ea77f399cc4ed55a4e769296fa4216f7bcc9fcf)
341 | - Bump example deps [`29c4e77`](https://github.com/felixmosh/i18next-hmr/commit/29c4e779e9655c865f1fd60b4821bd37641aac21)
342 | - Bump dev deps [`d2fbd62`](https://github.com/felixmosh/i18next-hmr/commit/d2fbd62b33b8ca526c6c96ccdd075c910719f8ef)
343 |
344 | #### [v1.4.1](https://github.com/felixmosh/i18next-hmr/compare/v1.4.0...v1.4.1)
345 |
346 | > 10 March 2020
347 |
348 | - fix: handle cases when IgnoringWatchFileSystem is used as watcher (has wfs field) [`1eb90a0`](https://github.com/felixmosh/i18next-hmr/commit/1eb90a04196b4f24783a0f7f64100a2592406672)
349 | - Bump v1.4.1 [`71aa933`](https://github.com/felixmosh/i18next-hmr/commit/71aa933229e74f4272c2b0886a779b51c56b1ef3)
350 |
351 | #### [v1.4.0](https://github.com/felixmosh/i18next-hmr/compare/v1.3.0...v1.4.0)
352 |
353 | > 10 March 2020
354 |
355 | - feat: add native HMR support for server side [`76adf27`](https://github.com/felixmosh/i18next-hmr/commit/76adf2773cfdc535c322eb299588aef9a75051b2)
356 | - fix: handle changes when i18next.language has lang & country [`d2eba98`](https://github.com/felixmosh/i18next-hmr/commit/d2eba982628cae700e9102285bae700985f79d56)
357 | - feat: add native HMR support explanation. [`94e29b5`](https://github.com/felixmosh/i18next-hmr/commit/94e29b56b8b608fd8069f9b07a94d11d657c95dd)
358 |
359 | #### [v1.3.0](https://github.com/felixmosh/i18next-hmr/compare/v1.2.0...v1.3.0)
360 |
361 | > 16 December 2019
362 |
363 | - Bump versions [`5578dcd`](https://github.com/felixmosh/i18next-hmr/commit/5578dcdc3135b2ff8476701602855060ef65313b)
364 | - Add notification when reload fails [`6517849`](https://github.com/felixmosh/i18next-hmr/commit/651784968f3f6f7f87517e6c9e1a708963ea2d89)
365 | - Bump v1.3.0 [`bac1830`](https://github.com/felixmosh/i18next-hmr/commit/bac1830a8dd3a1741261500aaff61f218192d018)
366 |
367 | #### [v1.2.0](https://github.com/felixmosh/i18next-hmr/compare/v1.1.3...v1.2.0)
368 |
369 | > 12 December 2019
370 |
371 | - Add vue-i18next example [`31be14c`](https://github.com/felixmosh/i18next-hmr/commit/31be14c9c7153bb5f375acf139de0717938cd726)
372 | - Bump deps [`22a21d9`](https://github.com/felixmosh/i18next-hmr/commit/22a21d9803b4fc2b77990d16ada63e6f48990ac6)
373 | - Split between client & server entries [`47ef299`](https://github.com/felixmosh/i18next-hmr/commit/47ef299b50a543e4e2103b552e7ba73472aaa690)
374 |
375 | #### [v1.1.3](https://github.com/felixmosh/i18next-hmr/compare/v1.1.2...v1.1.3)
376 |
377 | > 10 December 2019
378 |
379 | - Add some tests [`8c92ac6`](https://github.com/felixmosh/i18next-hmr/commit/8c92ac69f3f8b8ce37a4fc08b79f41c7254d61cd)
380 | - Bump versions [`d15ed3c`](https://github.com/felixmosh/i18next-hmr/commit/d15ed3c1a1b1f62075e6ad3fd5bade18e4835b7b)
381 | - Add client hmr tests [`53c5aab`](https://github.com/felixmosh/i18next-hmr/commit/53c5aab3d43f99dda1971367a078d65cbb552e52)
382 |
383 | #### [v1.1.2](https://github.com/felixmosh/i18next-hmr/compare/v1.1.1...v1.1.2)
384 |
385 | > 7 December 2019
386 |
387 | - Handle missing backend option [`7312b36`](https://github.com/felixmosh/i18next-hmr/commit/7312b36de6fdaaa08549395764a09d66725706b0)
388 | - Bump v1.1.2 [`27e7b24`](https://github.com/felixmosh/i18next-hmr/commit/27e7b248ed0b44f440f2ecd0212263cf9ba16b53)
389 | - Update cra example to use named chunks due to https://github.com/webpack/webpack/issues/10095 [`5bb327b`](https://github.com/felixmosh/i18next-hmr/commit/5bb327b3e1bdc1a8e7a7242b9e9a96a805f66ab7)
390 |
391 | #### [v1.1.1](https://github.com/felixmosh/i18next-hmr/compare/v1.1.0...v1.1.1)
392 |
393 | > 6 December 2019
394 |
395 | - Add types [`a9d2d19`](https://github.com/felixmosh/i18next-hmr/commit/a9d2d198ef34b2774f9bbac2f76f35f820fb8cd6)
396 | - Update example version [`78a1e37`](https://github.com/felixmosh/i18next-hmr/commit/78a1e37765926185d1ae51a88814489d0e01bc08)
397 | - small improvement of client code [`b751376`](https://github.com/felixmosh/i18next-hmr/commit/b75137620d9816c25b730b63bcbbef370af37886)
398 |
399 | #### [v1.1.0](https://github.com/felixmosh/i18next-hmr/compare/v1.0.3...v1.1.0)
400 |
401 | > 6 December 2019
402 |
403 | - Split server & client to separate methods (prevent race condition on server side) [`8a96f50`](https://github.com/felixmosh/i18next-hmr/commit/8a96f507e68d1ad96967237b9cf104d1ad065e9b)
404 | - Keep only the last server listener (in-case of server re-loaded) [`7fed769`](https://github.com/felixmosh/i18next-hmr/commit/7fed7694918a07bf44a6ff0e158a0fc72faf0913)
405 |
406 | #### [v1.0.3](https://github.com/felixmosh/i18next-hmr/compare/v1.0.2...v1.0.3)
407 |
408 | > 6 December 2019
409 |
410 | - Add lib as deps in package.json [`f0f134d`](https://github.com/felixmosh/i18next-hmr/commit/f0f134dc81e30fbf86baf874cc54022f8fcdfca6)
411 | - Remove console log [`5ce5b59`](https://github.com/felixmosh/i18next-hmr/commit/5ce5b5941273f9d02e18ee21424252fc2aa6236f)
412 |
413 | #### [v1.0.2](https://github.com/felixmosh/i18next-hmr/compare/v1.0.1...v1.0.2)
414 |
415 | > 6 December 2019
416 |
417 | - Readme fix [`3a33b75`](https://github.com/felixmosh/i18next-hmr/commit/3a33b75f54c420502437d3781f7bf681d0281b4c)
418 |
419 | #### v1.0.1
420 |
421 | > 6 December 2019
422 |
423 | - Add react-i18next example [`fd86257`](https://github.com/felixmosh/i18next-hmr/commit/fd86257a58419b6f644ddef429b33abccf3520dc)
424 | - Add next with next-i18next example [`2e993e8`](https://github.com/felixmosh/i18next-hmr/commit/2e993e826ae4214f1a670451a12f329efcb597f4)
425 | - Update the package-lock.json [`0d53840`](https://github.com/felixmosh/i18next-hmr/commit/0d538406a19da49ae749c5ca944fe6330ba21216)
426 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 felixmosh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # i18next-hmr
2 |
3 | [](https://www.npmjs.com/package/i18next-hmr)
4 | 
5 |
6 | I18Next HMR🔥 webpack / vite plugin that allows reload translation resources instantly on the client & the server.
7 |
8 | ## Requirements
9 |
10 | - Node.js v10 or above
11 | - Webpack v4.x - v5.x
12 | - Vite v3
13 |
14 |
15 | ## Installation
16 |
17 | ```sh
18 | $ npm install --save-dev i18next-hmr
19 | ```
20 |
21 | ## Usage
22 |
23 | Add the plugin to your webpack config (or next.config.js).
24 |
25 |
26 |
27 | ```js
28 | // webpack.config.js
29 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
30 |
31 | module.exports = {
32 | ...
33 | plugins: [
34 | new I18NextHMRPlugin({
35 | localesDir: path.resolve(__dirname, 'static/locales'),
36 | localesDirs: [
37 | // use this property for multiple locales directories
38 | ]
39 | })
40 | ]
41 | };
42 | ```
43 |
44 |
45 |
46 | ```js
47 | // i18next.config.js
48 | const Backend = require('i18next-http-backend');
49 | const i18next = require('i18next');
50 | const { HMRPlugin } = require('i18next-hmr/plugin');
51 |
52 | const instance = i18next.use(Backend); // http-backend is required for client side reloading
53 |
54 | if (process.env.NODE_ENV !== 'production') {
55 | instance.use(new HMRPlugin({
56 | webpack: {
57 | client: typeof window !== 'undefined', // enables client side HMR in webpack
58 | server: typeof window === 'undefined' // enables server side HMR in webpack
59 | },
60 | vite: {
61 | client: typeof window !== 'undefined', // enables client side HMR in Vite
62 | }
63 | }));
64 | }
65 |
66 | instance.init(options, callback);
67 |
68 | module.exports = instance;
69 | ```
70 |
71 |
72 |
73 |
74 | Start the app with `NODE_ENV=development`
75 |
76 | ### Server side
77 |
78 | This lib will trigger [`i18n.reloadResources([lang], [ns])`](https://www.i18next.com/overview/api#reloadresources) on the server side with `lang` & `namespace` extracted from the translation filename that was changed.
79 |
80 | ⚠️ 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/).
81 | There are 2 ways to do that:
82 |
83 | 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-).
84 | 2. (deprecated method) use a relative path to `node_modules`, something like:
85 | ```js
86 | // server.entry.js
87 | if (process.env.NODE_ENV !== 'production') {
88 | const { applyServerHMR } = require('../node_modules/i18next-hmr/server');
89 | applyServerHMR(i18n);
90 | }
91 | ```
92 |
93 | ### Client side
94 |
95 | The lib will invoke Webpack's / Vite HMR to update client side, that will re-fetch (with cache killer) the updated translation files and trigger [`i18n.changelanguage(lang)`](https://www.i18next.com/overview/api#changelanguage) to trigger listeners (which in React apps it will update the UI).
96 |
97 | ## Example
98 |
99 | Working examples can be found in the [`examples`](https://github.com/felixmosh/i18next-hmr/tree/master/examples) folder.
100 |
101 | #### [`nextjs`](https://github.com/zeit/next.js) with [`next-i18next`](https://github.com/isaachinman/next-i18next)
102 |
103 | 
104 |
--------------------------------------------------------------------------------
/__tests__/utils/preprocessor.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | process(src, path) {
3 | return {
4 | code: src
5 | .replace(/module\.hot/g, 'global.mockModule.hot')
6 | .replace(/import\.meta\.hot/g, 'global.mockImport.meta.hot')
7 | .replace(
8 | /export function applyViteClientHMR/,
9 | 'module.exports = function applyViteClientHMR'
10 | ),
11 | };
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/__tests__/vite/client-hmr.spec.js:
--------------------------------------------------------------------------------
1 | global.mockImport = {
2 | meta: {
3 | hot: {
4 | on: jest.fn(),
5 | },
6 | },
7 | };
8 |
9 | const applyClientHMR = require('../../lib/vite/client-hmr');
10 |
11 | function whenHotTriggeredWith(changedFiles) {
12 | const listenerCallback = mockImport.meta.hot.on.mock.calls[0][1];
13 | return listenerCallback({ changedFiles });
14 | }
15 |
16 | describe('Vite - client-hmr', () => {
17 | let i18nMock;
18 | let reloadError;
19 |
20 | beforeEach(() => {
21 | reloadError = undefined;
22 |
23 | i18nMock = {
24 | reloadResources: jest.fn().mockImplementation((_lang, _ns, callbackFn) => {
25 | if (typeof callbackFn === 'function') {
26 | callbackFn(reloadError);
27 | }
28 | return Promise.resolve();
29 | }),
30 | changeLanguage: jest.fn(),
31 | languages: ['en', 'de', 'en-US'],
32 | };
33 |
34 | mockImport.meta.hot.on.mockReset();
35 | });
36 |
37 | afterEach(() => {
38 | jest.restoreAllMocks();
39 | });
40 |
41 | it('should register a listener on the key `i18next-hmr:locale-changed`', () => {
42 | applyClientHMR(i18nMock);
43 | expect(mockImport.meta.hot.on).toHaveBeenCalledWith(
44 | 'i18next-hmr:locale-changed',
45 | expect.any(Function)
46 | );
47 | });
48 |
49 | it('should warn regarding missing backend options once', () => {
50 | jest.spyOn(global.console, 'warn');
51 | i18nMock.options = { ns: ['name-space'] };
52 |
53 | applyClientHMR(i18nMock);
54 | whenHotTriggeredWith(['en/name-space']);
55 |
56 | expect(global.console.warn).toHaveBeenCalledTimes(1);
57 | expect(global.console.warn).toHaveBeenCalledWith(
58 | expect.stringContaining('i18next-http-backend not found'),
59 | expect.any(String),
60 | expect.any(String)
61 | );
62 | });
63 |
64 | it('should use backendConnector options from services as cache killer param', () => {
65 | i18nMock.services = {
66 | ...i18nMock.services,
67 | backendConnector: { backend: { options: {} } },
68 | };
69 | i18nMock.language = 'en';
70 | i18nMock.options = { ns: ['name-space'] };
71 |
72 | applyClientHMR(i18nMock);
73 |
74 | whenHotTriggeredWith(['en/name-space']);
75 |
76 | expect(i18nMock.services.backendConnector.backend.options).toHaveProperty('queryStringParams', {
77 | _: expect.any(Number),
78 | });
79 | });
80 |
81 | it('should not throw backendConnector options is not available', () => {
82 | i18nMock.services = {
83 | ...i18nMock.services,
84 | backendConnector: { backend: {} },
85 | };
86 | i18nMock.language = 'en';
87 | i18nMock.options = { ns: ['name-space'] };
88 |
89 | applyClientHMR(i18nMock);
90 |
91 | expect(() => whenHotTriggeredWith(['en/name-space'])).not.toThrow();
92 | });
93 |
94 | it('should trigger reload when translation file changed', async () => {
95 | i18nMock.options = { backend: {}, ns: ['name-space'] };
96 | i18nMock.language = 'en';
97 |
98 | applyClientHMR(i18nMock);
99 |
100 | await whenHotTriggeredWith(['en/name-space']);
101 |
102 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
103 | ['en'],
104 | ['name-space'],
105 | expect.any(Function)
106 | );
107 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
108 | });
109 |
110 | it('should trigger reload when i18n given as a getter function', async () => {
111 | i18nMock.options = { backend: {}, ns: ['name-space'] };
112 | i18nMock.language = 'en';
113 |
114 | applyClientHMR(() => i18nMock);
115 |
116 | await whenHotTriggeredWith(['en/name-space']);
117 |
118 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
119 | ['en'],
120 | ['name-space'],
121 | expect.any(Function)
122 | );
123 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
124 | });
125 |
126 | it('should pass changed filed to the i18next getter', () => {
127 | i18nMock.options = { backend: {}, ns: ['name-space'] };
128 | i18nMock.language = 'en';
129 | const getter = jest.fn().mockImplementation(() => i18nMock);
130 | const changedFiles = ['en/name-space'];
131 |
132 | applyClientHMR(getter);
133 | whenHotTriggeredWith(changedFiles);
134 |
135 | expect(getter).toHaveBeenCalledWith({ changedFiles });
136 | });
137 |
138 | it('should trigger reload when lng-country combination file changed', () => {
139 | i18nMock.options = { backend: {}, ns: ['name-space'] };
140 | i18nMock.language = 'en-US';
141 |
142 | applyClientHMR(i18nMock);
143 |
144 | whenHotTriggeredWith(['en-US/name-space']);
145 |
146 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
147 | ['en-US'],
148 | ['name-space'],
149 | expect.any(Function)
150 | );
151 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
152 | });
153 |
154 | it('should trigger reload when translation file changed with nested namespace', async () => {
155 | i18nMock.options = { backend: {}, ns: ['name-space', 'nested/name-space'] };
156 | i18nMock.language = 'en';
157 |
158 | applyClientHMR(i18nMock);
159 |
160 | await whenHotTriggeredWith(['en/nested/name-space']);
161 |
162 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
163 | ['en'],
164 | ['nested/name-space'],
165 | expect.any(Function)
166 | );
167 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
168 | });
169 |
170 | it('should trigger reload when translation file with backslashes (windows)', async () => {
171 | i18nMock.options = { backend: {}, ns: ['name-space', 'nested/name-space'] };
172 | i18nMock.language = 'en';
173 |
174 | applyClientHMR(i18nMock);
175 |
176 | await whenHotTriggeredWith(['en\\nested\\name-space']);
177 |
178 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
179 | ['en'],
180 | ['nested/name-space'],
181 | expect.any(Function)
182 | );
183 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
184 | });
185 |
186 | it('should not trigger changeLanguage when current lang is not the one that was edited', async () => {
187 | i18nMock.options = { backend: {}, ns: ['name-space'] };
188 | i18nMock.language = 'en';
189 | i18nMock.languages.push('otherLang');
190 |
191 | applyClientHMR(i18nMock);
192 |
193 | await whenHotTriggeredWith(['otherLang/name-space']);
194 |
195 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
196 | ['otherLang'],
197 | ['name-space'],
198 | expect.any(Function)
199 | );
200 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
201 | });
202 |
203 | it('should notify that reload resource failed', async () => {
204 | jest.spyOn(global.console, 'error');
205 | i18nMock.options = { backend: {}, ns: ['name-space'] };
206 | i18nMock.language = 'en';
207 | reloadError = 'reload failed';
208 |
209 | applyClientHMR(i18nMock);
210 | await whenHotTriggeredWith(['en/name-space']);
211 |
212 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
213 | expect(global.console.error).toHaveBeenCalledWith(
214 | expect.stringContaining(reloadError),
215 | expect.any(String),
216 | expect.any(String)
217 | );
218 | });
219 |
220 | it('should ignore changes of none loaded namespace', async () => {
221 | jest.spyOn(global.console, 'log');
222 | i18nMock.options = { backend: {}, ns: ['name-space'] };
223 | i18nMock.language = 'en';
224 |
225 | applyClientHMR(i18nMock);
226 |
227 | await whenHotTriggeredWith(['en/none-loaded-ns']);
228 |
229 | expect(global.console.log).not.toHaveBeenCalledWith(
230 | expect.stringContaining('Got an update with'),
231 | expect.any(String)
232 | );
233 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
234 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
235 | });
236 |
237 | it('should distinguish containing namespaces names', async () => {
238 | jest.spyOn(global.console, 'log');
239 | i18nMock.options = { backend: {}, ns: ['name-space'] };
240 | i18nMock.language = 'en';
241 |
242 | applyClientHMR(i18nMock);
243 |
244 | await whenHotTriggeredWith(['en/none-loaded-name-space']);
245 |
246 | expect(global.console.log).not.toHaveBeenCalledWith(
247 | expect.stringContaining('Got an update with'),
248 | expect.any(String)
249 | );
250 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
251 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
252 | });
253 |
254 | it('should support fallbackNS as optional ns', async () => {
255 | i18nMock.options = {
256 | backend: {},
257 | ns: ['nested/name-space'],
258 | fallbackNS: ['nested/fallback-name-space'],
259 | };
260 | i18nMock.language = 'en-US';
261 |
262 | applyClientHMR(i18nMock);
263 |
264 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
265 |
266 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
267 | ['en-US'],
268 | ['nested/fallback-name-space'],
269 | expect.any(Function)
270 | );
271 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
272 | });
273 |
274 | it('should support defaultNS as optional ns', async () => {
275 | i18nMock.options = {
276 | backend: {},
277 | ns: [],
278 | defaultNS: ['common'],
279 | };
280 | i18nMock.language = 'en-US';
281 |
282 | applyClientHMR(i18nMock);
283 |
284 | await whenHotTriggeredWith(['common/en-US']);
285 |
286 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
287 | ['en-US'],
288 | ['common'],
289 | expect.any(Function)
290 | );
291 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
292 | });
293 |
294 | it('should support complex localePath {{ns}}/locales/{{lng}}.json', async () => {
295 | i18nMock.options = { backend: {}, ns: ['nested/name-space'] };
296 | i18nMock.language = 'en-US';
297 |
298 | applyClientHMR(i18nMock);
299 |
300 | await whenHotTriggeredWith(['nested/name-space/locales/en-US']);
301 |
302 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
303 | ['en-US'],
304 | ['nested/name-space'],
305 | expect.any(Function)
306 | );
307 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
308 | });
309 |
310 | it('should support options.supportedLngs as a language source', async () => {
311 | i18nMock.options = {
312 | backend: {},
313 | ns: ['nested/name-space'],
314 | fallbackNS: ['nested/fallback-name-space'],
315 | supportedLngs: ['en-US'],
316 | };
317 | i18nMock.language = 'en-US';
318 | i18nMock.languages = [];
319 |
320 | applyClientHMR(i18nMock);
321 |
322 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
323 |
324 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
325 | ['en-US'],
326 | ['nested/fallback-name-space'],
327 | expect.any(Function)
328 | );
329 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
330 | });
331 |
332 | it('should support options.lng as a language source', async () => {
333 | i18nMock.options = {
334 | backend: {},
335 | ns: ['nested/name-space'],
336 | fallbackNS: ['nested/fallback-name-space'],
337 | lng: 'en-US',
338 | };
339 | i18nMock.language = 'en-US';
340 | i18nMock.languages = [];
341 |
342 | applyClientHMR(i18nMock);
343 |
344 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
345 |
346 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
347 | ['en-US'],
348 | ['nested/fallback-name-space'],
349 | expect.any(Function)
350 | );
351 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
352 | });
353 |
354 | it('should support options.fallbackLng as a language source', async () => {
355 | i18nMock.options = {
356 | backend: {},
357 | ns: ['nested/name-space'],
358 | fallbackNS: ['nested/fallback-name-space'],
359 | fallbackLng: 'en-US',
360 | };
361 | i18nMock.language = 'en-US';
362 | i18nMock.languages = [];
363 |
364 | applyClientHMR(i18nMock);
365 |
366 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
367 |
368 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
369 | ['en-US'],
370 | ['nested/fallback-name-space'],
371 | expect.any(Function)
372 | );
373 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
374 | });
375 |
376 | describe('multiple files', () => {
377 | it('should support change of multiple files', async () => {
378 | i18nMock.options = { backend: {}, ns: ['name-space', 'name-space2'] };
379 | i18nMock.language = 'en';
380 |
381 | applyClientHMR(i18nMock);
382 |
383 | await whenHotTriggeredWith(['en/name-space', 'en/name-space2', 'de/name-space']);
384 |
385 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
386 | ['en', 'de'],
387 | ['name-space', 'name-space2'],
388 | expect.any(Function)
389 | );
390 | expect(i18nMock.changeLanguage).toHaveBeenCalled();
391 | });
392 |
393 | it('should not trigger `changeLanguage` when modified files are not related to the current language', async () => {
394 | i18nMock.options = { backend: {}, ns: ['name-space', 'name-space2'] };
395 | i18nMock.language = 'en';
396 |
397 | applyClientHMR(i18nMock);
398 |
399 | await whenHotTriggeredWith(['de/name-space', 'de/name-space2']);
400 |
401 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
402 | });
403 | });
404 | });
405 |
--------------------------------------------------------------------------------
/__tests__/webpack/client-hmr.spec.js:
--------------------------------------------------------------------------------
1 | let changedData = {};
2 | jest.mock('../../lib/webpack/trigger.js', () => {
3 | return changedData;
4 | });
5 |
6 | global.mockModule = {
7 | hot: {
8 | accept: jest.fn(),
9 | },
10 | };
11 |
12 | const applyClientHMR = require('../../lib/webpack/client-hmr');
13 |
14 | function whenHotTriggeredWith(changedFiles) {
15 | changedData.changedFiles = changedFiles;
16 |
17 | const acceptCallback = mockModule.hot.accept.mock.calls[0][1];
18 | return acceptCallback();
19 | }
20 |
21 | describe('Webpack - client-hmr', () => {
22 | let i18nMock;
23 | let reloadError;
24 |
25 | beforeEach(() => {
26 | reloadError = undefined;
27 |
28 | i18nMock = {
29 | reloadResources: jest.fn().mockImplementation((_lang, _ns, callbackFn) => {
30 | if (typeof callbackFn === 'function') {
31 | callbackFn(reloadError);
32 | }
33 | return Promise.resolve();
34 | }),
35 | changeLanguage: jest.fn(),
36 | languages: ['en', 'de', 'en-US'],
37 | };
38 |
39 | mockModule.hot.accept.mockReset();
40 | });
41 |
42 | afterEach(() => {
43 | jest.restoreAllMocks();
44 | });
45 |
46 | it('should warn regarding missing backend options once', () => {
47 | jest.spyOn(global.console, 'warn');
48 | i18nMock.options = { ns: ['name-space'] };
49 |
50 | applyClientHMR(i18nMock);
51 | whenHotTriggeredWith(['en/name-space']);
52 |
53 | expect(global.console.warn).toHaveBeenCalledTimes(1);
54 | expect(global.console.warn).toHaveBeenCalledWith(
55 | expect.stringContaining('i18next-http-backend not found'),
56 | expect.any(String),
57 | expect.any(String)
58 | );
59 | });
60 |
61 | it('should use backendConnector options from services as cache killer param', () => {
62 | i18nMock.services = {
63 | ...i18nMock.services,
64 | backendConnector: { backend: { options: {} } },
65 | };
66 | i18nMock.language = 'en';
67 | i18nMock.options = { ns: ['name-space'] };
68 |
69 | applyClientHMR(i18nMock);
70 |
71 | whenHotTriggeredWith(['en/name-space']);
72 |
73 | expect(i18nMock.services.backendConnector.backend.options).toHaveProperty('queryStringParams', {
74 | _: expect.any(Number),
75 | });
76 | });
77 |
78 | it('should trigger reload when translation file changed', async () => {
79 | i18nMock.options = { backend: {}, ns: ['name-space'] };
80 | i18nMock.language = 'en';
81 |
82 | applyClientHMR(i18nMock);
83 |
84 | await whenHotTriggeredWith(['en/name-space']);
85 |
86 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
87 | ['en'],
88 | ['name-space'],
89 | expect.any(Function)
90 | );
91 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
92 | });
93 |
94 | it('should trigger reload when i18n given as a getter function', async () => {
95 | i18nMock.options = { backend: {}, ns: ['name-space'] };
96 | i18nMock.language = 'en';
97 |
98 | applyClientHMR(() => i18nMock);
99 |
100 | await whenHotTriggeredWith(['en/name-space']);
101 |
102 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
103 | ['en'],
104 | ['name-space'],
105 | expect.any(Function)
106 | );
107 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
108 | });
109 |
110 | it('should pass changed filed to the i18next getter', () => {
111 | i18nMock.options = { backend: {}, ns: ['name-space'] };
112 | i18nMock.language = 'en';
113 | const getter = jest.fn().mockImplementation(() => i18nMock);
114 | const changedFiles = ['en/name-space'];
115 |
116 | applyClientHMR(getter);
117 | whenHotTriggeredWith(changedFiles);
118 |
119 | expect(getter).toHaveBeenCalledWith({ changedFiles });
120 | });
121 |
122 | it('should trigger reload when lng-country combination file changed', () => {
123 | i18nMock.options = { backend: {}, ns: ['name-space'] };
124 | i18nMock.language = 'en-US';
125 |
126 | applyClientHMR(i18nMock);
127 |
128 | whenHotTriggeredWith(['en-US/name-space']);
129 |
130 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
131 | ['en-US'],
132 | ['name-space'],
133 | expect.any(Function)
134 | );
135 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
136 | });
137 |
138 | it('should trigger reload when translation file changed with nested namespace', async () => {
139 | i18nMock.options = { backend: {}, ns: ['name-space', 'nested/name-space'] };
140 | i18nMock.language = 'en';
141 |
142 | applyClientHMR(i18nMock);
143 |
144 | await whenHotTriggeredWith(['en/nested/name-space']);
145 |
146 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
147 | ['en'],
148 | ['nested/name-space'],
149 | expect.any(Function)
150 | );
151 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
152 | });
153 |
154 | it('should trigger reload when translation file with backslashes (windows)', async () => {
155 | i18nMock.options = { backend: {}, ns: ['name-space', 'nested/name-space'] };
156 | i18nMock.language = 'en';
157 |
158 | applyClientHMR(i18nMock);
159 |
160 | await whenHotTriggeredWith(['en\\nested\\name-space']);
161 |
162 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
163 | ['en'],
164 | ['nested/name-space'],
165 | expect.any(Function)
166 | );
167 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en');
168 | });
169 |
170 | it('should not trigger changeLanguage when current lang is not the one that was edited', async () => {
171 | i18nMock.options = { backend: {}, ns: ['name-space'] };
172 | i18nMock.language = 'en';
173 | i18nMock.languages.push('otherLang');
174 |
175 | applyClientHMR(i18nMock);
176 |
177 | await whenHotTriggeredWith(['otherLang/name-space']);
178 |
179 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
180 | ['otherLang'],
181 | ['name-space'],
182 | expect.any(Function)
183 | );
184 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
185 | });
186 |
187 | it('should notify that reload resource failed', async () => {
188 | jest.spyOn(global.console, 'error');
189 | i18nMock.options = { backend: {}, ns: ['name-space'] };
190 | i18nMock.language = 'en';
191 | reloadError = 'reload failed';
192 |
193 | applyClientHMR(i18nMock);
194 | await whenHotTriggeredWith(['en/name-space']);
195 |
196 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
197 | expect(global.console.error).toHaveBeenCalledWith(
198 | expect.stringContaining(reloadError),
199 | expect.any(String),
200 | expect.any(String)
201 | );
202 | });
203 |
204 | it('should ignore changes of none loaded namespace', async () => {
205 | jest.spyOn(global.console, 'log');
206 | i18nMock.options = { backend: {}, ns: ['name-space'] };
207 | i18nMock.language = 'en';
208 |
209 | applyClientHMR(i18nMock);
210 |
211 | await whenHotTriggeredWith(['en/none-loaded-ns']);
212 |
213 | expect(global.console.log).not.toHaveBeenCalledWith(
214 | expect.stringContaining('Got an update with'),
215 | expect.any(String)
216 | );
217 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
218 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
219 | });
220 |
221 | it('should distinguish containing namespaces names', async () => {
222 | jest.spyOn(global.console, 'log');
223 | i18nMock.options = { backend: {}, ns: ['name-space'] };
224 | i18nMock.language = 'en';
225 |
226 | applyClientHMR(i18nMock);
227 |
228 | await whenHotTriggeredWith(['en/none-loaded-name-space']);
229 |
230 | expect(global.console.log).not.toHaveBeenCalledWith(
231 | expect.stringContaining('Got an update with'),
232 | expect.any(String)
233 | );
234 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
235 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
236 | });
237 |
238 | it('should support fallbackNS as optional ns', async () => {
239 | i18nMock.options = {
240 | backend: {},
241 | ns: ['nested/name-space'],
242 | fallbackNS: ['nested/fallback-name-space'],
243 | };
244 | i18nMock.language = 'en-US';
245 |
246 | applyClientHMR(i18nMock);
247 |
248 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
249 |
250 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
251 | ['en-US'],
252 | ['nested/fallback-name-space'],
253 | expect.any(Function)
254 | );
255 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
256 | });
257 |
258 | it('should support defaultNS as optional ns', async () => {
259 | i18nMock.options = {
260 | backend: {},
261 | ns: [],
262 | defaultNS: ['common'],
263 | };
264 | i18nMock.language = 'en-US';
265 |
266 | applyClientHMR(i18nMock);
267 |
268 | await whenHotTriggeredWith(['common/en-US']);
269 |
270 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
271 | ['en-US'],
272 | ['common'],
273 | expect.any(Function)
274 | );
275 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
276 | });
277 |
278 | it('should support complex localePath {{ns}}/locales/{{lng}}.json', async () => {
279 | i18nMock.options = { backend: {}, ns: ['nested/name-space'] };
280 | i18nMock.language = 'en-US';
281 |
282 | applyClientHMR(i18nMock);
283 |
284 | await whenHotTriggeredWith(['nested/name-space/locales/en-US']);
285 |
286 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
287 | ['en-US'],
288 | ['nested/name-space'],
289 | expect.any(Function)
290 | );
291 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
292 | });
293 |
294 | it('should support options.supportedLngs as a language source', async () => {
295 | i18nMock.options = {
296 | backend: {},
297 | ns: ['nested/name-space'],
298 | fallbackNS: ['nested/fallback-name-space'],
299 | supportedLngs: ['en-US'],
300 | };
301 | i18nMock.language = 'en-US';
302 | i18nMock.languages = [];
303 |
304 | applyClientHMR(i18nMock);
305 |
306 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
307 |
308 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
309 | ['en-US'],
310 | ['nested/fallback-name-space'],
311 | expect.any(Function)
312 | );
313 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
314 | });
315 |
316 | it('should support options.lng as a language source', async () => {
317 | i18nMock.options = {
318 | backend: {},
319 | ns: ['nested/name-space'],
320 | fallbackNS: ['nested/fallback-name-space'],
321 | lng: 'en-US',
322 | };
323 | i18nMock.language = 'en-US';
324 | i18nMock.languages = [];
325 |
326 | applyClientHMR(i18nMock);
327 |
328 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
329 |
330 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
331 | ['en-US'],
332 | ['nested/fallback-name-space'],
333 | expect.any(Function)
334 | );
335 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
336 | });
337 |
338 | it('should support options.fallbackLng as a language source', async () => {
339 | i18nMock.options = {
340 | backend: {},
341 | ns: ['nested/name-space'],
342 | fallbackNS: ['nested/fallback-name-space'],
343 | fallbackLng: 'en-US',
344 | };
345 | i18nMock.language = 'en-US';
346 | i18nMock.languages = [];
347 |
348 | applyClientHMR(i18nMock);
349 |
350 | await whenHotTriggeredWith(['nested/fallback-name-space/en-US']);
351 |
352 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
353 | ['en-US'],
354 | ['nested/fallback-name-space'],
355 | expect.any(Function)
356 | );
357 | expect(i18nMock.changeLanguage).toHaveBeenCalledWith('en-US');
358 | });
359 |
360 | describe('multiple files', () => {
361 | it('should support change of multiple files', async () => {
362 | i18nMock.options = { backend: {}, ns: ['name-space', 'name-space2'] };
363 | i18nMock.language = 'en';
364 |
365 | applyClientHMR(i18nMock);
366 |
367 | await whenHotTriggeredWith(['en/name-space', 'en/name-space2', 'de/name-space']);
368 |
369 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
370 | ['en', 'de'],
371 | ['name-space', 'name-space2'],
372 | expect.any(Function)
373 | );
374 | expect(i18nMock.changeLanguage).toHaveBeenCalled();
375 | });
376 |
377 | it('should not trigger `changeLanguage` when modified files are not related to the current language', async () => {
378 | i18nMock.options = { backend: {}, ns: ['name-space', 'name-space2'] };
379 | i18nMock.language = 'en';
380 |
381 | applyClientHMR(i18nMock);
382 |
383 | await whenHotTriggeredWith(['de/name-space', 'de/name-space2']);
384 |
385 | expect(i18nMock.changeLanguage).not.toHaveBeenCalled();
386 | });
387 | });
388 | });
389 |
--------------------------------------------------------------------------------
/__tests__/webpack/loader.spec.js:
--------------------------------------------------------------------------------
1 | const loader = require('../../lib/webpack/loader');
2 |
3 | describe('Webpack - loader', () => {
4 | let context;
5 | const options = ['en/namespace'];
6 | const query = {
7 | getChangedLang: jest.fn().mockImplementation(() => options),
8 | localesDirs: ['locals-dir'],
9 | };
10 | const content = `module.exports = '__PLACEHOLDER__';`;
11 |
12 | beforeEach(() => {
13 | context = {
14 | addContextDependency: jest.fn(),
15 | query,
16 | };
17 | });
18 |
19 | it('should add localesDir as context dependency', () => {
20 | loader.apply(context, [content]);
21 | expect(context.addContextDependency).toHaveBeenCalledWith(query.localesDirs[0]);
22 | });
23 |
24 | it('should add all locale dirs as context dependency', () => {
25 | query.localesDirs = ['folder1', 'folder2'];
26 | loader.apply(context, [content]);
27 |
28 | expect(context.addContextDependency).toHaveBeenCalledTimes(query.localesDirs.length);
29 | expect(context.addContextDependency).toHaveBeenCalledWith(query.localesDirs[0]);
30 | expect(context.addContextDependency).toHaveBeenCalledWith(query.localesDirs[1]);
31 | });
32 |
33 | it('should inject an object', () => {
34 | expect(loader.apply(context, [content])).toContain(JSON.stringify(options));
35 | });
36 |
37 | it('should invalidate content hash', async () => {
38 | const firstCall = loader.apply(context, [content]);
39 | await new Promise((resolve) => setTimeout(() => resolve(), 10));
40 | const secondCall = loader.apply(context, [content]);
41 | expect(firstCall).not.toEqual(secondCall);
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/__tests__/webpack/server-hmr.spec.js:
--------------------------------------------------------------------------------
1 | const changedData = {};
2 |
3 | jest.mock('../../lib/webpack/trigger.js', () => {
4 | return changedData;
5 | });
6 | const applyServerHMR = require('../../lib/webpack/server-hmr');
7 | const plugin = require('../../lib/webpack/plugin').I18NextHMRPlugin;
8 |
9 | function whenNativeHMRTriggeredWith(changedFiles) {
10 | changedData.changedFiles = changedFiles;
11 |
12 | const acceptCallback = mockModule.hot.accept.mock.calls[0][1];
13 | return acceptCallback();
14 | }
15 |
16 | describe('Webpack - server-hmr', () => {
17 | let i18nMock;
18 | let reloadError;
19 |
20 | beforeEach(() => {
21 | reloadError = undefined;
22 |
23 | i18nMock = {
24 | reloadResources: jest.fn().mockImplementation((_lang, _ns, callbackFn) => {
25 | if (typeof callbackFn === 'function') {
26 | callbackFn(reloadError);
27 | }
28 | return Promise.resolve();
29 | }),
30 | options: { ns: ['name-space', 'nested/name-space'] },
31 | languages: ['en', 'de'],
32 | };
33 | jest.spyOn(plugin, 'addListener');
34 | });
35 |
36 | afterEach(() => {
37 | jest.restoreAllMocks();
38 | });
39 |
40 | describe('with native HMR', () => {
41 | beforeEach(() => {
42 | global.mockModule = {
43 | hot: {
44 | accept: jest.fn(),
45 | },
46 | };
47 |
48 | applyServerHMR(i18nMock);
49 | });
50 |
51 | it('should accept hmr', () => {
52 | expect(global.mockModule.hot.accept).toHaveBeenCalledWith(
53 | './trigger.js',
54 | expect.any(Function)
55 | );
56 | });
57 |
58 | it('should notify that server HMR started HMR mode once', async () => {
59 | jest.spyOn(global.console, 'log');
60 |
61 | applyServerHMR(i18nMock); // second call
62 |
63 | expect(global.console.log).toHaveBeenCalledTimes(1);
64 | expect(global.console.log).toHaveBeenCalledWith(
65 | expect.stringContaining('Server HMR has started')
66 | );
67 | expect(global.console.log).not.toHaveBeenCalledWith(expect.stringContaining('callback mode'));
68 | });
69 |
70 | it('should reload resources on updated lang, ns', () => {
71 | const update = { lang: 'en', ns: 'name-space' };
72 | whenNativeHMRTriggeredWith([`${update.lang}/${update.ns}`]);
73 |
74 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
75 | [update.lang],
76 | [update.ns],
77 | expect.any(Function)
78 | );
79 | });
80 |
81 | it('should reload resources when nested namespace is updated', () => {
82 | const update = { lang: 'en', ns: 'nested/name-space' };
83 | whenNativeHMRTriggeredWith([`${update.lang}/${update.ns}`]);
84 |
85 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
86 | [update.lang],
87 | [update.ns],
88 | expect.any(Function)
89 | );
90 | });
91 |
92 | it('should reload resources when changed file based on back slashes (windows)', () => {
93 | const update = { lang: 'en', ns: 'nested/name-space' };
94 | whenNativeHMRTriggeredWith([`${update.lang}\\${update.ns.replace('/', '\\')}`]);
95 |
96 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
97 | [update.lang],
98 | [update.ns],
99 | expect.any(Function)
100 | );
101 | });
102 |
103 | it('should ignore changes of none loaded namespace', async () => {
104 | jest.spyOn(global.console, 'log');
105 | i18nMock.options = { backend: {}, ns: ['name-space'] };
106 | i18nMock.language = 'en';
107 |
108 | await whenNativeHMRTriggeredWith(['en/none-loaded-ns']);
109 |
110 | expect(global.console.log).not.toHaveBeenCalledWith(
111 | expect.stringContaining('Got an update with')
112 | );
113 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
114 | });
115 |
116 | it('should distinguish containing namespaces names', async () => {
117 | jest.spyOn(global.console, 'log');
118 | i18nMock.options = { backend: {}, ns: ['name-space'] };
119 | i18nMock.language = 'en';
120 |
121 | await whenNativeHMRTriggeredWith(['en/none-loaded-name-space']);
122 |
123 | expect(global.console.log).not.toHaveBeenCalledWith(
124 | expect.stringContaining('Got an update with')
125 | );
126 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
127 | });
128 |
129 | it('should support fallbackNS', async () => {
130 | const update = { lang: 'en', ns: 'nested/fallback-name-space' };
131 | i18nMock.options.fallbackNS = update.ns;
132 | whenNativeHMRTriggeredWith([`${update.lang}/${update.ns}`]);
133 |
134 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
135 | [update.lang],
136 | [update.ns],
137 | expect.any(Function)
138 | );
139 | });
140 |
141 | it('should support defaultNS as source of ns', async () => {
142 | const update = { lang: 'en', ns: 'nested/fallback-name-space' };
143 | i18nMock.options.ns = [];
144 | i18nMock.options.defaultNS = update.ns;
145 | whenNativeHMRTriggeredWith([`${update.lang}/${update.ns}`]);
146 |
147 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
148 | [update.lang],
149 | [update.ns],
150 | expect.any(Function)
151 | );
152 | });
153 |
154 | it('should notify on successful change', async () => {
155 | jest.spyOn(global.console, 'log');
156 |
157 | whenNativeHMRTriggeredWith(['en/name-space']);
158 |
159 | expect(global.console.log).toHaveBeenCalledWith(
160 | expect.stringContaining('Server reloaded locale')
161 | );
162 | });
163 |
164 | it('should notify when reload fails', async () => {
165 | reloadError = 'reload failed';
166 |
167 | jest.spyOn(global.console, 'log');
168 |
169 | whenNativeHMRTriggeredWith(['en/name-space']);
170 |
171 | expect(global.console.log).toHaveBeenCalledWith(expect.stringContaining(reloadError));
172 | });
173 |
174 | it('should support change of multiple files', () => {
175 | whenNativeHMRTriggeredWith([`en/name-space`, 'de/name-space']);
176 |
177 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
178 | ['en', 'de'],
179 | ['name-space'],
180 | expect.any(Function)
181 | );
182 | });
183 |
184 | it('should support complex localePath {{ns}}/locales/{{lng}}.json', async () => {
185 | i18nMock.options = { backend: {}, ns: ['nested/name-space'] };
186 | i18nMock.language = 'en-US';
187 | i18nMock.languages.push(i18nMock.language);
188 |
189 | await whenNativeHMRTriggeredWith(['nested/name-space/locales/en-US']);
190 |
191 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
192 | ['en-US'],
193 | ['nested/name-space'],
194 | expect.any(Function)
195 | );
196 | });
197 |
198 | it('should support options.supportedLngs as a language source', async () => {
199 | i18nMock.language = 'en-US';
200 | i18nMock.options = {
201 | backend: {},
202 | ns: ['nested/name-space'],
203 | supportedLngs: [i18nMock.language],
204 | };
205 |
206 | await whenNativeHMRTriggeredWith(['nested/name-space/locales/en-US']);
207 |
208 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
209 | ['en-US'],
210 | ['nested/name-space'],
211 | expect.any(Function)
212 | );
213 | });
214 |
215 | it('should support options.lng as a language source', async () => {
216 | i18nMock.language = 'en-US';
217 | i18nMock.options = { backend: {}, ns: ['nested/name-space'], lng: i18nMock.language };
218 |
219 | await whenNativeHMRTriggeredWith(['nested/name-space/locales/en-US']);
220 |
221 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
222 | ['en-US'],
223 | ['nested/name-space'],
224 | expect.any(Function)
225 | );
226 | });
227 |
228 | it('should support options.fallbackLng as a language source', async () => {
229 | i18nMock.language = 'en-US';
230 | i18nMock.options = { backend: {}, ns: ['nested/name-space'], fallbackLng: i18nMock.language };
231 |
232 | await whenNativeHMRTriggeredWith(['nested/name-space/locales/en-US']);
233 |
234 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
235 | ['en-US'],
236 | ['nested/name-space'],
237 | expect.any(Function)
238 | );
239 | });
240 | });
241 |
242 | describe('without native HMR', () => {
243 | beforeEach(() => {
244 | global.mockModule = {};
245 | applyServerHMR(i18nMock);
246 | });
247 |
248 | it('should register a listener on webpack plugin', () => {
249 | expect(plugin.addListener).toHaveBeenCalled();
250 | });
251 |
252 | it('should notify that server HMR started as callback mode once', async () => {
253 | jest.spyOn(global.console, 'log');
254 |
255 | applyServerHMR(i18nMock); // second call
256 |
257 | expect(global.console.log).toHaveBeenCalledTimes(1);
258 | expect(global.console.log).toHaveBeenCalledWith(
259 | expect.stringContaining('Server HMR has started - callback mode')
260 | );
261 | });
262 |
263 | it('should reload resources on updated lang, ns', () => {
264 | const update = { lang: 'en', ns: 'name-space' };
265 | plugin.callbacks[0]({ changedFiles: [`${update.lang}/${update.ns}`] });
266 |
267 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
268 | [update.lang],
269 | [update.ns],
270 | expect.any(Function)
271 | );
272 | });
273 |
274 | it('should reload resources when nested namespace is updated', () => {
275 | const update = { lang: 'en', ns: 'nested/name-space' };
276 | plugin.callbacks[0]({ changedFiles: [`${update.lang}/${update.ns}`] });
277 |
278 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
279 | [update.lang],
280 | [update.ns],
281 | expect.any(Function)
282 | );
283 | });
284 |
285 | it('should reload resources when changed file based on back slashes (windows)', () => {
286 | const update = { lang: 'en', ns: 'nested/name-space' };
287 | plugin.callbacks[0]({ changedFiles: [`${update.lang}\\${update.ns.replace('/', '\\')}`] });
288 |
289 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
290 | [update.lang],
291 | [update.ns],
292 | expect.any(Function)
293 | );
294 | });
295 |
296 | it('should support fallbackNS', async () => {
297 | const update = { lang: 'en', ns: 'nested/fallback-name-space' };
298 | i18nMock.options.fallbackNS = update.ns;
299 | plugin.callbacks[0]({ changedFiles: [`${update.lang}/${update.ns}`] });
300 |
301 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
302 | [update.lang],
303 | [update.ns],
304 | expect.any(Function)
305 | );
306 | });
307 |
308 | it('should support defaultNS as source of ns', async () => {
309 | const update = { lang: 'en', ns: 'nested/fallback-name-space' };
310 | i18nMock.options.ns = [];
311 | i18nMock.options.defaultNS = update.ns;
312 | plugin.callbacks[0]({ changedFiles: [`${update.lang}/${update.ns}`] });
313 |
314 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
315 | [update.lang],
316 | [update.ns],
317 | expect.any(Function)
318 | );
319 | });
320 |
321 | it('should ignore changes of none loaded namespace', async () => {
322 | jest.spyOn(global.console, 'log');
323 | i18nMock.options = { backend: {}, ns: ['name-space'] };
324 | i18nMock.language = 'en';
325 |
326 | plugin.callbacks[0]({ changedFiles: ['en/none-loaded-ns'] });
327 |
328 | expect(global.console.log).not.toHaveBeenCalledWith(
329 | expect.stringContaining('Got an update with')
330 | );
331 | expect(i18nMock.reloadResources).not.toHaveBeenCalled();
332 | });
333 |
334 | it('should notify on successful change', async () => {
335 | jest.spyOn(global.console, 'log');
336 |
337 | await plugin.callbacks[0]({ changedFiles: ['en/name-space'] });
338 |
339 | expect(global.console.log).toHaveBeenCalledWith(
340 | expect.stringContaining('Server reloaded locale')
341 | );
342 | });
343 |
344 | it('should notify when reload fails', async () => {
345 | reloadError = 'reload failed';
346 |
347 | jest.spyOn(global.console, 'log');
348 |
349 | await plugin.callbacks[0]({ changedFiles: ['en/name-space'] });
350 |
351 | expect(global.console.log).toHaveBeenCalledWith(expect.stringContaining(reloadError));
352 | });
353 |
354 | it('should support change of multiple files', () => {
355 | plugin.callbacks[0]({ changedFiles: [`en/name-space`, 'de/name-space'] });
356 |
357 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
358 | ['en', 'de'],
359 | ['name-space'],
360 | expect.any(Function)
361 | );
362 | });
363 |
364 | it('should support complex localePath {{ns}}/locales/{{lng}}.json', async () => {
365 | i18nMock.options = { backend: {}, ns: ['nested/name-space'] };
366 | i18nMock.language = 'en-US';
367 | i18nMock.languages.push(i18nMock.language);
368 |
369 | plugin.callbacks[0]({ changedFiles: ['nested/name-space/locales/en-US'] });
370 |
371 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
372 | ['en-US'],
373 | ['nested/name-space'],
374 | expect.any(Function)
375 | );
376 | });
377 |
378 | it('should support options.supportedLngs as a language source', async () => {
379 | i18nMock.language = 'en-US';
380 | i18nMock.options = {
381 | backend: {},
382 | ns: ['nested/name-space'],
383 | supportedLngs: [i18nMock.language],
384 | };
385 |
386 | plugin.callbacks[0]({ changedFiles: ['nested/name-space/locales/en-US'] });
387 |
388 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
389 | ['en-US'],
390 | ['nested/name-space'],
391 | expect.any(Function)
392 | );
393 | });
394 |
395 | it('should support options.lng as a language source', async () => {
396 | i18nMock.language = 'en-US';
397 | i18nMock.options = { backend: {}, ns: ['nested/name-space'], lng: i18nMock.language };
398 |
399 | plugin.callbacks[0]({ changedFiles: ['nested/name-space/locales/en-US'] });
400 |
401 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
402 | ['en-US'],
403 | ['nested/name-space'],
404 | expect.any(Function)
405 | );
406 | });
407 |
408 | it('should support options.fallbackLng as a language source', async () => {
409 | i18nMock.language = 'en-US';
410 | i18nMock.options = { backend: {}, ns: ['nested/name-space'], fallbackLng: i18nMock.language };
411 |
412 | plugin.callbacks[0]({ changedFiles: ['nested/name-space/locales/en-US'] });
413 |
414 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
415 | ['en-US'],
416 | ['nested/name-space'],
417 | expect.any(Function)
418 | );
419 | });
420 | });
421 |
422 | describe('i18n as a getter', () => {
423 | beforeEach(() => {
424 | global.mockModule = {
425 | hot: {
426 | accept: jest.fn(),
427 | },
428 | };
429 | });
430 |
431 | it('should reload resources on updated lang, ns', () => {
432 | const update = { lang: 'en', ns: 'name-space' };
433 | applyServerHMR(() => i18nMock);
434 |
435 | whenNativeHMRTriggeredWith([`${update.lang}/${update.ns}`]);
436 |
437 | expect(i18nMock.reloadResources).toHaveBeenCalledWith(
438 | [update.lang],
439 | [update.ns],
440 | expect.any(Function)
441 | );
442 | });
443 |
444 | it('should pass changed filed to the i18next getter', () => {
445 | const update = { lang: 'en', ns: 'name-space' };
446 |
447 | const getter = jest.fn().mockImplementation(() => i18nMock);
448 | const changedFiles = [`${update.lang}/${update.ns}`];
449 | applyServerHMR(getter);
450 |
451 | whenNativeHMRTriggeredWith(changedFiles);
452 |
453 | expect(getter).toHaveBeenCalledWith({ changedFiles });
454 | });
455 |
456 | it('should not fail when getter returns a none i18next instance', () => {
457 | const update = { lang: 'en', ns: 'name-space' };
458 |
459 | const getter = jest.fn().mockImplementation(() => null);
460 | const changedFiles = [`${update.lang}/${update.ns}`];
461 | applyServerHMR(getter);
462 |
463 | expect(() => whenNativeHMRTriggeredWith(changedFiles)).not.toThrow();
464 | });
465 | });
466 | });
467 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/components/Footer.js:
--------------------------------------------------------------------------------
1 | import pkg from 'next-i18next/package.json'
2 | import { useTranslation } from 'next-i18next'
3 |
4 | export const Footer = () => {
5 |
6 | const { t } = useTranslation('footer')
7 |
8 | return (
9 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/components/Header.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 |
3 | export const Header = ({ heading, title }) => (
4 | <>
5 |
6 | {title}
7 |
8 |
9 | next-i18next
10 |
11 |
12 |
13 | {heading}
14 |
15 |
19 |
20 |
21 | >
22 | )
23 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/next-i18next.config.js:
--------------------------------------------------------------------------------
1 | const HttpBackend = require('i18next-http-backend/cjs');
2 | const HMRPlugin = process.env.NODE_ENV !== 'production' ? require('i18next-hmr/plugin').HMRPlugin : undefined;
3 |
4 | module.exports = {
5 | // https://www.i18next.com/overview/configuration-options#logging
6 | i18n: {
7 | defaultLocale: 'en',
8 | locales: ['en', 'de'],
9 | },
10 | ...(typeof window !== 'undefined'
11 | ? {
12 | backend: {
13 | loadPath: '/locales/{{lng}}/{{ns}}.json',
14 | },
15 | }
16 | : {}),
17 | serializeConfig: false,
18 | // allows reloading translations on each page navigation / a hacky way to reload translations on the server at Next v13
19 | reloadOnPrerender: process.env.NODE_ENV === 'development',
20 | use:
21 | process.env.NODE_ENV !== 'production'
22 | ? typeof window !== 'undefined'
23 | ? [HttpBackend, new HMRPlugin({ webpack: { client: true } })]
24 | : [new HMRPlugin({ webpack: { server: true } })]
25 | : [],
26 | };
27 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/next.config.js:
--------------------------------------------------------------------------------
1 | const { i18n } = require('./next-i18next.config');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | i18n,
6 | webpack(config, { isServer }) {
7 | if (!isServer && config.mode === 'development') {
8 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
9 | config.plugins.push(
10 | new I18NextHMRPlugin({
11 | localesDir: path.resolve(__dirname, 'public/locales'),
12 | })
13 | );
14 | }
15 |
16 | return config;
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "v13",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "next",
8 | "build": "next build",
9 | "start": "next start -p ${PORT:=3000}"
10 | },
11 | "dependencies": {
12 | "i18next": "^22.5.0",
13 | "i18next-http-backend": "^1.4.1",
14 | "next": "^13",
15 | "next-i18next": "13.2.2",
16 | "prop-types": "15.8.1",
17 | "react": "18.2.0",
18 | "react-dom": "18.2.0",
19 | "react-i18next": "^12.3.1"
20 | },
21 | "devDependencies": {
22 | "i18next-hmr": "^3.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/pages/_app.js:
--------------------------------------------------------------------------------
1 | import { appWithTranslation } from 'next-i18next';
2 | import nextI18NextConfig from '../next-i18next.config';
3 |
4 | const MyApp = ({ Component, pageProps }) => ;
5 |
6 | // https://github.com/i18next/next-i18next#unserialisable-configs
7 | export default appWithTranslation(MyApp, nextI18NextConfig);
8 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Html, Head, Main, NextScript } from 'next/document';
2 | import i18nextConfig from '../next-i18next.config';
3 |
4 | class MyDocument extends Document {
5 | render() {
6 | const currentLocale = this.props.__NEXT_DATA__.locale || i18nextConfig.i18n.defaultLocale;
7 | return (
8 |
9 |
10 |
14 |
15 |
16 |
20 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | export default MyDocument;
40 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/pages/index.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { useRouter } from 'next/router';
3 |
4 | import { useTranslation, Trans } from 'next-i18next';
5 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
6 |
7 | import { Header } from '../components/Header';
8 | import { Footer } from '../components/Footer';
9 |
10 | const Homepage = () => {
11 | const router = useRouter();
12 | const { t } = useTranslation('common');
13 |
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 |
{t('blog.optimized.question')}
21 |
22 |
23 | Then you may have a look at{' '}
24 | this blog post.
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 |
{t('blog.ssg.question')}
36 |
37 |
38 | Then you may have a look at{' '}
39 | this blog post.
40 |
41 |
42 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | >
64 | );
65 | };
66 |
67 | export const getStaticProps = async ({ locale }) => ({
68 | props: {
69 | ...(await serverSideTranslations(locale, ['common', 'footer'])),
70 | },
71 | });
72 |
73 | export default Homepage;
74 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/pages/second-page.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | import { useTranslation } from 'next-i18next';
4 | import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
5 |
6 | import { Header } from '../components/Header';
7 | import { Footer } from '../components/Footer';
8 |
9 | const SecondPage = () => {
10 | const { t } = useTranslation('second-page');
11 |
12 | return (
13 | <>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | >
22 | );
23 | };
24 |
25 | export const getStaticProps = async ({ locale }) => ({
26 | props: {
27 | ...(await serverSideTranslations(locale, ['second-page', 'footer'])),
28 | },
29 | });
30 |
31 | export default SecondPage;
32 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/app.css:
--------------------------------------------------------------------------------
1 | #__next {
2 | font-family: 'Open Sans', sans-serif;
3 | text-align: center;
4 | background-image: linear-gradient(to left top, #ffffff, #f5f5f5, #eaeaea, #e0e0e0, #d6d6d6);
5 | display: flex;
6 | flex-direction: column;
7 | min-height: 100vh;
8 | min-width: 100vw;
9 | }
10 |
11 | h1,
12 | h2 {
13 | font-family: 'Oswald', sans-serif;
14 | }
15 |
16 | h1 {
17 | font-size: 3rem;
18 | margin: 5rem 0;
19 | }
20 | h2 {
21 | min-width: 18rem;
22 | font-size: 2rem;
23 | opacity: 0.3;
24 | }
25 | h3 {
26 | font-size: 1.5rem;
27 | opacity: 0.5;
28 | }
29 |
30 | p {
31 | line-height: 1.65em;
32 | }
33 | p:nth-child(2) {
34 | font-style: italic;
35 | opacity: 0.65;
36 | margin-top: 1rem;
37 | }
38 |
39 | a.github {
40 | position: fixed;
41 | top: 0.5rem;
42 | right: 0.75rem;
43 | font-size: 4rem;
44 | color: #888;
45 | opacity: 0.8;
46 | }
47 | a.github:hover {
48 | opacity: 1;
49 | }
50 |
51 | button {
52 | display: inline-block;
53 | vertical-align: bottom;
54 | outline: 0;
55 | text-decoration: none;
56 | cursor: pointer;
57 | background-color: rgba(255, 255, 255, 0.5);
58 | box-sizing: border-box;
59 | font-size: 1em;
60 | font-family: inherit;
61 | border-radius: 3px;
62 | transition: box-shadow .2s ease;
63 | user-select: none;
64 | line-height: 2.5em;
65 | min-height: 40px;
66 | padding: 0 .8em;
67 | border: 0;
68 | color: inherit;
69 | position: relative;
70 | transform: translateZ(0);
71 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26);
72 | margin: 0.8rem;
73 | }
74 |
75 | button:hover,
76 | button:focus {
77 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .4);
78 | }
79 |
80 | main {
81 | display: flex;
82 | flex-direction: column;
83 | flex: 1;
84 | justify-content: center;
85 | align-items: center;
86 | }
87 | footer {
88 | background-color: rgba(255, 255, 255, 0.5);
89 | width: 100vw;
90 | padding: 3rem 0;
91 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/locales/de/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "Ein einfaches Beispiel",
3 | "change-locale": "Sprache wechseln zu \"{{changeTo}}\"",
4 | "to-second-page": "Zur zweiten Seite",
5 | "error-with-status": "Auf dem Server ist ein Fehler ({{statusCode}}) aufgetreten",
6 | "error-without-status": "Auf dem Server ist ein Fehler aufgetreten",
7 | "title": "Hauptseite | next-i18next",
8 | "blog": {
9 | "optimized": {
10 | "question": "Möchtest du einige Superkräfte entfesseln, um für alle Seiten optimierte Übersetzungen zu haben?",
11 | "answer": "Dann schaue dir vielleicht <1>diesen Blogbeitrag1> an."
12 | },
13 | "ssg": {
14 | "question": "Möchtest du SSG (next export) verwenden?",
15 | "answer": "Dann schaue dir vielleicht <1>diesen Blogbeitrag1> an."
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/locales/de/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Dies ist eine Nicht-Seitenkomponente, die einen eigenen Namespace erfordert"
3 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/locales/de/second-page.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "Eine zweite Seite, um das Routing zu demonstrieren",
3 | "back-to-home": "Zurück zur Hauptseite",
4 | "title": "Zweite Seite | next-i18next"
5 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "A simple example",
3 | "change-locale": "Change locale to \"{{changeTo}}\"",
4 | "to-second-page": "To second page",
5 | "error-with-status": "A {{statusCode}} error occurred on server",
6 | "error-without-status": "An error occurred on the server",
7 | "title": "Home | next-i18next",
8 | "blog": {
9 | "optimized": {
10 | "question": "Do you like to unleash some super powers to have all side optimized translations?",
11 | "answer": "Then you may have a look at <1>this blog post1>."
12 | },
13 | "ssg": {
14 | "question": "Do you want to use SSG (next export)?",
15 | "answer": "Then you may have a look at <1>this blog post1>."
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/locales/en/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "This is a non-page component that requires its own namespace"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/public/locales/en/second-page.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "A second page, to demonstrate routing",
3 | "back-to-home": "Back to home",
4 | "title": "Second page | next-i18next"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next-v13/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.6":
6 | version "7.27.0"
7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
8 | integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
9 | dependencies:
10 | regenerator-runtime "^0.14.0"
11 |
12 | "@next/env@13.5.11":
13 | version "13.5.11"
14 | resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.11.tgz#6712d907e2682199aa1e8229b5ce028ee5a8001b"
15 | integrity sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==
16 |
17 | "@next/swc-darwin-arm64@13.5.9":
18 | version "13.5.9"
19 | resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.9.tgz#46c3a525039171ff1a83c813d7db86fb7808a9b2"
20 | integrity sha512-pVyd8/1y1l5atQRvOaLOvfbmRwefxLhqQOzYo/M7FQ5eaRwA1+wuCn7t39VwEgDd7Aw1+AIWwd+MURXUeXhwDw==
21 |
22 | "@next/swc-darwin-x64@13.5.9":
23 | version "13.5.9"
24 | resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.9.tgz#b690452e9a6ce839f8738e27e9fd1a8567dd7554"
25 | integrity sha512-DwdeJqP7v8wmoyTWPbPVodTwCybBZa02xjSJ6YQFIFZFZ7dFgrieKW4Eo0GoIcOJq5+JxkQyejmI+8zwDp3pwA==
26 |
27 | "@next/swc-linux-arm64-gnu@13.5.9":
28 | version "13.5.9"
29 | resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.9.tgz#c3e335e2da3ba932c0b2f571f0672d1aa7af33df"
30 | integrity sha512-wdQsKsIsGSNdFojvjW3Ozrh8Q00+GqL3wTaMjDkQxVtRbAqfFBtrLPO0IuWChVUP2UeuQcHpVeUvu0YgOP00+g==
31 |
32 | "@next/swc-linux-arm64-musl@13.5.9":
33 | version "13.5.9"
34 | resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.9.tgz#54600d4917bace2508725cc963eeeb3b6432889e"
35 | integrity sha512-6VpS+bodQqzOeCwGxoimlRoosiWlSc0C224I7SQWJZoyJuT1ChNCo+45QQH+/GtbR/s7nhaUqmiHdzZC9TXnXA==
36 |
37 | "@next/swc-linux-x64-gnu@13.5.9":
38 | version "13.5.9"
39 | resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.9.tgz#f869c2066f13ff2818140e0a145dfea1ea7c0333"
40 | integrity sha512-XxG3yj61WDd28NA8gFASIR+2viQaYZEFQagEodhI/R49gXWnYhiflTeeEmCn7Vgnxa/OfK81h1gvhUZ66lozpw==
41 |
42 | "@next/swc-linux-x64-musl@13.5.9":
43 | version "13.5.9"
44 | resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.9.tgz#09295ea60a42a1b22d927802d6e543d8a8bbb186"
45 | integrity sha512-/dnscWqfO3+U8asd+Fc6dwL2l9AZDl7eKtPNKW8mKLh4Y4wOpjJiamhe8Dx+D+Oq0GYVjuW0WwjIxYWVozt2bA==
46 |
47 | "@next/swc-win32-arm64-msvc@13.5.9":
48 | version "13.5.9"
49 | resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.9.tgz#f39e3513058d7af6e9f6b1f296bf071301217159"
50 | integrity sha512-T/iPnyurOK5a4HRUcxAlss8uzoEf5h9tkd+W2dSWAfzxv8WLKlUgbfk+DH43JY3Gc2xK5URLuXrxDZ2mGfk/jw==
51 |
52 | "@next/swc-win32-ia32-msvc@13.5.9":
53 | version "13.5.9"
54 | resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.9.tgz#d567f471e182efa4ea29f47f3030613dd3fc68b5"
55 | integrity sha512-BLiPKJomaPrTAb7ykjA0LPcuuNMLDVK177Z1xe0nAem33+9FIayU4k/OWrtSn9SAJW/U60+1hoey5z+KCHdRLQ==
56 |
57 | "@next/swc-win32-x64-msvc@13.5.9":
58 | version "13.5.9"
59 | resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.9.tgz#35c53bd6d33040ec0ce1dd613c59112aac06b235"
60 | integrity sha512-/72/dZfjXXNY/u+n8gqZDjI6rxKMpYsgBBYNZKWOQw0BpBF7WCnPflRy3ZtvQ2+IYI3ZH2bPyj7K+6a6wNk90Q==
61 |
62 | "@swc/helpers@0.5.2":
63 | version "0.5.2"
64 | resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
65 | integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
66 | dependencies:
67 | tslib "^2.4.0"
68 |
69 | "@types/hoist-non-react-statics@^3.3.1":
70 | version "3.3.1"
71 | resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
72 | integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
73 | dependencies:
74 | "@types/react" "*"
75 | hoist-non-react-statics "^3.3.0"
76 |
77 | "@types/prop-types@*":
78 | version "15.7.5"
79 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
80 | integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
81 |
82 | "@types/react@*":
83 | version "18.0.15"
84 | resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe"
85 | integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow==
86 | dependencies:
87 | "@types/prop-types" "*"
88 | "@types/scheduler" "*"
89 | csstype "^3.0.2"
90 |
91 | "@types/scheduler@*":
92 | version "0.16.2"
93 | resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
94 | integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
95 |
96 | busboy@1.6.0:
97 | version "1.6.0"
98 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
99 | integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
100 | dependencies:
101 | streamsearch "^1.1.0"
102 |
103 | caniuse-lite@^1.0.30001406:
104 | version "1.0.30001707"
105 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz#c5e104d199e6f4355a898fcd995a066c7eb9bf41"
106 | integrity sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==
107 |
108 | client-only@0.0.1:
109 | version "0.0.1"
110 | resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
111 | integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
112 |
113 | core-js@^3:
114 | version "3.23.5"
115 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.5.tgz#1f82b0de5eece800827a2f59d597509c67650475"
116 | integrity sha512-7Vh11tujtAZy82da4duVreQysIoO2EvVrur7y6IzZkH1IHPSekuDi8Vuw1+YKjkbfWLRD7Nc9ICQ/sIUDutcyg==
117 |
118 | cross-fetch@3.1.5:
119 | version "3.1.5"
120 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
121 | integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
122 | dependencies:
123 | node-fetch "2.6.7"
124 |
125 | csstype@^3.0.2:
126 | version "3.1.0"
127 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
128 | integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
129 |
130 | glob-to-regexp@^0.4.1:
131 | version "0.4.1"
132 | resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
133 | integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
134 |
135 | graceful-fs@^4.1.2:
136 | version "4.2.11"
137 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
138 | integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
139 |
140 | hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
141 | version "3.3.2"
142 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
143 | integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
144 | dependencies:
145 | react-is "^16.7.0"
146 |
147 | html-parse-stringify@^3.0.1:
148 | version "3.0.1"
149 | resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
150 | integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
151 | dependencies:
152 | void-elements "3.1.0"
153 |
154 | i18next-fs-backend@^2.1.1:
155 | version "2.1.2"
156 | resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-2.1.2.tgz#1431d30385eae5bcc8a5bb91e2276f8c74e37fcc"
157 | integrity sha512-y9vl8HC8b1ayqZELzKvaKgnphrxgbaGGSNQjPU0JoTVP1M3NI6C69SwiAAXi6xuF1FSySJG52EdQZdMUETlwRA==
158 |
159 | i18next-hmr@^3.0.0:
160 | version "3.1.3"
161 | resolved "https://registry.yarnpkg.com/i18next-hmr/-/i18next-hmr-3.1.3.tgz#5073673849412b2b4e7d34adeb97e42898e30ade"
162 | integrity sha512-zoM4B6toVk48rAMl0t9eV+ldEq9HIO9+bek8H1aGSLQZAjPSBQCUggkxdk0vQjEWSKLsssxZqZBAWS+Ow1rcsA==
163 |
164 | i18next-http-backend@^1.4.1:
165 | version "1.4.1"
166 | resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz#d8d308e7d8c5b89988446d0b83f469361e051bc0"
167 | integrity sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA==
168 | dependencies:
169 | cross-fetch "3.1.5"
170 |
171 | i18next@^22.5.0:
172 | version "22.5.0"
173 | resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.5.0.tgz#16d98eba7c748ab183a36505046b5b91f87e989b"
174 | integrity sha512-sqWuJFj+wJAKQP2qBQ+b7STzxZNUmnSxrehBCCj9vDOW9RDYPfqCaK1Hbh2frNYQuPziz6O2CGoJPwtzY3vAYA==
175 | dependencies:
176 | "@babel/runtime" "^7.20.6"
177 |
178 | "js-tokens@^3.0.0 || ^4.0.0":
179 | version "4.0.0"
180 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
181 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
182 |
183 | loose-envify@^1.1.0, loose-envify@^1.4.0:
184 | version "1.4.0"
185 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
186 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
187 | dependencies:
188 | js-tokens "^3.0.0 || ^4.0.0"
189 |
190 | nanoid@^3.3.6:
191 | version "3.3.11"
192 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
193 | integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
194 |
195 | next-i18next@13.2.2:
196 | version "13.2.2"
197 | resolved "https://registry.yarnpkg.com/next-i18next/-/next-i18next-13.2.2.tgz#9609546fab1d1d5f9b227e86c5ca23d0cbbbddb4"
198 | integrity sha512-t0WU6K+HJoq2nVQ0n6OiiEZja9GyMqtDSU74FmOafgk4ljns+iZ18bsNJiI8rOUXfFfkW96ea1N7D5kbMyT+PA==
199 | dependencies:
200 | "@babel/runtime" "^7.20.13"
201 | "@types/hoist-non-react-statics" "^3.3.1"
202 | core-js "^3"
203 | hoist-non-react-statics "^3.3.2"
204 | i18next-fs-backend "^2.1.1"
205 |
206 | next@^13:
207 | version "13.5.11"
208 | resolved "https://registry.yarnpkg.com/next/-/next-13.5.11.tgz#a021e849c5748fb6e2a4447585614e0c0e6c778d"
209 | integrity sha512-WUPJ6WbAX9tdC86kGTu92qkrRdgRqVrY++nwM+shmWQwmyxt4zhZfR59moXSI4N8GDYCBY3lIAqhzjDd4rTC8Q==
210 | dependencies:
211 | "@next/env" "13.5.11"
212 | "@swc/helpers" "0.5.2"
213 | busboy "1.6.0"
214 | caniuse-lite "^1.0.30001406"
215 | postcss "8.4.31"
216 | styled-jsx "5.1.1"
217 | watchpack "2.4.0"
218 | optionalDependencies:
219 | "@next/swc-darwin-arm64" "13.5.9"
220 | "@next/swc-darwin-x64" "13.5.9"
221 | "@next/swc-linux-arm64-gnu" "13.5.9"
222 | "@next/swc-linux-arm64-musl" "13.5.9"
223 | "@next/swc-linux-x64-gnu" "13.5.9"
224 | "@next/swc-linux-x64-musl" "13.5.9"
225 | "@next/swc-win32-arm64-msvc" "13.5.9"
226 | "@next/swc-win32-ia32-msvc" "13.5.9"
227 | "@next/swc-win32-x64-msvc" "13.5.9"
228 |
229 | node-fetch@2.6.7:
230 | version "2.6.7"
231 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
232 | integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
233 | dependencies:
234 | whatwg-url "^5.0.0"
235 |
236 | object-assign@^4.1.1:
237 | version "4.1.1"
238 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
239 | integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
240 |
241 | picocolors@^1.0.0:
242 | version "1.0.0"
243 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
244 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
245 |
246 | postcss@8.4.31:
247 | version "8.4.31"
248 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
249 | integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
250 | dependencies:
251 | nanoid "^3.3.6"
252 | picocolors "^1.0.0"
253 | source-map-js "^1.0.2"
254 |
255 | prop-types@15.8.1:
256 | version "15.8.1"
257 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
258 | integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
259 | dependencies:
260 | loose-envify "^1.4.0"
261 | object-assign "^4.1.1"
262 | react-is "^16.13.1"
263 |
264 | react-dom@18.2.0:
265 | version "18.2.0"
266 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
267 | integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
268 | dependencies:
269 | loose-envify "^1.1.0"
270 | scheduler "^0.23.0"
271 |
272 | react-i18next@^12.3.1:
273 | version "12.3.1"
274 | resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.3.1.tgz#30134a41a2a71c61dc69c6383504929aed1c99e7"
275 | integrity sha512-5v8E2XjZDFzK7K87eSwC7AJcAkcLt5xYZ4+yTPDAW1i7C93oOY1dnr4BaQM7un4Hm+GmghuiPvevWwlca5PwDA==
276 | dependencies:
277 | "@babel/runtime" "^7.20.6"
278 | html-parse-stringify "^3.0.1"
279 |
280 | react-is@^16.13.1, react-is@^16.7.0:
281 | version "16.13.1"
282 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
283 | integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
284 |
285 | react@18.2.0:
286 | version "18.2.0"
287 | resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
288 | integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
289 | dependencies:
290 | loose-envify "^1.1.0"
291 |
292 | regenerator-runtime@^0.14.0:
293 | version "0.14.1"
294 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
295 | integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
296 |
297 | scheduler@^0.23.0:
298 | version "0.23.0"
299 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
300 | integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
301 | dependencies:
302 | loose-envify "^1.1.0"
303 |
304 | source-map-js@^1.0.2:
305 | version "1.2.1"
306 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
307 | integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
308 |
309 | streamsearch@^1.1.0:
310 | version "1.1.0"
311 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
312 | integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
313 |
314 | styled-jsx@5.1.1:
315 | version "5.1.1"
316 | resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
317 | integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
318 | dependencies:
319 | client-only "0.0.1"
320 |
321 | tr46@~0.0.3:
322 | version "0.0.3"
323 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
324 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
325 |
326 | tslib@^2.4.0:
327 | version "2.4.0"
328 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
329 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
330 |
331 | void-elements@3.1.0:
332 | version "3.1.0"
333 | resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
334 | integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
335 |
336 | watchpack@2.4.0:
337 | version "2.4.0"
338 | resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
339 | integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
340 | dependencies:
341 | glob-to-regexp "^0.4.1"
342 | graceful-fs "^4.1.2"
343 |
344 | webidl-conversions@^3.0.0:
345 | version "3.0.1"
346 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
347 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
348 |
349 | whatwg-url@^5.0.0:
350 | version "5.0.0"
351 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
352 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
353 | dependencies:
354 | tr46 "~0.0.3"
355 | webidl-conversions "^3.0.0"
356 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules
3 |
4 | # Dist
5 | dist
6 | .next
7 |
8 | # OS
9 | .DS_Store
10 |
11 | # Development Environment
12 | .vscode
13 | .idea
14 |
15 | # Logs
16 | runtime.log
17 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/README.md:
--------------------------------------------------------------------------------
1 | # Simple example: next-i18next
2 |
3 | ## What is this?
4 |
5 | This subdirectory is a simple example of how to use [next-i18next](https://github.com/isaachinman/next-i18next) with [NextJs](https://github.com/zeit/next.js) to get translations up and running quickly and easily, while fully supporting SSR, multiple namespaces with codesplitting, etc.
6 |
7 | ## For more info...
8 |
9 | You may have arrived here from the [NextJs](https://github.com/zeit/next.js) repository, or the [react-i18next](https://github.com/i18next/react-i18next/) repository. Either way, for more documentation, please visit the [main README](https://github.com/isaachinman/next-i18next).
10 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import pkgJson from 'next-i18next/package.json';
4 |
5 | import { withTranslation } from '../i18n';
6 |
7 | const Footer = ({ t }) => (
8 |
17 | );
18 |
19 | Footer.propTypes = {
20 | t: PropTypes.func.isRequired,
21 | };
22 |
23 | export default withTranslation('footer')(Footer);
24 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Head from 'next/head';
4 |
5 | const Header = ({ title }) => (
6 |
7 |
8 | next-i18next
9 |
10 |
12 |
13 |
14 |
16 |
18 |
20 |
21 |
22 | next-i18next
23 |
24 |
25 |
26 | {title}
27 |
28 |
32 |
33 |
34 |
35 | );
36 |
37 | Header.propTypes = {
38 | title: PropTypes.string.isRequired,
39 | };
40 |
41 | export default Header;
42 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/i18n.js:
--------------------------------------------------------------------------------
1 | /*
2 | Do not copy/paste this file. It is used internally
3 | to manage end-to-end test suites.
4 | */
5 |
6 | const NextI18Next = require('next-i18next').default;
7 | const { localeSubpaths } = require('next/config').default().publicRuntimeConfig;
8 | const { HMRPlugin } = require('i18next-hmr/plugin');
9 | const localeSubpathVariations = {
10 | none: {},
11 | foreign: {
12 | de: 'de',
13 | },
14 | all: {
15 | en: 'en',
16 | de: 'de',
17 | },
18 | };
19 |
20 | const nextI18Next = new NextI18Next({
21 | otherLanguages: ['de'],
22 | localeSubpaths: localeSubpathVariations[localeSubpaths],
23 | use:
24 | process.env.NODE_ENV !== 'production'
25 | ? [
26 | new HMRPlugin({
27 | webpack: {
28 | client: typeof window !== 'undefined',
29 | server: typeof window === 'undefined',
30 | },
31 | }),
32 | ]
33 | : undefined,
34 | });
35 |
36 | module.exports = nextI18Next;
37 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/index.js:
--------------------------------------------------------------------------------
1 | const { setConfig } = require('next/config');
2 | setConfig(require('./next.config'));
3 |
4 | require('./server');
5 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | publicRuntimeConfig: {
5 | localeSubpaths:
6 | typeof process.env.LOCALE_SUBPATHS === 'string' ? process.env.LOCALE_SUBPATHS : 'none',
7 | },
8 | webpack(config, options) {
9 | if (!options.isServer && config.mode === 'development') {
10 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
11 | config.plugins.push(
12 | new I18NextHMRPlugin({
13 | localesDir: path.resolve(__dirname, 'public/static/locales'),
14 | })
15 | );
16 | }
17 |
18 | return config;
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-with-next-i18next",
3 | "version": "1.0.0",
4 | "main": "./dist/index.js",
5 | "license": "MIT",
6 | "engines": {
7 | "node": ">=8"
8 | },
9 | "scripts": {
10 | "start": "node index.js",
11 | "build": "next build",
12 | "start:prod": "NODE_ENV=production node index.js"
13 | },
14 | "devDependencies": {
15 | "i18next-hmr": "^3.0.0"
16 | },
17 | "dependencies": {
18 | "express": "^4.19.2",
19 | "i18next": "^20.3.1",
20 | "next": "^12.1.0",
21 | "next-i18next": "^4.5.0",
22 | "react": "^17.0.2",
23 | "react-dom": "^17.0.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/pages/_app.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import App from 'next/app'
3 | import { appWithTranslation } from '../i18n'
4 |
5 | class MyApp extends App {
6 | render() {
7 | const { Component, pageProps } = this.props
8 | return (
9 |
10 | )
11 | }
12 | }
13 |
14 | export default appWithTranslation(MyApp)
15 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/pages/_error.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import { withTranslation } from '../i18n'
5 |
6 | const Error = ({ statusCode, t }) => (
7 |
8 | {statusCode
9 | ? t('error-with-status', { statusCode })
10 | : t('error-without-status')}
11 |
12 | )
13 |
14 | Error.getInitialProps = async ({ res, err }) => {
15 | let statusCode = null
16 | if (res) {
17 | ({ statusCode } = res)
18 | } else if (err) {
19 | ({ statusCode } = err)
20 | }
21 | return {
22 | namespacesRequired: ['common'],
23 | statusCode,
24 | }
25 | }
26 |
27 | Error.defaultProps = {
28 | statusCode: null,
29 | }
30 |
31 | Error.propTypes = {
32 | statusCode: PropTypes.number,
33 | t: PropTypes.func.isRequired,
34 | }
35 |
36 | export default withTranslation('common')(Error)
37 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { i18n, Link, withTranslation } from '../i18n';
5 | import Header from '../components/Header';
6 | import Footer from '../components/Footer';
7 |
8 | const Homepage = ({ t }) => (
9 |
10 |
11 |
12 |
13 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 |
32 | Homepage.getInitialProps = async () => ({
33 | namespacesRequired: ['common', 'footer'],
34 | });
35 |
36 | Homepage.propTypes = {
37 | t: PropTypes.func.isRequired,
38 | };
39 |
40 | export default withTranslation('common')(Homepage);
41 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/pages/second-page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import { Link, withTranslation } from '../i18n';
5 |
6 | import Header from '../components/Header';
7 | import Footer from '../components/Footer';
8 |
9 | const SecondPage = ({ t }) => (
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 | );
24 |
25 | SecondPage.getInitialProps = async () => ({
26 | namespacesRequired: ['second-page', 'footer'],
27 | });
28 |
29 | SecondPage.propTypes = {
30 | t: PropTypes.func.isRequired,
31 | };
32 |
33 | export default withTranslation('second-page')(SecondPage);
34 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/app.css:
--------------------------------------------------------------------------------
1 | #__next {
2 | font-family: 'Open Sans', sans-serif;
3 | text-align: center;
4 | background-image: linear-gradient(to left top, #ffffff, #f5f5f5, #eaeaea, #e0e0e0, #d6d6d6);
5 | display: flex;
6 | flex-direction: column;
7 | min-height: 100vh;
8 | min-width: 100vw;
9 | }
10 |
11 | h1,
12 | h2 {
13 | font-family: 'Oswald', sans-serif;
14 | }
15 |
16 | h1 {
17 | font-size: 3rem;
18 | margin: 5rem 0;
19 | }
20 | h2 {
21 | min-width: 18rem;
22 | font-size: 2rem;
23 | opacity: 0.3;
24 | }
25 |
26 | p {
27 | line-height: 1.65em;
28 | }
29 | p:nth-child(2) {
30 | font-style: italic;
31 | opacity: 0.65;
32 | margin-top: 1rem;
33 | }
34 |
35 | a.github {
36 | position: fixed;
37 | top: 0.5rem;
38 | right: 0.75rem;
39 | font-size: 4rem;
40 | color: #888;
41 | opacity: 0.8;
42 | }
43 | a.github:hover {
44 | opacity: 1;
45 | }
46 |
47 | button {
48 | display: inline-block;
49 | vertical-align: bottom;
50 | outline: 0;
51 | text-decoration: none;
52 | cursor: pointer;
53 | padding: .4rem;
54 | background-color: rgba(255, 255, 255, 0.5);
55 | box-sizing: border-box;
56 | font-size: 1em;
57 | font-family: inherit;
58 | border-radius: 3px;
59 | margin: .1rem;
60 | transition: box-shadow .2s ease;
61 | user-select: none;
62 | line-height: 2.5em;
63 | min-height: 40px;
64 | padding: 0 .8em;
65 | border: 0;
66 | color: inherit;
67 | position: relative;
68 | transform: translateZ(0);
69 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .26);
70 | margin: 0.8rem;
71 | }
72 |
73 | button:hover,
74 | button:focus {
75 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, .4);
76 | }
77 |
78 | main {
79 | display: flex;
80 | flex-direction: column;
81 | flex: 1;
82 | justify-content: center;
83 | align-items: center;
84 | }
85 | footer {
86 | background-color: rgba(255, 255, 255, 0.5);
87 | width: 100vw;
88 | padding: 3rem 0;
89 | }
90 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/locales/de/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "Ein einfaches Beispiel",
3 | "change-locale": "Wechseln Locale",
4 | "to-second-page": "Zur zweiten Seite",
5 | "error-with-status": "Auf dem Server ist ein Fehler ({{statusCode}}) aufgetreten",
6 | "error-without-status": "Auf dem Server ist ein Fehler aufgetreten"
7 | }
8 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/locales/de/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Dies ist eine Nicht-Seitenkomponente, die einen eigenen Namespace erfordert"
3 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/locales/de/second-page.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "Eine zweite Seite, um das Routing zu demonstrieren",
3 | "back-to-home": "Zurück zur Hauptseite"
4 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/locales/en/common.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "A simple example",
3 | "change-locale": "Change locale",
4 | "to-second-page": "To second page",
5 | "error-with-status": "A {{statusCode}} error occurred on server",
6 | "error-without-status": "An error occurred on the server"
7 | }
8 |
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/locales/en/footer.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "This is a non-page component that requires its own namespace"
3 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/public/static/locales/en/second-page.json:
--------------------------------------------------------------------------------
1 | {
2 | "h1": "A second page, to demonstrate routing",
3 | "back-to-home": "Back to home"
4 | }
--------------------------------------------------------------------------------
/examples/next-with-next-i18next/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const next = require('next');
3 | const nextI18NextMiddleware = require('next-i18next/middleware').default;
4 |
5 | const nextI18next = require('./i18n');
6 |
7 | const port = process.env.PORT || 3000;
8 | const app = next({ dev: process.env.NODE_ENV !== 'production' });
9 | const handle = app.getRequestHandler();
10 | (async () => {
11 | await app.prepare();
12 | const server = express();
13 | server.use(nextI18NextMiddleware(nextI18next));
14 | server.get('*', (req, res) => handle(req, res));
15 |
16 | await server.listen(port);
17 | console.log(`> Ready on http://localhost:${port}`); // eslint-disable-line no-console
18 | })().catch((e) => console.error(e));
19 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 | npm-debug.log*
4 | .DS_Store
5 |
6 | coverage
7 | node_modules
8 | build
9 | .env.local
10 | .env.development.local
11 | .env.test.local
12 | .env.production.local
13 | yarn.lock
14 | cache
15 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/README.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | Build with [razzle](https://github.com/jaredpalmer/razzle) using its react ssr.
4 |
5 | ```bash
6 | # npm start
7 | ```
8 |
9 | **open:**
10 |
11 | will detect language: [http://localhost:3000](http://localhost:3000)
12 |
13 | german: [http://localhost:3000/?lng=de](http://localhost:3000/?lng=de)
14 |
15 | english: [http://localhost:3000/?lng=en](http://localhost:3000/?lng=en)
16 |
17 | ## production
18 |
19 | ```bash
20 | # npm run build
21 | # npm run start:prod
22 | ```
23 |
24 | ## Learn more
25 |
26 | - Uses express to also serve translations for clientside
27 | - Translations are passed down to client on initial serverside render -> no reload of translations, no flickering
28 | - Uses _i18next-express-middleware_ on the serverside to assert that every request gets his own instance of i18next (no race condition conflicts when user b overrides set language in i18next singleton of user a!!!)
29 | - completely allows saveMissing feature of i18next -> added content will be pushed to server and stored in `xyz.missing.json`
30 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-razzle-app",
3 | "version": "0.1.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "start": "razzle start",
7 | "build": "razzle build",
8 | "test": "razzle test --env=jsdom",
9 | "start:prod": "NODE_ENV=production node build/server.js"
10 | },
11 | "dependencies": {
12 | "i18next": "20.3.1",
13 | "i18next-browser-languagedetector": "6.1.1",
14 | "i18next-http-middleware": "3.1.3",
15 | "i18next-node-fs-backend": "^2.1.3",
16 | "i18next-xhr-backend": "3.2.2",
17 | "razzle": "^4.0.4",
18 | "react": "17.0.2",
19 | "react-dom": "17.0.2",
20 | "react-i18next": "11.11.0",
21 | "react-router-dom": "5.2.0"
22 | },
23 | "devDependencies": {
24 | "babel-preset-razzle": "^4.0.4",
25 | "html-webpack-plugin": "^5.3.1",
26 | "i18next-hmr": "^3.0.0",
27 | "mini-css-extract-plugin": "^1.6.0",
28 | "razzle-dev-utils": "^4.0.4",
29 | "webpack": "^5.39.0",
30 | "webpack-dev-server": "^3.11.2"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/felixmosh/i18next-hmr/f35b7ffe162c284e80a798d51694aa80762a1766/examples/razzle-ssr/public/favicon.ico
--------------------------------------------------------------------------------
/examples/razzle-ssr/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 |
3 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/razzle.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | modifyWebpackConfig: ({ webpackConfig, env: { dev } }) => {
5 | if (dev) {
6 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
7 | webpackConfig.plugins.push(
8 | new I18NextHMRPlugin({
9 | localesDir: path.resolve(__dirname, 'src/locales'),
10 | })
11 | );
12 | }
13 | return webpackConfig;
14 | },
15 | };
16 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/App.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch } from 'react-router-dom';
3 | import Home from './Home';
4 | import './App.css';
5 |
6 | function App() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/Home.css:
--------------------------------------------------------------------------------
1 | .Home {
2 | text-align: center;
3 | }
4 |
5 | .Home-logo {
6 | animation: Home-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .Home-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .Home-intro {
18 | margin-top: 30px;
19 | font-size: large;
20 | }
21 |
22 | .Home-resources {
23 | list-style: none;
24 | }
25 |
26 | .Home-resources > li {
27 | display: inline-block;
28 | padding: 1rem;
29 | }
30 |
31 | .Home-lang-buttons {
32 | margin: 2rem 0;
33 | }
34 |
35 | @keyframes Home-logo-spin {
36 | from { transform: rotate(0deg); }
37 | to { transform: rotate(360deg); }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Trans, useTranslation } from 'react-i18next';
3 | import logo from './react.svg';
4 | import './Home.css';
5 |
6 | function Home() {
7 | const [t, i18n] = useTranslation('translations');
8 |
9 | return (
10 |
11 |
12 |

13 |
{t('message.welcome')}
14 |
15 |
16 |
17 | To get started, edit src/App.js
or src/Home.js
and save to
18 | reload.
19 |
20 |
21 |
22 |
23 |
24 |
25 |
36 |
37 | );
38 | }
39 |
40 | export default Home;
41 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/client.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 | import { BrowserRouter } from 'react-router-dom';
3 | import React, { Suspense } from 'react';
4 | import { hydrate } from 'react-dom';
5 | import { useSSR } from 'react-i18next';
6 | import './i18n';
7 |
8 | const BaseApp = () => {
9 | useSSR(window.initialI18nStore, window.initialLanguage);
10 | return (
11 | Still loading i18n...}>
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
19 | hydrate(, document.getElementById('root'));
20 |
21 | if (module.hot) {
22 | module.hot.accept();
23 | }
24 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 | import XHR from 'i18next-xhr-backend';
4 | import LanguageDetector from 'i18next-browser-languagedetector';
5 | import { HMRPlugin } from 'i18next-hmr/plugin';
6 |
7 | const options = {
8 | fallbackLng: 'en',
9 | lng: 'en',
10 | load: 'languageOnly', // we only provide en, de -> no region specific locals like en-US, de-DE
11 | // have a common namespace used around the full app
12 | ns: ['translations'],
13 | defaultNS: 'translations',
14 |
15 | saveMissing: true,
16 | debug: true,
17 |
18 | interpolation: {
19 | escapeValue: false, // not needed for react!!
20 | formatSeparator: ',',
21 | format: (value, format, lng) => {
22 | if (format === 'uppercase') return value.toUpperCase();
23 | return value;
24 | },
25 | },
26 | wait: process && !process.release,
27 | };
28 |
29 | // for browser use xhr backend to load translations and browser lng detector
30 | if (process && !process.release) {
31 | i18n.use(XHR).use(initReactI18next).use(LanguageDetector);
32 | }
33 |
34 | // initialize if not already initialized
35 | if (!i18n.isInitialized) {
36 | if (process.env.NODE_ENV !== 'production') {
37 | i18n.use(new HMRPlugin({ webpack: { client: true } }));
38 | }
39 |
40 | i18n.init(options);
41 | }
42 |
43 | export default i18n;
44 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import app from './server';
3 |
4 | if (module.hot) {
5 | module.hot.accept('./server', function() {
6 | console.log('🔁 HMR Reloading `./server`...');
7 | });
8 | console.info('✅ Server-side HMR Enabled!');
9 | }
10 |
11 | const port = process.env.PORT || 3000;
12 |
13 | export default express()
14 | .use((req, res) => app.handle(req, res))
15 | .listen(port, function(err) {
16 | if (err) {
17 | console.error(err);
18 | return;
19 | }
20 | console.log(`> Started on port ${port}`);
21 | });
22 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/locales/de/translations.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": {
3 | "welcome": "Willkommen zu Razzle mit i18n"
4 | },
5 | "guideline": "Um zu beginnen, ändere <1>src/App.js1> oder <3>src/Home.js3> und speichere deine Änderungen um neu zu laden."
6 | }
7 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/locales/en/translations.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": {
3 | "welcome": "Welcome to Razzle with i18n"
4 | },
5 | "guideline": "To get started, edit <1>src/App.js1> or <3>src/Home.js3> and save to reload."
6 | }
7 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/react.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/examples/razzle-ssr/src/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-dynamic-require */
2 | import React from 'react';
3 | import { StaticRouter } from 'react-router-dom';
4 |
5 | import express from 'express';
6 | import path from 'path';
7 | import fs from 'fs';
8 | import { renderToString } from 'react-dom/server';
9 |
10 | import { I18nextProvider } from 'react-i18next';
11 | import Backend from 'i18next-node-fs-backend';
12 | import App from './App';
13 | import i18n from './i18n';
14 | import { HMRPlugin } from 'i18next-hmr/plugin';
15 |
16 | // Make sure any symlinks in the project folder are resolved:
17 | // https://github.com/facebookincubator/create-react-app/issues/637
18 | const appDirectory = fs.realpathSync(process.cwd());
19 | const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
20 | const appSrc = resolveApp('src');
21 |
22 | const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
23 | const i18nextMiddleware = require('i18next-http-middleware');
24 |
25 | const server = express();
26 |
27 | if (process.env.NODE_ENV !== 'production') {
28 | const { applyServerHMR } = require('../node_modules/i18next-hmr/server'); // this relative import is to overcome webpack-node-externals
29 | applyServerHMR(i18n);
30 | }
31 |
32 | const instance = i18n.use(Backend).use(i18nextMiddleware.LanguageDetector);
33 |
34 | if (process.env.NODE_ENV !== 'production') {
35 | instance.use(new HMRPlugin({ webpack: { server: true } }));
36 | }
37 |
38 | instance.init(
39 | {
40 | debug: false,
41 | preload: ['en', 'de'],
42 | ns: ['translations'],
43 | defaultNS: 'translations',
44 | backend: {
45 | loadPath: `${appSrc}/locales/{{lng}}/{{ns}}.json`,
46 | addPath: `${appSrc}/locales/{{lng}}/{{ns}}.missing.json`,
47 | },
48 | },
49 | () => {
50 | server
51 | .disable('x-powered-by')
52 | .use(i18nextMiddleware.handle(i18n))
53 | .use('/locales', express.static(`${appSrc}/locales`))
54 | .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
55 | .get('/*', (req, res) => {
56 | const context = {};
57 | const markup = renderToString(
58 |
59 |
60 |
61 |
62 |
63 | );
64 | // This line must be placed after renderToString method
65 | // otherwise context won't be populated by App
66 | const { url } = context;
67 | if (url) {
68 | res.redirect(url);
69 | } else {
70 | const initialI18nStore = {};
71 | req.i18n.languages.forEach((l) => {
72 | initialI18nStore[l] = req.i18n.services.resourceStore.data[l];
73 | });
74 | const initialLanguage = req.i18n.language;
75 |
76 | res.status(200).send(
77 | `
78 |
79 |
80 |
81 |
82 | Welcome to Razzle
83 |
84 | ${assets.client.css ? `` : ''}
85 |
86 |
90 |
91 |
92 | ${markup}
93 |
94 | `
95 | );
96 | }
97 | });
98 | }
99 | );
100 |
101 | export default server;
102 |
--------------------------------------------------------------------------------
/examples/react-i18next/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/examples/react-i18next/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/react-i18next/config-overrides.js:
--------------------------------------------------------------------------------
1 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | webpack(config, env) {
6 | config.plugins = config.plugins || [];
7 | if (env === 'development') {
8 | config.plugins.push(
9 | new I18NextHMRPlugin({ localesDir: path.resolve(__dirname, 'public/locales') })
10 | );
11 | }
12 |
13 | return config;
14 | },
15 | devServer: function (configFunction) {
16 | return function (proxy, allowedHost) {
17 | const config = configFunction(proxy, allowedHost);
18 |
19 | if (config.static?.watch?.ignored) {
20 | config.static.watch.ignored = [config.static.watch.ignored, /\/locales\/.*\.json$/]; // prevents from dev-server to reload the page when locales are changing
21 | }
22 |
23 | return config;
24 | };
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/examples/react-i18next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react_usinghooks",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "i18next": "21.6.16",
7 | "i18next-browser-languagedetector": "6.1.4",
8 | "i18next-http-backend": "1.4.0",
9 | "react": "18.1.0",
10 | "react-dom": "18.1.0",
11 | "react-i18next": "11.16.7",
12 | "react-scripts": "5.0.1"
13 | },
14 | "scripts": {
15 | "start": "react-app-rewired start",
16 | "build": "react-app-rewired build",
17 | "test": "react-app-rewired test --env=jsdom",
18 | "eject": "react-app-rewired eject"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "development": [
28 | "last 2 chrome versions",
29 | "last 2 firefox versions",
30 | "last 2 edge versions"
31 | ],
32 | "production": [
33 | ">1%",
34 | "last 4 versions",
35 | "Firefox ESR",
36 | "not ie < 11"
37 | ]
38 | },
39 | "devDependencies": {
40 | "i18next-hmr": "^3.0.0",
41 | "react-app-rewired": "^2.2.1"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/examples/react-i18next/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/felixmosh/i18next-hmr/f35b7ffe162c284e80a798d51694aa80762a1766/examples/react-i18next/public/favicon.ico
--------------------------------------------------------------------------------
/examples/react-i18next/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/examples/react-i18next/public/locales/de/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Willkommen zu react und react-i18next",
3 | "description": {
4 | "part1": "Um loszulegen, ändere <1>src/App(DE).js1> und speichere um neuzuladen.",
5 | "part2": "Wechsle die Sprache zwischen deutsch und englisch mit Hilfe der beiden Schalter."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/react-i18next/public/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Welcome to react using react-i18next",
3 | "description": {
4 | "part1": "To get started, edit <1>src/App.js1> and save to reload.",
5 | "part2": "Switch language between english and german using buttons above."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/react-i18next/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/examples/react-i18next/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/react-i18next/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Suspense } from 'react';
2 | import { useTranslation, withTranslation, Trans } from 'react-i18next';
3 | import logo from './logo.svg';
4 | import './App.css';
5 |
6 | // use hoc for class based components
7 | class LegacyWelcomeClass extends Component {
8 | render() {
9 | const { t } = this.props;
10 | return {t('title')}
;
11 | }
12 | }
13 | const Welcome = withTranslation()(LegacyWelcomeClass);
14 |
15 | // Component using the Trans component
16 | function MyComponent() {
17 | return (
18 |
19 | To get started, edit src/App.js
and save to reload.
20 |
21 | );
22 | }
23 |
24 | // page uses the hook
25 | function Page() {
26 | const { t, i18n } = useTranslation();
27 |
28 | const changeLanguage = (lng) => {
29 | i18n.changeLanguage(lng);
30 | };
31 |
32 | return (
33 |
34 |
35 |

36 |
37 |
40 |
43 |
44 |
45 |
46 |
47 |
{t('description.part2')}
48 |
49 | );
50 | }
51 |
52 | // loading component for suspense fallback
53 | const Loader = () => (
54 |
55 |

56 |
loading...
57 |
58 | );
59 |
60 | // here app catches the suspense from page in case translations are not yet loaded
61 | export default function App() {
62 | return (
63 | }>
64 |
65 |
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/examples/react-i18next/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import Backend from 'i18next-http-backend';
3 | import LanguageDetector from 'i18next-browser-languagedetector';
4 | import { initReactI18next } from 'react-i18next';
5 | import { HMRPlugin } from 'i18next-hmr/plugin';
6 |
7 | const instance = i18n
8 | // load translation using http -> see /public/locales
9 | // learn more: https://github.com/i18next/i18next-http-backend
10 | .use(Backend)
11 | // detect user language
12 | // learn more: https://github.com/i18next/i18next-browser-languageDetector
13 | .use(LanguageDetector)
14 | // pass the i18n instance to react-i18next.
15 | .use(initReactI18next);
16 |
17 | if (process.env.NODE_ENV !== 'production') {
18 | instance.use(new HMRPlugin({ webpack: { client: true } }));
19 | }
20 |
21 | instance.init({
22 | fallbackLng: 'en',
23 |
24 | interpolation: {
25 | escapeValue: false, // not needed for react as it escapes by default
26 | },
27 | });
28 |
29 | export default i18n;
30 |
--------------------------------------------------------------------------------
/examples/react-i18next/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/react-i18next/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 |
6 | // import i18n (needs to be bundled ;))
7 | import './i18n';
8 |
9 | const root = createRoot(document.getElementById('root'));
10 | root.render(
11 |
12 |
13 | ,
14 | );
15 |
--------------------------------------------------------------------------------
/examples/react-i18next/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/examples/rspack-react/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/rspack-react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Rspack + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/rspack-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rspack-react-ts-starter",
3 | "private": true,
4 | "version": "1.0.0",
5 | "scripts": {
6 | "dev": "NODE_ENV=development rspack serve",
7 | "build": "NODE_ENV=production rspack build"
8 | },
9 | "dependencies": {
10 | "react": "^18.2.0",
11 | "react-dom": "^18.2.0",
12 | "i18next": "^23.10.1",
13 | "react-i18next": "^14.1.0",
14 | "i18next-http-backend": "^2.5.0",
15 | "i18next-browser-languagedetector": "^7.2.1"
16 | },
17 | "devDependencies": {
18 | "@rspack/cli": "0.5.9",
19 | "@rspack/core": "1.0.0",
20 | "@rspack/plugin-react-refresh": "0.5.9",
21 | "@types/react": "^18.2.48",
22 | "@types/react-dom": "^18.2.18",
23 | "react-refresh": "^0.14.0",
24 | "i18next-hmr": "^3.0.4"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/rspack-react/public/locales/de/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Willkommen zu Rspack + TypeScript",
3 | "description": {
4 | "part1": "Um loszulegen, ändere <1>src/App(DE).js1> und speichere um neuzuladen.",
5 | "part2": "Wechsle die Sprache zwischen deutsch und englisch mit Hilfe der beiden Schalter."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/rspack-react/public/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Welcome to react using Rspack + TypeScript\n",
3 | "description": {
4 | "part1": "To get started, edit <1>src/App.tsx1> and save to reload.",
5 | "part2": "Switch language between english and german using buttons above."
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/examples/rspack-react/public/react.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/rspack-react/rspack.config.js:
--------------------------------------------------------------------------------
1 | const rspack = require('@rspack/core');
2 | const path = require('node:path');
3 | const refreshPlugin = require('@rspack/plugin-react-refresh');
4 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
5 | const isDev = process.env.NODE_ENV === 'development';
6 | /**
7 | * @type {import('@rspack/cli').Configuration}
8 | */
9 | module.exports = {
10 | context: __dirname,
11 | entry: {
12 | main: './src/main.tsx',
13 | },
14 | resolve: {
15 | extensions: ['...', '.ts', '.tsx', '.jsx'],
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.svg$/,
21 | type: 'asset',
22 | },
23 | {
24 | test: /\.(jsx?|tsx?)$/,
25 | use: [
26 | {
27 | loader: 'builtin:swc-loader',
28 | options: {
29 | sourceMap: true,
30 | jsc: {
31 | parser: {
32 | syntax: 'typescript',
33 | tsx: true,
34 | },
35 | transform: {
36 | react: {
37 | runtime: 'automatic',
38 | development: isDev,
39 | refresh: isDev,
40 | },
41 | },
42 | },
43 | env: {
44 | targets: ['chrome >= 87', 'edge >= 88', 'firefox >= 78', 'safari >= 14'],
45 | },
46 | },
47 | },
48 | ],
49 | },
50 | ],
51 | },
52 | plugins: [
53 | new rspack.DefinePlugin({
54 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
55 | }),
56 | new rspack.ProgressPlugin({}),
57 | new rspack.HtmlRspackPlugin({
58 | template: './index.html',
59 | }),
60 | isDev ? new refreshPlugin() : null,
61 | isDev ? new I18NextHMRPlugin({ localesDir: path.resolve('./public/locales') }) : null,
62 | ].filter(Boolean),
63 | devServer: {
64 | static: {
65 | directory: path.resolve('./public'),
66 | watch: {
67 | ignored: /\/public\/locales\//,
68 | },
69 | },
70 | },
71 | };
72 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { WithT } from 'i18next';
2 | import { Component, useState } from 'react';
3 | import { Trans, useTranslation, withTranslation } from 'react-i18next';
4 | import reactLogo from './assets/react.svg';
5 | import './App.css';
6 |
7 | // use hoc for class based components
8 | class LegacyWelcomeClass extends Component {
9 | render() {
10 | const { t } = this.props;
11 | return {t('title')}
;
12 | }
13 | }
14 | const Welcome = withTranslation()(LegacyWelcomeClass);
15 |
16 | // Component using the Trans component
17 | function MyComponent() {
18 | return (
19 |
20 | To get started, edit src/App.js
and save to reload.
21 |
22 | );
23 | }
24 |
25 | function App() {
26 | const [count, setCount] = useState(0);
27 | const { t, i18n } = useTranslation();
28 |
29 | const changeLanguage = (lng: string) => {
30 | i18n.changeLanguage(lng);
31 | };
32 |
33 | return (
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
50 |
53 |
{t('description.part2')}
54 |
55 | );
56 | }
57 |
58 | export default App;
59 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import Backend from 'i18next-http-backend';
3 | import LanguageDetector from 'i18next-browser-languagedetector';
4 | import { initReactI18next } from 'react-i18next';
5 | import { HMRPlugin } from 'i18next-hmr/plugin';
6 |
7 | const instance = i18n
8 | // load translation using http -> see /public/locales
9 | // learn more: https://github.com/i18next/i18next-http-backend
10 | .use(Backend)
11 | // detect user language
12 | // learn more: https://github.com/i18next/i18next-browser-languageDetector
13 | .use(LanguageDetector)
14 | // pass the i18n instance to react-i18next.
15 | .use(initReactI18next);
16 |
17 | if (process.env.NODE_ENV !== 'production') {
18 | instance.use(new HMRPlugin({ webpack: { client: true } }));
19 | }
20 |
21 | instance.init({
22 | fallbackLng: 'en',
23 |
24 | interpolation: {
25 | escapeValue: false, // not needed for react as it escapes by default
26 | },
27 | });
28 |
29 | export default i18n;
30 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App.tsx';
4 | import './index.css';
5 | // import i18n (needs to be bundled ;))
6 | import './i18n';
7 |
8 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/examples/rspack-react/src/react-env.d.ts:
--------------------------------------------------------------------------------
1 | // CSS modules
2 | type CSSModuleClasses = { readonly [key: string]: string };
3 |
4 | declare module "*.module.css" {
5 | const classes: CSSModuleClasses;
6 | export default classes;
7 | }
8 | declare module "*.module.scss" {
9 | const classes: CSSModuleClasses;
10 | export default classes;
11 | }
12 | declare module "*.module.sass" {
13 | const classes: CSSModuleClasses;
14 | export default classes;
15 | }
16 | declare module "*.module.less" {
17 | const classes: CSSModuleClasses;
18 | export default classes;
19 | }
20 | declare module "*.module.styl" {
21 | const classes: CSSModuleClasses;
22 | export default classes;
23 | }
24 | declare module "*.module.stylus" {
25 | const classes: CSSModuleClasses;
26 | export default classes;
27 | }
28 | declare module "*.module.pcss" {
29 | const classes: CSSModuleClasses;
30 | export default classes;
31 | }
32 | declare module "*.module.sss" {
33 | const classes: CSSModuleClasses;
34 | export default classes;
35 | }
36 |
37 | // CSS
38 | declare module "*.css" {
39 | /**
40 | * @deprecated Use `import style from './style.css?inline'` instead.
41 | */
42 | const css: string;
43 | export default css;
44 | }
45 | declare module "*.scss" {
46 | /**
47 | * @deprecated Use `import style from './style.scss?inline'` instead.
48 | */
49 | const css: string;
50 | export default css;
51 | }
52 | declare module "*.sass" {
53 | /**
54 | * @deprecated Use `import style from './style.sass?inline'` instead.
55 | */
56 | const css: string;
57 | export default css;
58 | }
59 | declare module "*.less" {
60 | /**
61 | * @deprecated Use `import style from './style.less?inline'` instead.
62 | */
63 | const css: string;
64 | export default css;
65 | }
66 | declare module "*.styl" {
67 | /**
68 | * @deprecated Use `import style from './style.styl?inline'` instead.
69 | */
70 | const css: string;
71 | export default css;
72 | }
73 | declare module "*.stylus" {
74 | /**
75 | * @deprecated Use `import style from './style.stylus?inline'` instead.
76 | */
77 | const css: string;
78 | export default css;
79 | }
80 | declare module "*.pcss" {
81 | /**
82 | * @deprecated Use `import style from './style.pcss?inline'` instead.
83 | */
84 | const css: string;
85 | export default css;
86 | }
87 | declare module "*.sss" {
88 | /**
89 | * @deprecated Use `import style from './style.sss?inline'` instead.
90 | */
91 | const css: string;
92 | export default css;
93 | }
94 |
95 | // images
96 | declare module "*.png" {
97 | const src: string;
98 | export default src;
99 | }
100 | declare module "*.jpg" {
101 | const src: string;
102 | export default src;
103 | }
104 | declare module "*.jpeg" {
105 | const src: string;
106 | export default src;
107 | }
108 | declare module "*.jfif" {
109 | const src: string;
110 | export default src;
111 | }
112 | declare module "*.pjpeg" {
113 | const src: string;
114 | export default src;
115 | }
116 | declare module "*.pjp" {
117 | const src: string;
118 | export default src;
119 | }
120 | declare module "*.gif" {
121 | const src: string;
122 | export default src;
123 | }
124 | declare module "*.svg" {
125 | const ReactComponent: React.FC>;
126 | const content: string;
127 |
128 | export { ReactComponent };
129 | export default content;
130 | }
131 | declare module "*.ico" {
132 | const src: string;
133 | export default src;
134 | }
135 | declare module "*.webp" {
136 | const src: string;
137 | export default src;
138 | }
139 | declare module "*.avif" {
140 | const src: string;
141 | export default src;
142 | }
143 |
144 | // media
145 | declare module "*.mp4" {
146 | const src: string;
147 | export default src;
148 | }
149 | declare module "*.webm" {
150 | const src: string;
151 | export default src;
152 | }
153 | declare module "*.ogg" {
154 | const src: string;
155 | export default src;
156 | }
157 | declare module "*.mp3" {
158 | const src: string;
159 | export default src;
160 | }
161 | declare module "*.wav" {
162 | const src: string;
163 | export default src;
164 | }
165 | declare module "*.flac" {
166 | const src: string;
167 | export default src;
168 | }
169 | declare module "*.aac" {
170 | const src: string;
171 | export default src;
172 | }
173 |
174 | declare module "*.opus" {
175 | const src: string;
176 | export default src;
177 | }
178 |
179 | // fonts
180 | declare module "*.woff" {
181 | const src: string;
182 | export default src;
183 | }
184 | declare module "*.woff2" {
185 | const src: string;
186 | export default src;
187 | }
188 | declare module "*.eot" {
189 | const src: string;
190 | export default src;
191 | }
192 | declare module "*.ttf" {
193 | const src: string;
194 | export default src;
195 | }
196 | declare module "*.otf" {
197 | const src: string;
198 | export default src;
199 | }
200 |
201 | // other
202 | declare module "*.webmanifest" {
203 | const src: string;
204 | export default src;
205 | }
206 | declare module "*.pdf" {
207 | const src: string;
208 | export default src;
209 | }
210 | declare module "*.txt" {
211 | const src: string;
212 | export default src;
213 | }
214 |
--------------------------------------------------------------------------------
/examples/rspack-react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowImportingTsExtensions": true,
8 | "resolveJsonModule": true,
9 | "isolatedModules": true,
10 | "noEmit": true,
11 | "jsx": "react-jsx",
12 | "strict": true,
13 | "noUnusedLocals": true,
14 | "noUnusedParameters": true,
15 | "noFallthroughCasesInSwitch": true
16 | },
17 | "include": ["src"]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-react-i18next",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "i18next": "^24.1.0",
14 | "i18next-browser-languagedetector": "^8.0.2",
15 | "i18next-hmr": "^3.1.3",
16 | "i18next-http-backend": "^3.0.1",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0",
19 | "react-i18next": "^15.2.0"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^18.2.15",
23 | "@types/react-dom": "^18.2.7",
24 | "@vitejs/plugin-react": "^4.3.4",
25 | "eslint": "^8.45.0",
26 | "eslint-plugin-react": "^7.32.2",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "eslint-plugin-react-refresh": "^0.4.3",
29 | "vite": "^6.2.7"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/public/locales/de/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Vite + React",
3 | "button_text": "anzahl ist {{count}}",
4 | "description": {
5 | "part1": "Bearbeiten Sie <0>src/App.jsx0> und speichern Sie, um HMR zu testen",
6 | "part2": "Klicken Sie auf die Logos von Vite und React, um mehr zu erfahren"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/public/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Vite + React",
3 | "button_text": "count is {{count}}",
4 | "description": {
5 | "part1": "Edit <0>src/App.jsx0> and save to test HMR",
6 | "part2": "Click on the Vite and React logos to learn more"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/public/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import reactLogo from './assets/react.svg';
3 | import viteLogo from '/vite.svg';
4 | import './App.css';
5 | import { Trans, useTranslation } from 'react-i18next';
6 |
7 | function App() {
8 | const [count, setCount] = useState(0);
9 | const { t, i18n } = useTranslation();
10 |
11 | return (
12 | <>
13 |
21 | {t('title')}
22 |
23 |
26 |
27 | ]} />
28 |
29 |
30 | {t('description.part2')}
31 |
32 |
35 |
38 |
39 | >
40 | );
41 | }
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import Backend from 'i18next-http-backend';
3 | import LanguageDetector from 'i18next-browser-languagedetector';
4 | import { initReactI18next } from 'react-i18next';
5 |
6 | export async function init() {
7 | const instance = i18n
8 | // load translation using http -> see /public/locales
9 | // learn more: https://github.com/i18next/i18next-http-backend
10 | .use(Backend)
11 | // detect user language
12 | // learn more: https://github.com/i18next/i18next-browser-languageDetector
13 | .use(LanguageDetector)
14 | // pass the i18n instance to react-i18next.
15 | .use(initReactI18next);
16 |
17 | if (import.meta.env.MODE !== 'production') {
18 | const { HMRPlugin } = await import('i18next-hmr/plugin');
19 |
20 | instance.use(new HMRPlugin({ vite: { client: true } }));
21 | }
22 |
23 | await instance.init({
24 | fallbackLng: 'en',
25 | supportedLngs: ['en', 'de'],
26 | interpolation: {
27 | escapeValue: false, // not needed for react as it escapes by default
28 | },
29 | });
30 |
31 | return instance;
32 | }
33 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | display: flex;
29 | place-items: center;
30 | min-width: 320px;
31 | min-height: 100vh;
32 | }
33 |
34 | h1 {
35 | font-size: 3.2em;
36 | line-height: 1.1;
37 | }
38 |
39 | button {
40 | border-radius: 8px;
41 | border: 1px solid transparent;
42 | padding: 0.6em 1.2em;
43 | font-size: 1em;
44 | font-weight: 500;
45 | font-family: inherit;
46 | background-color: #1a1a1a;
47 | cursor: pointer;
48 | transition: border-color 0.25s;
49 | }
50 | button:hover {
51 | border-color: #646cff;
52 | }
53 | button:focus,
54 | button:focus-visible {
55 | outline: 4px auto -webkit-focus-ring-color;
56 | }
57 |
58 | @media (prefers-color-scheme: light) {
59 | :root {
60 | color: #213547;
61 | background-color: #ffffff;
62 | }
63 | a:hover {
64 | color: #747bff;
65 | }
66 | button {
67 | background-color: #f9f9f9;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App.jsx';
4 | import './index.css';
5 | import { init } from './i18n.js';
6 |
7 | init().then(() => {
8 | ReactDOM.createRoot(document.getElementById('root')).render(
9 |
10 |
11 |
12 | );
13 | });
14 |
--------------------------------------------------------------------------------
/examples/vite-react-i18next/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig(async ({ mode }) => {
6 | const devPlugins = [];
7 | if (mode === 'development') {
8 | const { i18nextHMRPlugin } = await import('i18next-hmr/vite');
9 | devPlugins.push(i18nextHMRPlugin({ localesDir: './public/locales' }));
10 | }
11 | return {
12 | plugins: [react()].concat(devPlugins),
13 | };
14 | });
15 |
--------------------------------------------------------------------------------
/examples/vue-i18next/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/preset-env", { "modules": false }]
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/examples/vue-i18next/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log
5 | yarn-error.log
6 |
7 | # Editor directories and files
8 | .idea
9 | *.suo
10 | *.ntvs*
11 | *.njsproj
12 | *.sln
13 |
--------------------------------------------------------------------------------
/examples/vue-i18next/README.md:
--------------------------------------------------------------------------------
1 | # vue-i18next-with-hmr
2 |
3 | > List rendering with vue-i18next `$t` method
4 |
5 | ## Build Setup
6 |
7 | ``` bash
8 | # install dependencies
9 | npm install
10 |
11 | # serve with hot reload at localhost:8080
12 | npm run dev
13 |
14 | # build for production with minification
15 | npm run build
16 | ```
17 |
18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader).
19 |
--------------------------------------------------------------------------------
/examples/vue-i18next/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | vue-i18next-with-hmr
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/examples/vue-i18next/locales/en/translation.json:
--------------------------------------------------------------------------------
1 | {
2 | "TITLE": "Welcome to Your Vue.js App"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/vue-i18next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-i18next-with-hmr",
3 | "description": "A Vue.js project",
4 | "version": "1.0.0",
5 | "private": true,
6 | "scripts": {
7 | "start": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
8 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
9 | },
10 | "dependencies": {
11 | "@panter/vue-i18next": "^0.15.2",
12 | "i18next": "^20.3.1",
13 | "i18next-xhr-backend": "^3.2.2",
14 | "vue": "^2.7.7"
15 | },
16 | "devDependencies": {
17 | "@babel/core": "^7.18.9",
18 | "@babel/preset-env": "^7.18.9",
19 | "babel-loader": "^8.2.5",
20 | "cross-env": "^7.0.2",
21 | "css-loader": "^5.2.6",
22 | "file-loader": "^6.0.0",
23 | "i18next-hmr": "^3.0.0",
24 | "vue-loader": "^15.9.3",
25 | "vue-style-loader": "^4.1.2",
26 | "vue-template-compiler": "^2.7.7",
27 | "webpack": "^5.94.0",
28 | "webpack-cli": "^3.3.12",
29 | "webpack-dev-server": "^3.11.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/vue-i18next/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
18 |
19 |
29 |
--------------------------------------------------------------------------------
/examples/vue-i18next/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ $t('TITLE') }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
35 |
--------------------------------------------------------------------------------
/examples/vue-i18next/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App.vue';
3 | import VueI18Next from '@panter/vue-i18next';
4 | import i18next from 'i18next';
5 | import Backend from 'i18next-xhr-backend';
6 | import { HMRPlugin } from 'i18next-hmr/plugin';
7 |
8 | Vue.use(VueI18Next);
9 |
10 | const instance = i18next.use(Backend);
11 |
12 | if (process.env.NODE_ENV !== 'production') {
13 | instance.use(new HMRPlugin({ webpack: { client: true } }));
14 | }
15 |
16 | instance.init({
17 | lng: 'en',
18 | });
19 |
20 | const i18n = new VueI18Next(instance);
21 |
22 | Vue.config.productionTip = false;
23 |
24 | new Vue({
25 | i18n,
26 | render: (h) => h(App),
27 | }).$mount('#app');
28 |
--------------------------------------------------------------------------------
/examples/vue-i18next/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const VueLoaderPlugin = require('vue-loader/lib/plugin');
3 | const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
4 |
5 | module.exports = {
6 | entry: './src/main.js',
7 | output: {
8 | path: path.resolve(__dirname, './dist'),
9 | publicPath: '/dist/',
10 | filename: 'build.js',
11 | },
12 | module: {
13 | rules: [
14 | {
15 | test: /\.vue$/,
16 | loader: 'vue-loader',
17 | options: {
18 | loaders: {},
19 | // other vue-loader options go here
20 | },
21 | },
22 | {
23 | test: /\.js$/,
24 | loader: 'babel-loader',
25 | exclude: /node_modules/,
26 | },
27 | {
28 | test: /\.(png|jpg|gif|svg)$/,
29 | loader: 'file-loader',
30 | options: {
31 | name: '[name].[ext]?[hash]',
32 | },
33 | },
34 | {
35 | test: /\.css$/,
36 | use: ['vue-style-loader', 'css-loader'],
37 | },
38 | ],
39 | },
40 | plugins: [
41 | new VueLoaderPlugin(),
42 | new I18NextHMRPlugin({ localesDir: path.resolve(__dirname, 'locales') }),
43 | ],
44 | devServer: {
45 | historyApiFallback: true,
46 | },
47 | performance: {
48 | hints: false,
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/lib/plugin.d.ts:
--------------------------------------------------------------------------------
1 | export declare class HMRPlugin {
2 | type: '3rdParty';
3 | constructor(
4 | hmrOptions: Partial<{
5 | webpack: Partial<{
6 | client: boolean;
7 | server: boolean;
8 | }>;
9 | vite: {
10 | client: boolean;
11 | };
12 | }>
13 | );
14 | init(i18nInstance: any): void;
15 | toJSON(): null;
16 | toString(): string;
17 | }
18 |
--------------------------------------------------------------------------------
/lib/plugin.js:
--------------------------------------------------------------------------------
1 | class HMRPlugin {
2 | constructor(hmrOptions = {}) {
3 | this.type = '3rdParty';
4 |
5 | const webpack = hmrOptions.webpack || {};
6 | const vite = hmrOptions.vite || {};
7 |
8 | if (webpack.client && typeof window !== 'undefined') {
9 | const applyClientHMR = require('./webpack/client-hmr');
10 | applyClientHMR(() => this.i18nInstance);
11 | } else if (webpack.server && typeof window === 'undefined') {
12 | const applyServerHMR = require('./webpack/server-hmr');
13 | applyServerHMR(() => this.i18nInstance);
14 | } else if (vite.client && typeof window !== 'undefined') {
15 | const { applyViteClientHMR } = require('./vite/client-hmr');
16 | applyViteClientHMR(() => this.i18nInstance);
17 | }
18 | }
19 |
20 | init(i18nInstance) {
21 | this.i18nInstance = i18nInstance;
22 | }
23 |
24 | toJSON() {
25 | return null;
26 | }
27 |
28 | toString() {
29 | return 'HMRPlugin';
30 | }
31 | }
32 |
33 | module.exports.HMRPlugin = HMRPlugin;
34 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | function extractLangAndNS(changedFile, currentConfig) {
2 | const changedFileParts = changedFile.replace(/\\/g, '/').split('/');
3 |
4 | const firstLongestNSMatchParts = []
5 | .concat(currentConfig.namespaces)
6 | .map((ns) => ns.split('/'))
7 | .sort((a, b) => b.length - a.length)
8 | .find((optionalNS) =>
9 | optionalNS.every((optionalNSPart) => changedFileParts.includes(optionalNSPart))
10 | );
11 |
12 | if (!firstLongestNSMatchParts) {
13 | return { lang: null, ns: null };
14 | }
15 |
16 | const lang = changedFileParts
17 | .filter(
18 | (part) => !firstLongestNSMatchParts.includes(part) && currentConfig.languages.includes(part)
19 | )
20 | .join('/');
21 |
22 | return {
23 | lang,
24 | ns: firstLongestNSMatchParts.join('/'),
25 | };
26 | }
27 |
28 | function printList(list) {
29 | return list.map((item) => `${item.lang}/${item.ns}`).join(', ');
30 | }
31 |
32 | function extractList(changedFiles, i18nInstance) {
33 | const namespaces = uniqueList(
34 | []
35 | .concat(
36 | i18nInstance.options.ns,
37 | i18nInstance.options.fallbackNS,
38 | i18nInstance.options.defaultNS
39 | )
40 | .filter(Boolean)
41 | );
42 | const languages = uniqueList(
43 | [].concat(
44 | i18nInstance.languages,
45 | i18nInstance.options.supportedLngs,
46 | i18nInstance.options.lng,
47 | i18nInstance.options.fallbackLng
48 | )
49 | );
50 |
51 | return changedFiles
52 | .map((changedFile) => extractLangAndNS(changedFile, { namespaces, languages }))
53 | .filter(({ lang, ns }) => Boolean(lang) && Boolean(ns));
54 | }
55 |
56 | function uniqueList(list) {
57 | return [...new Set(list)];
58 | }
59 |
60 | function createLoggerOnce(logger) {
61 | const msgCount = new Map();
62 | return (msg, type = 'log') => {
63 | const count = msgCount.has(msg) ? msgCount.get(msg) : 0;
64 | if (count > 0) {
65 | return;
66 | }
67 |
68 | logger(msg, type);
69 | msgCount.set(msg, count + 1);
70 | };
71 | }
72 |
73 | function log(msg, type = 'log') {
74 | console[type](`[%cI18NextHMR%c] ${msg}`, 'color:#bc93b6', '');
75 | }
76 |
77 | const logOnce = createLoggerOnce(log);
78 |
79 | async function reloadTranslations(list, i18nInstance) {
80 | let backendOptions = { queryStringParams: {} };
81 | try {
82 | backendOptions = i18nInstance.services.backendConnector.backend.options;
83 | backendOptions.queryStringParams = backendOptions.queryStringParams || {};
84 | backendOptions.queryStringParams._ = new Date().getTime(); // cache killer
85 | } catch (e) {
86 | logOnce('Client i18next-http-backend not found, hmr may not work', 'warn');
87 | }
88 |
89 | const langs = uniqueList(list.map((item) => item.lang));
90 | const namespaces = uniqueList(list.map((item) => item.ns));
91 |
92 | await i18nInstance.reloadResources(langs, namespaces, (error) => {
93 | if (error) {
94 | log(error, 'error');
95 | return;
96 | }
97 |
98 | const currentLang = i18nInstance.language;
99 |
100 | if (langs.includes(currentLang)) {
101 | i18nInstance.changeLanguage(currentLang);
102 | log(`Update applied successfully`);
103 | } else {
104 | log(`Resources of '${printList(list)}' were reloaded successfully`);
105 | }
106 | });
107 | }
108 |
109 | module.exports = {
110 | printList: printList,
111 | extractList: extractList,
112 | log: log,
113 | createLoggerOnce: createLoggerOnce,
114 | uniqueList: uniqueList,
115 | reloadTranslations: reloadTranslations,
116 | };
117 |
--------------------------------------------------------------------------------
/lib/vite/client-hmr.js:
--------------------------------------------------------------------------------
1 | export function applyViteClientHMR(i18nOrGetter) {
2 | if (import.meta.hot) {
3 | const { extractList, printList, reloadTranslations, log } = require('../utils');
4 |
5 | log('Client HMR has started');
6 |
7 | import.meta.hot.on('i18next-hmr:locale-changed', ({ changedFiles }) => {
8 | const i18nInstance =
9 | typeof i18nOrGetter === 'function' ? i18nOrGetter({ changedFiles }) : i18nOrGetter;
10 |
11 | const list = extractList(changedFiles, i18nInstance);
12 |
13 | if (!list.length) {
14 | return;
15 | }
16 |
17 | log(`Got an update with ${printList(list)}`);
18 |
19 | return reloadTranslations(list, i18nInstance);
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/vite/plugin.d.ts:
--------------------------------------------------------------------------------
1 | interface VitePlugin {
2 | name: string;
3 | }
4 | export declare function i18nextHMRPlugin(
5 | options: { localesDir: string } | { localesDirs: string[] }
6 | ): VitePlugin;
7 |
--------------------------------------------------------------------------------
/lib/vite/plugin.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports.i18nextHMRPlugin = function i18nextHMRPlugin(options) {
4 | let absoluteLocaleDirs = [];
5 |
6 | return {
7 | name: 'i18next-hmr',
8 | configResolved(config) {
9 | absoluteLocaleDirs = []
10 | .concat(options.localesDir, options.localesDirs)
11 | .filter(Boolean)
12 | .map((localeDir) =>
13 | path.isAbsolute(localeDir)
14 | ? localeDir
15 | : path.resolve(config.root || process.cwd(), localeDir)
16 | );
17 | },
18 | handleHotUpdate({ file, server }) {
19 | const relevantLocaleDir = absoluteLocaleDirs.find((dir) => file.startsWith(dir));
20 | if (relevantLocaleDir) {
21 | const fileExt = path.extname(file);
22 | server.ws.send('i18next-hmr:locale-changed', {
23 | changedFiles: [
24 | path.relative(relevantLocaleDir, file).slice(0, -1 * fileExt.length || undefined),
25 | ],
26 | });
27 | }
28 | },
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/lib/webpack/client-hmr.js:
--------------------------------------------------------------------------------
1 | module.exports = function applyClientHMR(i18nOrGetter) {
2 | if (module.hot) {
3 | const { extractList, printList, reloadTranslations, log } = require('../utils');
4 | const { changedFile } = require('./trigger.js');
5 |
6 | if (!changedFile || changedFile) {
7 | // We must use the required variable
8 | log('Client HMR has started');
9 | }
10 |
11 | module.hot.accept('./trigger.js', () => {
12 | const { changedFiles } = require('./trigger.js');
13 | const i18nInstance =
14 | typeof i18nOrGetter === 'function' ? i18nOrGetter({ changedFiles }) : i18nOrGetter;
15 |
16 | const list = extractList(changedFiles, i18nInstance);
17 |
18 | if (!list.length) {
19 | return;
20 | }
21 |
22 | log(`Got an update with ${printList(list)}`);
23 |
24 | return reloadTranslations(list, i18nInstance);
25 | });
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/lib/webpack/loader.js:
--------------------------------------------------------------------------------
1 | module.exports = function (content) {
2 | this.query.localesDirs.forEach((dir) => this.addContextDependency(dir));
3 | return (
4 | content.replace(`'__PLACEHOLDER__'`, JSON.stringify(this.query.getChangedLang())) +
5 | '//' +
6 | new Date().getTime()
7 | );
8 | };
9 |
--------------------------------------------------------------------------------
/lib/webpack/plugin.d.ts:
--------------------------------------------------------------------------------
1 | export declare class I18NextHMRPlugin {
2 | static addListener(cb: (data: { lang: string; ns: string }) => void): void;
3 |
4 | constructor(options: { localesDir: string; } | { localesDirs: string[] });
5 | apply: (compiler: any) => void;
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/lib/webpack/plugin.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | const pluginName = 'I18nextHMRPlugin';
5 |
6 | const DEFAULT_OPTIONS = {
7 | localesDir: '',
8 | localesDirs: [],
9 | };
10 |
11 | class I18NextHMRPlugin {
12 | constructor(options) {
13 | this.options = { ...DEFAULT_OPTIONS, ...options };
14 | this.options.localesDirs = []
15 | .concat(this.options.localesDirs, this.options.localesDir)
16 | .filter(Boolean);
17 | this.lastUpdate = { changedFiles: [] };
18 | }
19 |
20 | apply(compiler) {
21 | const isWebpack5 = compiler.webpack
22 | ? +compiler.webpack.version.split('.').reverse().pop() === 5
23 | : false;
24 |
25 | compiler.hooks.beforeCompile.tapAsync(pluginName, (params, callback) => {
26 | const noneExistsDirs = this.options.localesDirs.filter((dir) => !fs.existsSync(dir));
27 |
28 | if (noneExistsDirs.length === 0) {
29 | return callback();
30 | }
31 | throw new Error(
32 | `i18next-hmr: \n'${noneExistsDirs.join(`',\n'`)}'${
33 | noneExistsDirs.length > 1 ? '\nare' : ''
34 | } not found`
35 | );
36 | });
37 |
38 | compiler.hooks.environment.tap(pluginName, () => {
39 | compiler.options.module?.rules.push({
40 | resource: path.resolve(__dirname, 'trigger.js'),
41 | loader: path.resolve(__dirname, 'loader.js'), // Path to loader
42 | options: {
43 | localesDirs: this.options.localesDirs,
44 | getChangedLang: () => ({ ...this.lastUpdate }),
45 | },
46 | });
47 | });
48 |
49 | compiler.hooks.watchRun.tap(pluginName, (compiler) => {
50 | const watcher = (compiler.watchFileSystem.wfs || compiler.watchFileSystem).watcher;
51 |
52 | if (!watcher) {
53 | return;
54 | }
55 |
56 | const changedTimes = isWebpack5 ? watcher.getTimes() : watcher.mtimes;
57 |
58 | const { startTime = 0 } = watcher || {};
59 |
60 | const files = Object.keys(changedTimes).filter((file) => {
61 | const fileExt = path.extname(file);
62 |
63 | return (
64 | this.options.localesDirs.some((dir) => file.startsWith(dir)) &&
65 | !!fileExt &&
66 | changedTimes[file] > startTime
67 | );
68 | });
69 |
70 | if (!files.length) {
71 | return;
72 | }
73 |
74 | const changedFiles = files.map((file) => {
75 | const fileExt = path.extname(file);
76 | const dir = this.options.localesDirs.find((dir) => file.startsWith(dir));
77 |
78 | return path.relative(dir, file).slice(0, -1 * fileExt.length || undefined); // keep all when fileExt.length === 0
79 | });
80 |
81 | this.lastUpdate = { changedFiles };
82 |
83 | I18NextHMRPlugin.callbacks.forEach((cb) => cb({ changedFiles }));
84 | });
85 | }
86 | }
87 |
88 | I18NextHMRPlugin.callbacks = [];
89 |
90 | I18NextHMRPlugin.addListener = function (cb) {
91 | I18NextHMRPlugin.callbacks.length = 0;
92 | I18NextHMRPlugin.callbacks.push(cb);
93 | };
94 |
95 | module.exports.I18NextHMRPlugin = I18NextHMRPlugin;
96 |
--------------------------------------------------------------------------------
/lib/webpack/server-hmr.js:
--------------------------------------------------------------------------------
1 | const { extractList, printList, uniqueList, createLoggerOnce } = require('../utils');
2 |
3 | module.exports = function applyServerHMR(i18nOrGetter) {
4 | const pluginName = `\x1b[35m\x1b[1m${'I18NextHMR'}\x1b[0m\x1b[39m`;
5 | function log(message, type = 'log') {
6 | console[type](`[ ${pluginName} ] ${message}`);
7 | }
8 |
9 | const logOnce = createLoggerOnce(log);
10 |
11 | function reloadServerTranslation({ changedFiles }) {
12 | const i18nInstance =
13 | typeof i18nOrGetter === 'function' ? i18nOrGetter({ changedFiles }) : i18nOrGetter;
14 |
15 | if (!i18nInstance) {
16 | return;
17 | }
18 |
19 | const list = extractList(changedFiles, i18nInstance);
20 |
21 | if (list.length === 0) {
22 | return;
23 | }
24 |
25 | log(`Got an update with ${printList(list)}`);
26 |
27 | const langs = uniqueList(list.map((item) => item.lang));
28 | const namespaces = uniqueList(list.map((item) => item.ns));
29 |
30 | i18nInstance.reloadResources(langs, namespaces, (error) => {
31 | if (error) {
32 | log(`\x1b[31m\x1b[1m${error}\x1b[0m\x1b[39m`);
33 | } else {
34 | log(`Server reloaded locale of ${printList(list)} successfully`);
35 | }
36 | });
37 | }
38 |
39 | if (module.hot) {
40 | const { changedFile } = require('./trigger.js');
41 |
42 | if (!changedFile || changedFile) {
43 | // We must use the required variable
44 | logOnce(`Server HMR has started`);
45 | }
46 |
47 | module.hot.accept('./trigger.js', () => {
48 | const changedFiles = require('./trigger.js');
49 | reloadServerTranslation(changedFiles);
50 | });
51 | } else {
52 | logOnce(`Server HMR has started - callback mode`);
53 |
54 | const HMRPlugin = require('./plugin').I18NextHMRPlugin;
55 | HMRPlugin.addListener(reloadServerTranslation);
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/lib/webpack/trigger.js:
--------------------------------------------------------------------------------
1 | module.exports = '__PLACEHOLDER__';
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "i18next-hmr",
3 | "version": "3.1.4",
4 | "description": "I18Next HMR🔥 webpack / vite plugin that allows reload translation resources instantly on the client & the server.",
5 | "keywords": [
6 | "i18next",
7 | "HMR",
8 | "webpack",
9 | "vite",
10 | "plugin"
11 | ],
12 | "homepage": "https://github.com/felixmosh/i18next-hmr",
13 | "bugs": "https://github.com/felixmosh/i18next-hmr/issues",
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/felixmosh/i18next-hmr"
17 | },
18 | "license": "MIT",
19 | "author": "felixmosh",
20 | "exports": {
21 | "./plugin": "./lib/plugin.js",
22 | "./webpack": "./lib/webpack/plugin.js",
23 | "./vite": "./lib/vite/plugin.js"
24 | },
25 | "typesVersions": {
26 | "*": {
27 | "plugin": [
28 | "./lib/plugin.d.ts"
29 | ],
30 | "webpack": [
31 | "./lib/webpack/plugin.d.ts"
32 | ],
33 | "vite": [
34 | "./lib/vite/plugin.d.ts"
35 | ]
36 | }
37 | },
38 | "scripts": {
39 | "test": "jest",
40 | "version": "auto-changelog -p && git add CHANGELOG.md",
41 | "release": "release-it --only-version"
42 | },
43 | "jest": {
44 | "testEnvironment": "node",
45 | "testMatch": [
46 | "**/__tests__/**/*.spec.js"
47 | ],
48 | "testPathIgnorePatterns": [
49 | "/node_modules/",
50 | "/dist/"
51 | ],
52 | "transform": {
53 | "(client|server)-hmr\\.js": "/__tests__/utils/preprocessor.js"
54 | }
55 | },
56 | "devDependencies": {
57 | "@types/jest": "^28.1.6",
58 | "@types/node": "^18.0.6",
59 | "auto-changelog": "^2.4.0",
60 | "jest": "^29.7.0",
61 | "prettier": "^3.0.3",
62 | "release-it": "^16.1.5"
63 | },
64 | "release-it": {
65 | "git": {
66 | "changelog": "npx auto-changelog --stdout --commit-limit false -u --template https://raw.githubusercontent.com/release-it/release-it/master/templates/changelog-compact.hbs"
67 | },
68 | "hooks": {
69 | "before:init": "yarn test",
70 | "after:bump": "npx auto-changelog -p"
71 | },
72 | "github": {
73 | "release": true
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100,
3 | trailingComma: 'es5',
4 | singleQuote: true,
5 | };
6 |
--------------------------------------------------------------------------------