@prosopo/locale 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +15 -13
  2. package/.turbo/turbo-build$colon$tsc.log +8 -5
  3. package/.turbo/turbo-build.log +16 -14
  4. package/CHANGELOG.md +47 -0
  5. package/dist/cjs/i18SharedOptions.cjs +2 -2
  6. package/dist/cjs/i18nBackend.cjs +1 -1
  7. package/dist/cjs/i18nFrontend.cjs +169 -51
  8. package/dist/cjs/i18nMiddleware.cjs +5 -2
  9. package/dist/cjs/index.cjs +3 -2
  10. package/dist/cjs/translationKey.cjs +2 -3
  11. package/dist/cjs/translations.cjs +4 -5
  12. package/dist/cjs/translator.cjs +2 -3
  13. package/dist/i18SharedOptions.d.ts +2 -2
  14. package/dist/i18SharedOptions.js +3 -3
  15. package/dist/i18SharedOptions.js.map +1 -1
  16. package/dist/i18nBackend.js +1 -1
  17. package/dist/i18nFrontend.d.ts +2 -2
  18. package/dist/i18nFrontend.d.ts.map +1 -1
  19. package/dist/i18nFrontend.js +169 -45
  20. package/dist/i18nFrontend.js.map +1 -1
  21. package/dist/i18nMiddleware.d.ts.map +1 -1
  22. package/dist/i18nMiddleware.js +3 -1
  23. package/dist/i18nMiddleware.js.map +1 -1
  24. package/dist/index.d.ts +4 -3
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +3 -3
  27. package/dist/index.js.map +1 -1
  28. package/dist/loadI18next.d.ts +2 -3
  29. package/dist/loadI18next.d.ts.map +1 -1
  30. package/dist/loadI18next.js.map +1 -1
  31. package/dist/tests/i18nFrontend.unit.test.js +218 -170
  32. package/dist/tests/i18nFrontend.unit.test.js.map +1 -1
  33. package/dist/tests/i18nMiddleware.unit.test.js +11 -2
  34. package/dist/tests/i18nMiddleware.unit.test.js.map +1 -1
  35. package/dist/tests/loadI18next.unit.test.js.map +1 -1
  36. package/dist/tests/locale.test-d.js +15 -11
  37. package/dist/tests/locale.test-d.js.map +1 -1
  38. package/dist/tests/translationKey.unit.test.js +11 -33
  39. package/dist/tests/translationKey.unit.test.js.map +1 -1
  40. package/dist/tests/translations.unit.test.js +12 -22
  41. package/dist/tests/translations.unit.test.js.map +1 -1
  42. package/dist/translationKey.d.ts +2 -3
  43. package/dist/translationKey.d.ts.map +1 -1
  44. package/dist/translationKey.js +2 -3
  45. package/dist/translationKey.js.map +1 -1
  46. package/dist/translations.d.ts +3 -2
  47. package/dist/translations.d.ts.map +1 -1
  48. package/dist/translations.js +3 -3
  49. package/dist/translations.js.map +1 -1
  50. package/dist/translator.d.ts +2 -3
  51. package/dist/translator.d.ts.map +1 -1
  52. package/dist/translator.js +2 -3
  53. package/dist/translator.js.map +1 -1
  54. package/dist/types.d.ts +16 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +3 -7
  59. package/src/i18SharedOptions.ts +3 -3
  60. package/src/i18nFrontend.ts +240 -73
  61. package/src/i18nMiddleware.ts +6 -1
  62. package/src/index.ts +9 -3
  63. package/src/loadI18next.ts +7 -8
  64. package/src/tests/i18nFrontend.unit.test.ts +310 -216
  65. package/src/tests/i18nMiddleware.unit.test.ts +21 -2
  66. package/src/tests/loadI18next.unit.test.ts +11 -11
  67. package/src/tests/locale.test-d.ts +24 -23
  68. package/src/tests/translationKey.unit.test.ts +12 -38
  69. package/src/tests/translations.unit.test.ts +18 -31
  70. package/src/translationKey.ts +10 -9
  71. package/src/translations.ts +6 -5
  72. package/src/translator.ts +6 -7
  73. package/src/types.ts +42 -0
  74. package/tsconfig.cjs.json +3 -0
  75. package/tsconfig.json +3 -0
  76. package/tsconfig.tsbuildinfo +1 -1
@@ -1,10 +1,12 @@
1
1
 
2
- > @prosopo/locale@3.5.0 build:cjs
2
+ > @prosopo/locale@3.6.0 build:cjs
3
3
  > NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
4
4
 
5
5
  ViteCommonJSConfig: .
6
- { tsConfigPaths: [] }
7
- { externals: [] }
6
+ {
7
+ tsConfigPaths: [ '/home/runner/work/captcha/captcha/packages/util/tsconfig.json' ]
8
+ }
9
+ { externals: [ '@prosopo/util' ] }
8
10
  The plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the resolve.tsconfigPaths option. You can remove the plugin and set resolve.tsconfigPaths: true in your Vite config instead.
9
11
  vite v8.1.5 building ssr environment for production...
10
12
  Bundle build started
@@ -13,20 +15,20 @@ Bundle build started
13
15
  rendering chunks...
14
16
  computing gzip size...
15
17
  dist/cjs/util.cjs 0.28 kB │ gzip: 0.17 kB
16
- dist/cjs/i18nMiddleware.cjs 0.36 kB │ gzip: 0.21 kB
17
- dist/cjs/i18SharedOptions.cjs 0.41 kB │ gzip: 0.25 kB
18
- dist/cjs/translationKey.cjs 0.74 kB │ gzip: 0.38 kB
18
+ dist/cjs/i18SharedOptions.cjs 0.39 kB │ gzip: 0.24 kB
19
+ dist/cjs/i18nMiddleware.cjs 0.51 kB │ gzip: 0.26 kB
20
+ dist/cjs/translationKey.cjs 0.68 kB │ gzip: 0.35 kB
19
21
  dist/cjs/process.cjs 0.78 kB │ gzip: 0.46 kB
20
- dist/cjs/index.cjs 0.83 kB │ gzip: 0.27 kB
21
- dist/cjs/translations.cjs 0.86 kB │ gzip: 0.46 kB
22
- dist/cjs/translator.cjs 0.89 kB │ gzip: 0.49 kB
22
+ dist/cjs/translations.cjs 0.80 kB │ gzip: 0.40 kB
23
+ dist/cjs/translator.cjs 0.83 kB │ gzip: 0.45 kB
24
+ dist/cjs/index.cjs 0.87 kB │ gzip: 0.27 kB
23
25
  dist/cjs/_virtual/_rolldown/runtime.cjs 1.00 kB │ gzip: 0.50 kB
24
26
  dist/cjs/i18nBackend.cjs 1.47 kB │ gzip: 0.62 kB
25
27
  dist/cjs/loadI18next.cjs 2.03 kB │ gzip: 0.84 kB
26
- dist/cjs/i18nFrontend.cjs 2.35 kB │ gzip: 0.83 kB
28
+ dist/cjs/i18nFrontend.cjs 6.07 kB │ gzip: 2.44 kB
27
29
  dist/cjs/locales/en/translation.cjs 16.64 kB │ gzip: 6.01 kB
28
30
 
29
- ✓ built in 92ms
31
+ ✓ built in 40ms
30
32
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/ar/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/ar/translation.json
31
33
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/az/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/az/translation.json
32
34
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/cs/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/cs/translation.json
@@ -34,15 +36,15 @@ dist/cjs/locales/en/translation.cjs 16.64 kB │ gzip: 6.01 kB
34
36
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/el/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/el/translation.json
35
37
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/en/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/en/translation.json
36
38
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/es/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/es/translation.json
39
+ [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/fi/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/fi/translation.json
37
40
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/fr/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/fr/translation.json
38
41
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/hi/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/hi/translation.json
39
- [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/fi/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/fi/translation.json
40
42
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/hu/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/hu/translation.json
41
43
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/id/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/id/translation.json
42
44
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/it/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/it/translation.json
43
45
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/ja/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/ja/translation.json
44
- [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/jv/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/jv/translation.json
45
46
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/ko/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/ko/translation.json
47
+ [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/jv/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/jv/translation.json
46
48
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/ml/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/ml/translation.json
47
49
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/ms/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/ms/translation.json
48
50
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/nl/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/nl/translation.json
@@ -1,14 +1,17 @@
1
1
 
2
- > @prosopo/locale@3.5.0 build:tsc
2
+ > @prosopo/locale@3.6.0 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 11:03:35 AM - Projects in this build:
5
+ 3:28:19 PM - Projects in this build:
6
6
  * ../../dev/config/tsconfig.json
7
+ * ../util/tsconfig.json
7
8
  * tsconfig.json
8
9
 
9
- 11:03:35 AM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/NodejsPolarsDirnamePlugin.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
10
+ 3:28:19 PM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
10
11
 
11
- 11:03:35 AM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
12
+ 3:28:19 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
12
13
 
13
- 11:03:35 AM - Building project '/home/runner/work/captcha/captcha/packages/locale/tsconfig.json'...
14
+ 3:28:19 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
15
+
16
+ 3:28:19 PM - Building project '/home/runner/work/captcha/captcha/packages/locale/tsconfig.json'...
14
17
 
@@ -1,14 +1,16 @@
1
1
 
2
- > @prosopo/locale@3.5.0 build
2
+ > @prosopo/locale@3.6.0 build
3
3
  > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
4
 
5
5
 
6
- > @prosopo/locale@3.5.0 build:cross-env
6
+ > @prosopo/locale@3.6.0 build:cross-env
7
7
  > vite build --config vite.esm.config.ts --mode production
8
8
 
9
9
  ViteEsmConfig: .
10
- { tsConfigPaths: [] }
11
- { externals: [] }
10
+ {
11
+ tsConfigPaths: [ '/home/runner/work/captcha/captcha/packages/util/tsconfig.json' ]
12
+ }
13
+ { externals: [ '@prosopo/util' ] }
12
14
  The plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the resolve.tsconfigPaths option. You can remove the plugin and set resolve.tsconfigPaths: true in your Vite config instead.
13
15
  vite v8.1.5 building ssr environment for production...
14
16
  Bundle build started
@@ -18,22 +20,22 @@ rendering chunks...
18
20
  computing gzip size...
19
21
  dist/_virtual/_rolldown/runtime.js 0.04 kB │ gzip: 0.06 kB
20
22
  dist/util.js 0.25 kB │ gzip: 0.16 kB
21
- dist/i18nMiddleware.js 0.27 kB │ gzip: 0.18 kB
22
- dist/i18SharedOptions.js 0.37 kB │ gzip: 0.25 kB
23
- dist/index.js 0.47 kB │ gzip: 0.20 kB
24
- dist/translationKey.js 0.68 kB │ gzip: 0.37 kB
25
- dist/translations.js 0.70 kB │ gzip: 0.39 kB
23
+ dist/i18nMiddleware.js 0.31 kB │ gzip: 0.19 kB
24
+ dist/i18SharedOptions.js 0.35 kB │ gzip: 0.24 kB
25
+ dist/index.js 0.48 kB │ gzip: 0.20 kB
26
+ dist/translationKey.js 0.63 kB │ gzip: 0.34 kB
27
+ dist/translations.js 0.74 kB │ gzip: 0.40 kB
26
28
  dist/process.js 0.77 kB │ gzip: 0.46 kB
27
- dist/translator.js 0.85 kB │ gzip: 0.48 kB
29
+ dist/translator.js 0.79 kB │ gzip: 0.44 kB
28
30
  dist/i18nBackend.js 1.01 kB │ gzip: 0.53 kB
29
- dist/i18nFrontend.js 1.64 kB │ gzip: 0.74 kB
30
31
  dist/loadI18next.js 1.93 kB │ gzip: 0.83 kB
32
+ dist/i18nFrontend.js 5.98 kB │ gzip: 2.42 kB
31
33
  dist/locales/en/translation.js 16.41 kB │ gzip: 5.97 kB
32
34
 
33
- ✓ built in 79ms
35
+ ✓ built in 30ms
34
36
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/ar/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/ar/translation.json
35
- [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/az/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/az/translation.json
36
37
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/cs/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/cs/translation.json
38
+ [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/az/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/az/translation.json
37
39
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/de/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/de/translation.json
38
40
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/el/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/el/translation.json
39
41
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/en/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/en/translation.json
@@ -61,5 +63,5 @@ dist/locales/en/translation.js 16.41 kB │ gzip: 5.97 kB
61
63
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/th/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/th/translation.json
62
64
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/tr/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/tr/translation.json
63
65
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/uk/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/uk/translation.json
64
- [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/vi/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/vi/translation.json
65
66
  [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/zh-CN/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/zh-CN/translation.json
67
+ [copy-plugin] copying /home/runner/work/captcha/captcha/packages/locale/src/locales/vi/translation.json to /home/runner/work/captcha/captcha/packages/locale/dist/locales/vi/translation.json
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @prosopo/locale
2
2
 
3
+ ## 3.6.0
4
+ ### Minor Changes
5
+
6
+ - a9141c3: Stop shipping i18next to the browser. Saves about 22KB gzipped off the widget.
7
+
8
+ The widget was pulling in i18next and four of its plugins — a language detector, an HTTP backend, a chained backend and a resources-to-backend adapter, which between them also dragged in the `cross-fetch` polyfill — to look up 444 short strings with no plurals, no nesting and two interpolated values. That machinery is about 23KB gzipped; the replacement is 1.25KB.
9
+
10
+ `i18nFrontend.ts` now does the job directly: pick a language, fetch the matching `locales/<lang>/translation.json` next to the bundle, and look keys up in it. Behaviour is unchanged in the ways a visitor can see:
11
+
12
+ - language is chosen from the widget's own setting first, then a cookie, then localStorage, then the browser — the same order as before, and the choice is still remembered in both cookie and localStorage
13
+ - a regional tag like `de-AT` still resolves to `de`
14
+ - English is still fetched alongside the chosen language, so a key a translation is missing still renders English rather than its key
15
+ - `{{name}}` placeholders are still filled in, including in a caller's `defaultValue`
16
+ - if the catalogue cannot be fetched the widget still renders, in English, rather than waiting
17
+
18
+ i18next stays for the server, where `i18next-http-middleware` needs the real instance to read the `Accept-Language` header. `Ti18n` is now a small interface describing the handful of methods this repository actually calls, which both implementations satisfy, so `@prosopo/common` no longer needs i18next for a type either.
19
+
20
+ Covered by 189 tests in `@prosopo/locale`, and the built bundle was checked in a real browser: French picked up from the browser, a switch to German, interpolation, and both fallbacks.
21
+ - a9141c3: Stop loading zod before the widget can draw itself. Takes another 13KB gzipped off the critical path.
22
+
23
+ zod is 14KB gzipped and it was being downloaded and parsed before the checkbox appeared, because six small things on the startup path happened to use it:
24
+
25
+ - two lists of strings in `@prosopo/logger` (log levels, output format)
26
+ - two lists of strings in `@prosopo/locale` (language codes, translation keys)
27
+ - two lists of two strings in `@prosopo/types` (start mode, challenge placement)
28
+ - one four-field object in `@prosopo/load-balancer` (a provider entry)
29
+ - an `instanceof ZodError` check in `@prosopo/common`
30
+ - `INPUT_LIMITS`, a plain table of numbers, that happened to live in the same file as zod-based string builders
31
+
32
+ None of these need a validation library. They are now plain TypeScript: a list, a type, and where input is untrusted, a one-line guard. `INPUT_LIMITS` moved to its own file so reading it no longer drags the builders along.
33
+
34
+ zod has not gone anywhere — the real request and response schemas in `@prosopo/types` still use it, and still validate exactly as before. It now arrives with the code that needs it, after the widget is on screen, rather than in front of it.
35
+
36
+ Two API changes for anyone importing these directly:
37
+
38
+ - `LanguageSchema`, `TranslationKeysSchema`, `StartModeSchema` and `Placement` are no longer exported as zod schemas. Use `isLanguage()`, `isStartMode()`, `isPlacement()` to check a value, and `LanguageCodes`, `translationKeys`, `StartModes`, `Placements` for the lists.
39
+ - `isZodError()` now recognises a zod error by its name rather than `instanceof`. That is strictly more tolerant: the name still matches when an error crosses a realm boundary or comes from a second copy of zod, which `instanceof` misses — it was already the fallback arm of the same check.
40
+
41
+ Two behaviour notes: a malformed entry in the fetched provider list now throws a plain `Error` naming the entry, where it used to throw an untranslated zod error; and the language codes accepted are unchanged.
42
+
43
+ Covered by the existing suites for every package touched (types, types-database, locale, logger, common, load-balancer, all five procaptcha packages, api, cli, api-express-router, server, and the provider's 1322 unit tests), all passing. The built bundle was also loaded in a real browser: the widget renders from the first eight chunks, zod arrives in the second wave, and the provider's error came back translated into German.
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [a9141c3]
48
+ - @prosopo/util@3.3.11
49
+
3
50
  ## 3.5.0
4
51
  ### Minor Changes
5
52
 
@@ -1,9 +1,9 @@
1
1
  const require_translations = require("./translations.cjs");
2
2
  var i18nSharedOptions = {
3
3
  debug: require("./process.cjs").getProcess()?.env?.PROSOPO_LOG_LEVEL === "debug",
4
- fallbackLng: require_translations.LanguageSchema.enum.en,
4
+ fallbackLng: require_translations.Languages.english,
5
5
  namespace: "translation",
6
- supportedLngs: Object.values(require_translations.Languages),
6
+ supportedLngs: require_translations.LanguageCodes,
7
7
  nonExplicitSupportedLngs: false
8
8
  };
9
9
  //#endregion
@@ -1,9 +1,9 @@
1
1
  const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
2
2
  const require_util = require("./util.cjs");
3
3
  const require_i18SharedOptions = require("./i18SharedOptions.cjs");
4
- let i18next_http_middleware = require("i18next-http-middleware");
5
4
  let i18next = require("i18next");
6
5
  i18next = require_runtime.__toESM(i18next, 1);
6
+ let i18next_http_middleware = require("i18next-http-middleware");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_runtime.__toESM(node_path, 1);
9
9
  let i18next_fs_backend_cjs = require("i18next-fs-backend/cjs");
@@ -1,58 +1,176 @@
1
- const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
2
1
  const require_i18SharedOptions = require("./i18SharedOptions.cjs");
3
- let i18next = require("i18next");
4
- i18next = require_runtime.__toESM(i18next, 1);
5
- let i18next_browser_languagedetector = require("i18next-browser-languagedetector");
6
- i18next_browser_languagedetector = require_runtime.__toESM(i18next_browser_languagedetector, 1);
7
- let i18next_chained_backend = require("i18next-chained-backend");
8
- i18next_chained_backend = require_runtime.__toESM(i18next_chained_backend, 1);
9
- let i18next_http_backend = require("i18next-http-backend");
10
- i18next_http_backend = require_runtime.__toESM(i18next_http_backend, 1);
11
- let i18next_resources_to_backend = require("i18next-resources-to-backend");
12
- i18next_resources_to_backend = require_runtime.__toESM(i18next_resources_to_backend, 1);
2
+ let _prosopo_util = require("@prosopo/util");
13
3
  //#region src/i18nFrontend.ts
14
- var detectionOptions = { detection: {
15
- order: [
16
- "navigator",
17
- "htmlTag",
18
- "path",
19
- "subdomain"
20
- ],
21
- caches: ["localStorage", "cookie"]
22
- } };
23
- var getLoadPath = (language, namespace) => {
24
- const filename = require("url").pathToFileURL(__filename).href.split("/").pop();
25
- return `${require("url").pathToFileURL(__filename).href.replace(`/${filename}`, "")}/locales/${language}/${namespace}.json`;
4
+ var NAMESPACE = "translation";
5
+ var FALLBACK_LANGUAGE = require_i18SharedOptions.i18nSharedOptions.fallbackLng;
6
+ var STORAGE_KEY = "i18nextLng";
7
+ var COOKIE_KEY = "i18next";
8
+ var COOKIE_MAX_AGE_SECONDS = 10080 * 60;
9
+ /**
10
+ * How long to wait for a catalogue before rendering untranslated.
11
+ *
12
+ * A widget with no text is worse than a widget in English: every caller
13
+ * supplies a `defaultValue`, and `t()` falls back to the key otherwise, so a
14
+ * catalogue that never arrives degrades rather than blocks.
15
+ */
16
+ var CATALOGUE_FETCH_TIMEOUT_MS = 1e4;
17
+ var SUPPORTED_LANGUAGES = require_i18SharedOptions.i18nSharedOptions.supportedLngs;
18
+ /**
19
+ * Locale JSON sits next to the bundle that asked for it. The widget is embedded
20
+ * by third parties and cannot know a base URL, but it does know where its own
21
+ * module was served from.
22
+ */
23
+ var catalogueUrl = (language) => {
24
+ const moduleUrl = require("url").pathToFileURL(__filename).href;
25
+ return `${moduleUrl.slice(0, moduleUrl.lastIndexOf("/"))}/locales/${language}/${NAMESPACE}.json`;
26
26
  };
27
+ /**
28
+ * `en-GB` is not in the catalogue list but `en` is, so a regional tag resolves
29
+ * to its base language rather than falling all the way back to English.
30
+ */
31
+ var resolveLanguage = (candidate) => {
32
+ if (!candidate) return;
33
+ if (SUPPORTED_LANGUAGES.includes(candidate)) return candidate;
34
+ const base = candidate.split("-")[0];
35
+ return base && SUPPORTED_LANGUAGES.includes(base) ? base : void 0;
36
+ };
37
+ var readCookie = () => {
38
+ try {
39
+ const match = document.cookie.match(new RegExp(`(?:^|; )${COOKIE_KEY}=([^;]*)`));
40
+ return match?.[1] ? decodeURIComponent(match[1]) : void 0;
41
+ } catch {
42
+ return;
43
+ }
44
+ };
45
+ var readStorage = () => {
46
+ try {
47
+ return localStorage.getItem(STORAGE_KEY) ?? void 0;
48
+ } catch {
49
+ return;
50
+ }
51
+ };
52
+ var navigatorLanguages = () => {
53
+ try {
54
+ return navigator.languages?.length ? navigator.languages : [navigator.language];
55
+ } catch {
56
+ return [];
57
+ }
58
+ };
59
+ /**
60
+ * Cookie and localStorage are both written, matching what the widget did when
61
+ * i18next-browser-languagedetector handled this, so a visitor's choice survives
62
+ * across pages that embed the widget. Either store can throw in a sandboxed
63
+ * iframe or with third-party storage blocked; neither is load-bearing.
64
+ */
65
+ var cacheLanguage = (language) => {
66
+ try {
67
+ localStorage.setItem(STORAGE_KEY, language);
68
+ } catch {}
69
+ try {
70
+ document.cookie = `${COOKIE_KEY}=${encodeURIComponent(language)};max-age=${COOKIE_MAX_AGE_SECONDS};path=/`;
71
+ } catch {}
72
+ };
73
+ var detectLanguage = () => {
74
+ const candidates = [
75
+ readCookie(),
76
+ readStorage(),
77
+ ...navigatorLanguages()
78
+ ];
79
+ for (const candidate of candidates) {
80
+ const resolved = resolveLanguage(candidate);
81
+ if (resolved) return resolved;
82
+ }
83
+ return FALLBACK_LANGUAGE;
84
+ };
85
+ var fetchCatalogue = async (language) => {
86
+ const controller = new AbortController();
87
+ const timer = setTimeout(() => controller.abort(), CATALOGUE_FETCH_TIMEOUT_MS);
88
+ try {
89
+ const response = await fetch(catalogueUrl(language), { signal: controller.signal });
90
+ if (!response.ok) return {};
91
+ const body = await response.json();
92
+ return typeof body === "object" && body !== null ? (0, _prosopo_util.flatten)(body) : {};
93
+ } catch {
94
+ return {};
95
+ } finally {
96
+ clearTimeout(timer);
97
+ }
98
+ };
99
+ var interpolate = (template, options) => options ? template.replace(/\{\{(\w+)\}\}/g, (placeholder, name) => name in options ? String(options[name]) : placeholder) : template;
100
+ var createBrowserI18n = (requested) => {
101
+ const catalogues = /* @__PURE__ */ new Map();
102
+ const listeners = /* @__PURE__ */ new Map();
103
+ let language = resolveLanguage(requested) ?? detectLanguage();
104
+ let initialised = false;
105
+ const emit = (event) => {
106
+ for (const listener of listeners.get(event) ?? []) listener();
107
+ };
108
+ /**
109
+ * The fallback catalogue is fetched alongside the active one so a key
110
+ * missing from a translation still renders English rather than its key —
111
+ * the same key-level fallback i18next gave us via `fallbackLng`.
112
+ */
113
+ const loadCatalogues = async (target) => {
114
+ const wanted = [.../* @__PURE__ */ new Set([target, FALLBACK_LANGUAGE])].filter((candidate) => !catalogues.has(candidate));
115
+ await Promise.all(wanted.map(async (candidate) => {
116
+ catalogues.set(candidate, await fetchCatalogue(candidate));
117
+ }));
118
+ };
119
+ const lookup = (candidate, key) => catalogues.get(candidate)?.[key];
120
+ cacheLanguage(language);
121
+ const loading = loadCatalogues(language).then(() => {
122
+ initialised = true;
123
+ emit("initialized");
124
+ emit("loaded");
125
+ });
126
+ const instance = {
127
+ get language() {
128
+ return language;
129
+ },
130
+ get isInitialized() {
131
+ return initialised;
132
+ },
133
+ t: (key, options) => interpolate(lookup(language, key) ?? lookup(FALLBACK_LANGUAGE, key) ?? options?.defaultValue ?? key, options),
134
+ hasLoadedNamespace: (namespace) => namespace === NAMESPACE && catalogues.has(language),
135
+ changeLanguage: async (next) => {
136
+ const resolved = resolveLanguage(next) ?? FALLBACK_LANGUAGE;
137
+ if (resolved !== language) {
138
+ language = resolved;
139
+ cacheLanguage(resolved);
140
+ await loadCatalogues(resolved);
141
+ emit("loaded");
142
+ emit("languageChanged");
143
+ }
144
+ return instance;
145
+ },
146
+ on: (event, listener) => {
147
+ const existing = listeners.get(event) ?? /* @__PURE__ */ new Set();
148
+ existing.add(listener);
149
+ listeners.set(event, existing);
150
+ },
151
+ off: (event, listener) => {
152
+ listeners.get(event)?.delete(listener);
153
+ },
154
+ ready: async () => {
155
+ await loading;
156
+ return instance;
157
+ }
158
+ };
159
+ return instance;
160
+ };
161
+ /**
162
+ * The widget owns this instance. A host page running its own i18n must not be
163
+ * able to capture the widget's translations, and every widget on the page
164
+ * shares one catalogue fetch.
165
+ */
166
+ var instance;
27
167
  function initializeI18n(i18nLoadedCallback, lng) {
28
- if (!i18next.default.isInitialized) {
29
- i18next.default.use(i18next_chained_backend.default).use(new i18next_browser_languagedetector.default(null, {
30
- order: [
31
- "cookie",
32
- "localStorage",
33
- "navigator"
34
- ],
35
- lookupQuerystring: "lng",
36
- lookupCookie: "i18next",
37
- lookupLocalStorage: "i18nextLng",
38
- caches: ["localStorage", "cookie"]
39
- })).init({
40
- ...require_i18SharedOptions.i18nSharedOptions,
41
- ...lng ? { lng } : {},
42
- backend: {
43
- backends: [i18next_http_backend.default, (0, i18next_resources_to_backend.default)((language, namespace) => import(getLoadPath(language, namespace)))],
44
- backendOptions: [{ loadPath: getLoadPath("{{lng}}", "{{ns}}") }]
45
- },
46
- ...detectionOptions
47
- });
48
- let notified = false;
49
- i18next.default.on("loaded", () => {
50
- if (notified) return;
51
- notified = true;
52
- i18nLoadedCallback?.(i18next.default);
53
- });
54
- } else i18nLoadedCallback?.(i18next.default);
55
- return i18next.default;
168
+ if (!instance) {
169
+ const created = createBrowserI18n(lng);
170
+ instance = created;
171
+ created.ready().then(() => i18nLoadedCallback?.(created));
172
+ } else i18nLoadedCallback?.(instance);
173
+ return instance;
56
174
  }
57
175
  //#endregion
58
176
  exports.default = initializeI18n;
@@ -1,9 +1,12 @@
1
+ const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
1
2
  const require_loadI18next = require("./loadI18next.cjs");
3
+ let i18next = require("i18next");
4
+ i18next = require_runtime.__toESM(i18next, 1);
2
5
  let i18next_http_middleware = require("i18next-http-middleware");
3
6
  //#region src/i18nMiddleware.ts
4
7
  async function i18nMiddleware(options) {
5
- const i18n = await require_loadI18next.default(true);
6
- return (0, i18next_http_middleware.handle)(i18n, { ...options });
8
+ await require_loadI18next.default(true);
9
+ return (0, i18next_http_middleware.handle)(i18next.default, { ...options });
7
10
  }
8
11
  //#endregion
9
12
  exports.default = i18nMiddleware;
@@ -5,10 +5,11 @@ const require_translations = require("./translations.cjs");
5
5
  const require_util = require("./util.cjs");
6
6
  const require_translationKey = require("./translationKey.cjs");
7
7
  const require_translator = require("./translator.cjs");
8
- exports.LanguageSchema = require_translations.LanguageSchema;
8
+ exports.LanguageCodes = require_translations.LanguageCodes;
9
9
  exports.Languages = require_translations.Languages;
10
- exports.TranslationKeysSchema = require_translationKey.TranslationKeysSchema;
11
10
  exports.createTranslator = require_translator.createTranslator;
12
11
  exports.i18nMiddleware = require_i18nMiddleware.default;
13
12
  exports.isClientSide = require_util.isClientSide;
13
+ exports.isLanguage = require_translations.isLanguage;
14
14
  exports.loadI18next = require_loadI18next.default;
15
+ exports.translationKeys = require_translationKey.translationKeys;
@@ -1,5 +1,4 @@
1
1
  const require_translation = require("./locales/en/translation.cjs");
2
- let zod = require("zod");
3
2
  //#region src/translationKey.ts
4
3
  function getLeafFieldPath(obj) {
5
4
  if (typeof obj === "string") return [];
@@ -13,7 +12,7 @@ function getLeafFieldPath(obj) {
13
12
  }));
14
13
  }, []);
15
14
  }
16
- var TranslationKeysSchema = zod.z.enum(getLeafFieldPath(require_translation.default));
15
+ var translationKeys = getLeafFieldPath(require_translation.default);
17
16
  //#endregion
18
- exports.TranslationKeysSchema = TranslationKeysSchema;
19
17
  exports.getLeafFieldPath = getLeafFieldPath;
18
+ exports.translationKeys = translationKeys;
@@ -1,6 +1,3 @@
1
- const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
2
- let zod = require("zod");
3
- zod = require_runtime.__toESM(zod, 1);
4
1
  //#region src/translations.ts
5
2
  var Languages = {
6
3
  arabic: "ar",
@@ -36,7 +33,9 @@ var Languages = {
36
33
  vietnamese: "vi",
37
34
  chinese: "zh-CN"
38
35
  };
39
- var LanguageSchema = zod.default.enum(Object.values(Languages));
36
+ var LanguageCodes = Object.values(Languages);
37
+ var isLanguage = (value) => LanguageCodes.includes(value);
40
38
  //#endregion
41
- exports.LanguageSchema = LanguageSchema;
39
+ exports.LanguageCodes = LanguageCodes;
42
40
  exports.Languages = Languages;
41
+ exports.isLanguage = isLanguage;
@@ -2,9 +2,8 @@ const require_i18nFrontend = require("./i18nFrontend.cjs");
2
2
  //#region src/translator.ts
3
3
  var NAMESPACE = "translation";
4
4
  /**
5
- * Vanilla replacement for the `useTranslation` hook. i18next is already
6
- * framework-agnostic and event-driven; this just exposes its `t` plus the three
7
- * events that used to trigger a React re-render.
5
+ * Vanilla replacement for the `useTranslation` hook: the instance's `t` plus
6
+ * the three events that used to trigger a React re-render.
8
7
  */
9
8
  var createTranslator = (existing) => {
10
9
  const i18n = existing ?? require_i18nFrontend.default();
@@ -1,8 +1,8 @@
1
1
  export declare const i18nSharedOptions: {
2
2
  debug: boolean;
3
- fallbackLng: string | undefined;
3
+ fallbackLng: "en";
4
4
  namespace: string;
5
- supportedLngs: ("ar" | "az" | "cs" | "de" | "el" | "en" | "es" | "fi" | "fr" | "hi" | "hu" | "id" | "it" | "ja" | "jv" | "ko" | "ml" | "ms" | "nl" | "no" | "pl" | "pt-BR" | "pt" | "ro" | "ru" | "sr" | "sv" | "th" | "tr" | "uk" | "vi" | "zh-CN")[];
5
+ supportedLngs: readonly string[];
6
6
  nonExplicitSupportedLngs: boolean;
7
7
  };
8
8
  //# sourceMappingURL=i18SharedOptions.d.ts.map
@@ -1,10 +1,10 @@
1
- import { LanguageSchema, Languages } from "./translations.js";
1
+ import { LanguageCodes, Languages } from "./translations.js";
2
2
  import { getProcess } from "./process.js";
3
3
  var i18nSharedOptions = {
4
4
  debug: getProcess()?.env?.PROSOPO_LOG_LEVEL === "debug",
5
- fallbackLng: LanguageSchema.enum.en,
5
+ fallbackLng: Languages.english,
6
6
  namespace: "translation",
7
- supportedLngs: Object.values(Languages),
7
+ supportedLngs: LanguageCodes,
8
8
  nonExplicitSupportedLngs: false
9
9
  };
10
10
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"i18SharedOptions.js","sourceRoot":"","sources":["../src/i18SharedOptions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAM9D,MAAM,QAAQ,GAAG,UAAU,EAAE,EAAE,GAAG,EAAE,iBAAiB,CAAC;AAEtD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,KAAK,EAAE,QAAQ,KAAK,OAAO;IAC3B,WAAW,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE;IACnC,SAAS,EAAE,aAAa;IACxB,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;IACvC,wBAAwB,EAAE,KAAK;CAC/B,CAAC"}
1
+ {"version":3,"file":"i18SharedOptions.js","sourceRoot":"","sources":["../src/i18SharedOptions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAM7D,MAAM,QAAQ,GAAG,UAAU,EAAE,EAAE,GAAG,EAAE,iBAAiB,CAAC;AAEtD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,KAAK,EAAE,QAAQ,KAAK,OAAO;IAC3B,WAAW,EAAE,SAAS,CAAC,OAAO;IAC9B,SAAS,EAAE,aAAa;IACxB,aAAa,EAAE,aAAa;IAC5B,wBAAwB,EAAE,KAAK;CAC/B,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import "./_virtual/_rolldown/runtime.js";
2
2
  import { isServerSide } from "./util.js";
3
3
  import { i18nSharedOptions } from "./i18SharedOptions.js";
4
- import { LanguageDetector } from "i18next-http-middleware";
5
4
  import i18n from "i18next";
5
+ import { LanguageDetector } from "i18next-http-middleware";
6
6
  import path from "node:path";
7
7
  import FSBackend from "i18next-fs-backend/cjs";
8
8
  //#region src/i18nBackend.ts
@@ -1,4 +1,4 @@
1
- import i18n from "i18next";
2
- export declare function initializeI18n(i18nLoadedCallback?: (value: typeof i18n) => void, lng?: string): import("i18next").i18n;
1
+ import type { Ti18n } from "./types.js";
2
+ export declare function initializeI18n(i18nLoadedCallback?: (value: Ti18n) => void, lng?: string): Ti18n;
3
3
  export default initializeI18n;
4
4
  //# sourceMappingURL=i18nFrontend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"i18nFrontend.d.ts","sourceRoot":"","sources":["../src/i18nFrontend.ts"],"names":[],"mappings":"AAcA,OAAO,IAA0B,MAAM,SAAS,CAAC;AAmBjD,wBAAgB,cAAc,CAC7B,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,IAAI,KAAK,IAAI,EACjD,GAAG,CAAC,EAAE,MAAM,0BA8DZ;AAED,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"i18nFrontend.d.ts","sourceRoot":"","sources":["../src/i18nFrontend.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAa,KAAK,EAAoB,MAAM,YAAY,CAAC;AAsOrE,wBAAgB,cAAc,CAC7B,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAC3C,GAAG,CAAC,EAAE,MAAM,GACV,KAAK,CAeP;AAED,eAAe,cAAc,CAAC"}