├── .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 | [![npm](https://img.shields.io/npm/v/i18next-hmr.svg)](https://www.npmjs.com/package/i18next-hmr) 4 | ![CI](https://github.com/felixmosh/i18next-hmr/workflows/CI/badge.svg) 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 | ![screenshot](https://user-images.githubusercontent.com/9304194/71188474-b1f97100-2289-11ea-9363-257f8a2124b1.gif) 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 |