@bleedingdev/modern-js-i18n-runtime-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.1

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 (104) hide show
  1. package/dist/cjs/index.js +10 -10
  2. package/dist/cjs/localeRedirectPolicy.js +6 -6
  3. package/dist/cjs/native.js +160 -0
  4. package/dist/cjs/paths.js +11 -3
  5. package/dist/cjs/requestLanguage.js +195 -0
  6. package/dist/cjs/router-navigation/adapter.js +93 -0
  7. package/dist/cjs/{localisedUrls/config.js → router-navigation/index.js} +5 -14
  8. package/dist/cjs/ssrLanguageHandoff.js +94 -0
  9. package/dist/cjs/urlRewrite.js +64 -0
  10. package/dist/esm/index.mjs +1 -1
  11. package/dist/esm/localeRedirectPolicy.mjs +1 -1
  12. package/dist/esm/native.mjs +112 -0
  13. package/dist/esm/paths.mjs +7 -2
  14. package/dist/esm/requestLanguage.mjs +148 -0
  15. package/dist/esm/router-navigation/adapter.mjs +55 -0
  16. package/dist/esm/router-navigation/index.mjs +1 -0
  17. package/dist/esm/ssrLanguageHandoff.mjs +44 -0
  18. package/dist/esm/urlRewrite.mjs +26 -0
  19. package/dist/esm-node/index.mjs +1 -1
  20. package/dist/esm-node/localeRedirectPolicy.mjs +1 -1
  21. package/dist/esm-node/native.mjs +113 -0
  22. package/dist/esm-node/paths.mjs +7 -2
  23. package/dist/esm-node/requestLanguage.mjs +149 -0
  24. package/dist/esm-node/router-navigation/adapter.mjs +56 -0
  25. package/dist/esm-node/router-navigation/index.mjs +2 -0
  26. package/dist/esm-node/ssrLanguageHandoff.mjs +45 -0
  27. package/dist/esm-node/urlRewrite.mjs +27 -0
  28. package/dist/types/index.d.ts +1 -1
  29. package/dist/types/localeRedirectPolicy.d.ts +1 -1
  30. package/dist/types/native.d.ts +21 -0
  31. package/dist/types/paths.d.ts +8 -1
  32. package/dist/types/requestLanguage.d.ts +60 -0
  33. package/dist/types/router-navigation/adapter.d.ts +23 -0
  34. package/dist/types/router-navigation/index.d.ts +1 -0
  35. package/dist/types/ssrLanguageHandoff.d.ts +60 -0
  36. package/dist/types/urlRewrite.d.ts +60 -0
  37. package/package.json +101 -14
  38. package/src/index.ts +1 -1
  39. package/src/localeRedirectPolicy.ts +2 -2
  40. package/src/native.ts +212 -0
  41. package/src/paths.ts +23 -2
  42. package/src/requestLanguage.ts +311 -0
  43. package/src/router-navigation/adapter.tsx +120 -0
  44. package/src/router-navigation/index.ts +6 -0
  45. package/src/ssrLanguageHandoff.ts +138 -0
  46. package/src/urlRewrite.ts +107 -0
  47. package/dist/cjs/localisedUrls/index.js +0 -98
  48. package/dist/cjs/localisedUrls/normalise.js +0 -87
  49. package/dist/cjs/localisedUrls/pathname.js +0 -68
  50. package/dist/cjs/localisedUrls/patterns.js +0 -214
  51. package/dist/cjs/localisedUrls/redirect.js +0 -100
  52. package/dist/cjs/localisedUrls/resolve.js +0 -78
  53. package/dist/cjs/localisedUrls/routeIdentity.js +0 -118
  54. package/dist/cjs/localisedUrls/routes.js +0 -210
  55. package/dist/cjs/localisedUrls/types.js +0 -18
  56. package/dist/esm/localisedUrls/config.mjs +0 -11
  57. package/dist/esm/localisedUrls/index.mjs +0 -8
  58. package/dist/esm/localisedUrls/normalise.mjs +0 -37
  59. package/dist/esm/localisedUrls/pathname.mjs +0 -24
  60. package/dist/esm/localisedUrls/patterns.mjs +0 -152
  61. package/dist/esm/localisedUrls/redirect.mjs +0 -53
  62. package/dist/esm/localisedUrls/resolve.mjs +0 -37
  63. package/dist/esm/localisedUrls/routeIdentity.mjs +0 -77
  64. package/dist/esm/localisedUrls/routes.mjs +0 -169
  65. package/dist/esm/localisedUrls/types.mjs +0 -0
  66. package/dist/esm-node/localisedUrls/config.mjs +0 -12
  67. package/dist/esm-node/localisedUrls/index.mjs +0 -9
  68. package/dist/esm-node/localisedUrls/normalise.mjs +0 -38
  69. package/dist/esm-node/localisedUrls/pathname.mjs +0 -25
  70. package/dist/esm-node/localisedUrls/patterns.mjs +0 -153
  71. package/dist/esm-node/localisedUrls/redirect.mjs +0 -54
  72. package/dist/esm-node/localisedUrls/resolve.mjs +0 -38
  73. package/dist/esm-node/localisedUrls/routeIdentity.mjs +0 -78
  74. package/dist/esm-node/localisedUrls/routes.mjs +0 -170
  75. package/dist/esm-node/localisedUrls/types.mjs +0 -1
  76. package/dist/types/localisedUrls/config.d.ts +0 -9
  77. package/dist/types/localisedUrls/index.d.ts +0 -10
  78. package/dist/types/localisedUrls/normalise.d.ts +0 -10
  79. package/dist/types/localisedUrls/pathname.d.ts +0 -4
  80. package/dist/types/localisedUrls/patterns.d.ts +0 -11
  81. package/dist/types/localisedUrls/redirect.d.ts +0 -19
  82. package/dist/types/localisedUrls/resolve.d.ts +0 -7
  83. package/dist/types/localisedUrls/routeIdentity.d.ts +0 -24
  84. package/dist/types/localisedUrls/routes.d.ts +0 -3
  85. package/dist/types/localisedUrls/types.d.ts +0 -14
  86. package/rslib.config.mts +0 -4
  87. package/rstest.config.mts +0 -36
  88. package/src/localisedUrls/config.ts +0 -18
  89. package/src/localisedUrls/index.ts +0 -28
  90. package/src/localisedUrls/normalise.ts +0 -62
  91. package/src/localisedUrls/pathname.ts +0 -60
  92. package/src/localisedUrls/patterns.ts +0 -283
  93. package/src/localisedUrls/redirect.ts +0 -107
  94. package/src/localisedUrls/resolve.ts +0 -106
  95. package/src/localisedUrls/routeIdentity.ts +0 -107
  96. package/src/localisedUrls/routes.ts +0 -432
  97. package/src/localisedUrls/types.ts +0 -18
  98. package/tests/languageSyncController.test.ts +0 -136
  99. package/tests/localeRedirectPolicy.test.ts +0 -173
  100. package/tests/localisedUrls.test.ts +0 -532
  101. package/tests/paths.test.ts +0 -24
  102. package/tests/reactLanguageSync.test.tsx +0 -136
  103. package/tests/tsconfig.json +0 -11
  104. package/tsconfig.json +0 -11
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ I18N_SSR_HANDOFF_ELEMENT_ID: ()=>I18N_SSR_HANDOFF_ELEMENT_ID,
32
+ createI18nSsrHandoffInlineData: ()=>createI18nSsrHandoffInlineData,
33
+ readI18nSsrHandoff: ()=>readI18nSsrHandoff,
34
+ readI18nSsrHandoffFromText: ()=>readI18nSsrHandoffFromText,
35
+ serializeI18nSsrHandoff: ()=>serializeI18nSsrHandoff
36
+ });
37
+ const data_namespaceObject = require("@modern-js/renderer-core/data");
38
+ const document_namespaceObject = require("@modern-js/renderer-core/document");
39
+ const I18N_SSR_HANDOFF_ELEMENT_ID = '__modernjs_i18n_ssr__';
40
+ function assertPayload(payload) {
41
+ if ('string' != typeof payload.language || 0 === payload.language.length) throw new TypeError('An i18n SSR handoff payload requires a nonempty language.');
42
+ if (void 0 !== payload.resources && ('object' != typeof payload.resources || null === payload.resources)) throw new TypeError('An i18n SSR handoff payload resources field must be a plain object.');
43
+ }
44
+ function createI18nSsrHandoffInlineData(payload, options = {}) {
45
+ assertPayload(payload);
46
+ return {
47
+ id: options.id ?? I18N_SSR_HANDOFF_ELEMENT_ID,
48
+ payload: (0, data_namespaceObject.serializePublicData)({
49
+ language: payload.language,
50
+ ...payload.resources ? {
51
+ resources: payload.resources
52
+ } : {}
53
+ })
54
+ };
55
+ }
56
+ function serializeI18nSsrHandoff(payload, options = {}) {
57
+ return (0, document_namespaceObject.serializeInlineData)({
58
+ ...createI18nSsrHandoffInlineData(payload, options),
59
+ nonce: options.nonce
60
+ });
61
+ }
62
+ function isHandoffPayload(value) {
63
+ return Boolean(value) && 'object' == typeof value && 'string' == typeof value.language;
64
+ }
65
+ function readI18nSsrHandoffFromText(text) {
66
+ if (!text) return;
67
+ try {
68
+ const result = (0, data_namespaceObject.parsePublicData)(text);
69
+ return isHandoffPayload(result) ? result : void 0;
70
+ } catch {
71
+ return;
72
+ }
73
+ }
74
+ function readI18nSsrHandoff(options = {}) {
75
+ const doc = options.doc ?? ("u" < typeof document ? void 0 : document);
76
+ if (!doc) return;
77
+ const element = doc.getElementById(options.id ?? I18N_SSR_HANDOFF_ELEMENT_ID);
78
+ return readI18nSsrHandoffFromText(element?.textContent ?? null);
79
+ }
80
+ exports.I18N_SSR_HANDOFF_ELEMENT_ID = __webpack_exports__.I18N_SSR_HANDOFF_ELEMENT_ID;
81
+ exports.createI18nSsrHandoffInlineData = __webpack_exports__.createI18nSsrHandoffInlineData;
82
+ exports.readI18nSsrHandoff = __webpack_exports__.readI18nSsrHandoff;
83
+ exports.readI18nSsrHandoffFromText = __webpack_exports__.readI18nSsrHandoffFromText;
84
+ exports.serializeI18nSsrHandoff = __webpack_exports__.serializeI18nSsrHandoff;
85
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
86
+ "I18N_SSR_HANDOFF_ELEMENT_ID",
87
+ "createI18nSsrHandoffInlineData",
88
+ "readI18nSsrHandoff",
89
+ "readI18nSsrHandoffFromText",
90
+ "serializeI18nSsrHandoff"
91
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
92
+ Object.defineProperty(exports, '__esModule', {
93
+ value: true
94
+ });
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, getters, values)=>{
5
+ var define = (defs, kind)=>{
6
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ [kind]: defs[key]
9
+ });
10
+ };
11
+ define(getters, "get");
12
+ define(values, "value");
13
+ };
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
17
+ })();
18
+ (()=>{
19
+ __webpack_require__.r = (exports1)=>{
20
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
21
+ value: 'Module'
22
+ });
23
+ Object.defineProperty(exports1, '__esModule', {
24
+ value: true
25
+ });
26
+ };
27
+ })();
28
+ var __webpack_exports__ = {};
29
+ __webpack_require__.r(__webpack_exports__);
30
+ __webpack_require__.d(__webpack_exports__, {
31
+ createI18nUrlRewrite: ()=>createI18nUrlRewrite
32
+ });
33
+ const localised_urls_namespaceObject = require("@modern-js/runtime-extensions/localised-urls");
34
+ const external_requestLanguage_js_namespaceObject = require("./requestLanguage.js");
35
+ const createI18nUrlRewrite = (options)=>{
36
+ const { languages, getLanguage, localisedUrls, onLanguageDetected } = options;
37
+ const languageList = [
38
+ ...languages
39
+ ];
40
+ return {
41
+ input: ({ url })=>{
42
+ const pathname = url.pathname;
43
+ if (onLanguageDetected) {
44
+ const segments = pathname.split('/').filter(Boolean);
45
+ const firstSegment = segments[0];
46
+ const detected = firstSegment ? languageList.find((language)=>language.toLowerCase() === firstSegment.toLowerCase()) : void 0;
47
+ if (detected) onLanguageDetected(detected);
48
+ }
49
+ url.pathname = (0, localised_urls_namespaceObject.canonicalTargetPathname)(pathname, languageList, localisedUrls);
50
+ return url;
51
+ },
52
+ output: ({ url })=>{
53
+ url.pathname = (0, localised_urls_namespaceObject.localiseTargetPathname)(url.pathname, (0, external_requestLanguage_js_namespaceObject.languageFromPathname)(url.pathname, languageList) ?? getLanguage(), languageList, localisedUrls);
54
+ return url;
55
+ }
56
+ };
57
+ };
58
+ exports.createI18nUrlRewrite = __webpack_exports__.createI18nUrlRewrite;
59
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
60
+ "createI18nUrlRewrite"
61
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
62
+ Object.defineProperty(exports, '__esModule', {
63
+ value: true
64
+ });
@@ -1,4 +1,4 @@
1
+ export * from "@modern-js/runtime-extensions/localised-urls";
1
2
  export * from "./language-sync/controller.mjs";
2
3
  export * from "./language-sync/react.mjs";
3
4
  export * from "./localeRedirectPolicy.mjs";
4
- export * from "./localisedUrls/index.mjs";
@@ -1,4 +1,4 @@
1
- import { isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, shouldSkipLocaleRedirect, stripLanguagePrefix } from "./localisedUrls/index.mjs";
1
+ import { isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, shouldSkipLocaleRedirect, stripLanguagePrefix } from "@modern-js/runtime-extensions/localised-urls";
2
2
  const stripUrlPathPrefix = (pathname, urlPath)=>{
3
3
  const basePath = urlPath.replace('/*', '');
4
4
  if (!basePath || '/' === basePath) return pathname;
@@ -0,0 +1,112 @@
1
+ import i18next from "i18next";
2
+ import { createLatestLanguageSyncBinding } from "./language-sync/controller.mjs";
3
+ import { createRequestLanguageRedirect, languageFromPathname, resolveRequestLanguage } from "./requestLanguage.mjs";
4
+ import { createI18nSsrHandoffInlineData, readI18nSsrHandoff } from "./ssrLanguageHandoff.mjs";
5
+ import { createI18nUrlRewrite } from "./urlRewrite.mjs";
6
+ function unwrapResource(module) {
7
+ const resource = 'object' == typeof module && null !== module && 'default' in module ? module.default : module;
8
+ return resource;
9
+ }
10
+ function createNativeI18n(options, loaders) {
11
+ const { initOptions = {}, ...routing } = options;
12
+ const namespaces = [
13
+ ...new Set(Object.values(loaders).flatMap(Object.keys))
14
+ ].sort();
15
+ const defaultNamespace = 'string' == typeof initOptions.defaultNS ? initOptions.defaultNS : namespaces.includes('translation') ? 'translation' : namespaces[0] ?? 'translation';
16
+ const cookie = routing.detection?.lookupCookie ?? 'i18next';
17
+ const backend = {
18
+ type: 'backend',
19
+ init () {},
20
+ read (language, namespace, callback) {
21
+ const bundles = Object.hasOwn(loaders, language) ? loaders[language] : void 0;
22
+ const load = bundles && Object.hasOwn(bundles, namespace) ? bundles[namespace] : void 0;
23
+ if (!load) return void callback(null, {});
24
+ load().then((module)=>callback(null, unwrapResource(module)), (error)=>callback(error, false));
25
+ }
26
+ };
27
+ const currentLanguage = ()=>languageFromPathname(window.location.pathname, routing.languages, routing.basePath);
28
+ return {
29
+ languages: routing.languages,
30
+ ...void 0 === routing.localisedUrls ? {} : {
31
+ localisedUrls: routing.localisedUrls
32
+ },
33
+ resolveRequest: (request)=>resolveRequestLanguage(request, routing),
34
+ redirect: createRequestLanguageRedirect,
35
+ async create (language, resources) {
36
+ const instance = i18next.createInstance();
37
+ instance.use(backend);
38
+ await instance.init({
39
+ ...initOptions,
40
+ lng: language,
41
+ fallbackLng: routing.fallbackLanguage,
42
+ supportedLngs: [
43
+ ...routing.languages
44
+ ],
45
+ ns: namespaces,
46
+ defaultNS: defaultNamespace,
47
+ partialBundledLanguages: true,
48
+ resources: resources ? {
49
+ [language]: resources
50
+ } : {},
51
+ interpolation: {
52
+ escapeValue: false,
53
+ ...initOptions.interpolation
54
+ }
55
+ });
56
+ return instance;
57
+ },
58
+ rewrite: (getLanguage)=>createI18nUrlRewrite({
59
+ languages: routing.languages,
60
+ getLanguage,
61
+ localisedUrls: routing.localisedUrls
62
+ }),
63
+ handoff (language, instance) {
64
+ return createI18nSsrHandoffInlineData({
65
+ language,
66
+ ...instance ? {
67
+ resources: Object.fromEntries(namespaces.map((namespace)=>[
68
+ namespace,
69
+ instance.getResourceBundle(language, namespace) ?? {}
70
+ ]))
71
+ } : {}
72
+ });
73
+ },
74
+ clientHandoff () {
75
+ const handoff = readI18nSsrHandoff();
76
+ if (handoff && routing.languages.includes(handoff.language)) return handoff;
77
+ return {
78
+ language: currentLanguage() ?? routing.fallbackLanguage
79
+ };
80
+ },
81
+ syncWithRouter (router, instance) {
82
+ const sync = createLatestLanguageSyncBinding();
83
+ sync.updateCallbacks({
84
+ changeLanguage: (target, language)=>target.changeLanguage(language),
85
+ commitLanguage () {},
86
+ readLanguage: (target)=>target.language,
87
+ reportFailure: ()=>window.location.reload()
88
+ });
89
+ sync.activate(instance);
90
+ const unsubscribe = router.subscribe('onBeforeLoad', ({ toLocation })=>{
91
+ const publicHref = toLocation.maskedLocation?.publicHref ?? toLocation.publicHref;
92
+ const pathname = new URL(publicHref, window.location.origin).pathname;
93
+ const language = languageFromPathname(pathname, routing.languages, routing.basePath);
94
+ if (language) sync.request(language);
95
+ });
96
+ return ()=>{
97
+ unsubscribe();
98
+ sync.deactivate();
99
+ };
100
+ },
101
+ persist (instance) {
102
+ const persist = (language)=>{
103
+ document.documentElement.lang = language;
104
+ document.cookie = `${cookie}=${encodeURIComponent(language)}; path=/; max-age=31536000; samesite=lax`;
105
+ };
106
+ if (instance.language) persist(instance.language);
107
+ instance.on('languageChanged', persist);
108
+ return ()=>instance.off('languageChanged', persist);
109
+ }
110
+ };
111
+ }
112
+ export { createNativeI18n };
@@ -1,5 +1,10 @@
1
+ import { canonicalTargetPathname, localiseTargetPathname } from "@modern-js/runtime-extensions/localised-urls";
1
2
  import { splitUrlTarget } from "@modern-js/runtime-utils/url";
2
- import { canonicalTargetPathname, localiseTargetPathname } from "./localisedUrls/index.mjs";
3
+ const configuredLanguage = (requested, languages)=>{
4
+ const language = languages.find((candidate)=>candidate.toLowerCase() === String(requested).toLowerCase());
5
+ if (void 0 === language) throw new RangeError(`Unsupported language "${requested}"; expected one of: ${languages.join(', ')}`);
6
+ return language;
7
+ };
3
8
  const buildLocalizedUrl = (target, language, languages, localisedUrls)=>{
4
9
  const { pathname, search, hash } = splitUrlTarget(target);
5
10
  return `${localiseTargetPathname(pathname, language, languages, localisedUrls)}${search}${hash}`;
@@ -9,4 +14,4 @@ const canonicalPath = (target, config)=>{
9
14
  const { pathname, search, hash } = splitUrlTarget(target);
10
15
  return `${canonicalTargetPathname(pathname, config.languages, config.localisedUrls)}${search}${hash}`;
11
16
  };
12
- export { buildLocalizedUrl, canonicalPath, localizePath };
17
+ export { buildLocalizedUrl, canonicalPath, configuredLanguage, localizePath };
@@ -0,0 +1,148 @@
1
+ import { localiseTargetPathname, shouldSkipLocaleRedirect } from "@modern-js/runtime-extensions/localised-urls";
2
+ const DEFAULT_ORDER = [
3
+ 'querystring',
4
+ 'cookie',
5
+ 'header'
6
+ ];
7
+ const normaliseBasePath = (basePath)=>{
8
+ if (!basePath || '/' === basePath) return '';
9
+ return `/${basePath.split('/').filter(Boolean).join('/')}`;
10
+ };
11
+ const entryPathname = (pathname, basePath)=>{
12
+ const base = normaliseBasePath(basePath);
13
+ if (!base) return pathname || '/';
14
+ if (pathname === base) return '/';
15
+ return pathname.startsWith(`${base}/`) ? pathname.slice(base.length) : void 0;
16
+ };
17
+ const baseLanguage = (tag)=>tag.toLowerCase().split(/[-_]/u)[0];
18
+ const compatibleLanguages = (candidate, languages)=>{
19
+ const value = candidate?.trim().toLowerCase();
20
+ if (!value) return [];
21
+ const base = baseLanguage(value);
22
+ return [
23
+ ...languages.filter((language)=>language.toLowerCase() === value),
24
+ ...languages.filter((language)=>language.toLowerCase() === base),
25
+ ...base === value ? languages.filter((language)=>baseLanguage(language) === value) : []
26
+ ].filter((language, index, all)=>all.indexOf(language) === index);
27
+ };
28
+ const matchLanguage = (candidate, languages)=>compatibleLanguages(candidate, languages)[0];
29
+ function languageFromPathname(pathname, languages, basePath) {
30
+ const remaining = entryPathname(pathname, basePath);
31
+ if (void 0 === remaining) return;
32
+ const first = remaining.split('/').filter(Boolean)[0];
33
+ if (!first) return;
34
+ return languages.find((language)=>language.toLowerCase() === first.toLowerCase());
35
+ }
36
+ const readCookie = (header, name)=>{
37
+ if (!header) return null;
38
+ for (const part of header.split(';')){
39
+ const separator = part.indexOf('=');
40
+ if (separator < 0) continue;
41
+ if (part.slice(0, separator).trim() !== name) continue;
42
+ const raw = part.slice(separator + 1).trim();
43
+ try {
44
+ return decodeURIComponent(raw);
45
+ } catch {
46
+ return raw;
47
+ }
48
+ }
49
+ return null;
50
+ };
51
+ const QVALUE = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/;
52
+ const rangeSpecificity = (range, language)=>{
53
+ const value = range.toLowerCase().replaceAll('_', '-');
54
+ const supported = language.toLowerCase().replaceAll('_', '-');
55
+ if ('*' === value) return 0;
56
+ if (value === supported) return Number.MAX_SAFE_INTEGER;
57
+ if (supported.startsWith(`${value}-`)) return 1 + value.split('-').length;
58
+ if (baseLanguage(value) === supported) return 1;
59
+ };
60
+ const acceptedLanguages = (header, languages)=>{
61
+ const items = (header ?? '').split(',').map((item, index)=>{
62
+ const [tag, ...parameters] = item.trim().split(';');
63
+ const quality = parameters.map((parameter)=>parameter.trim()).find((parameter)=>parameter.toLowerCase().startsWith('q='));
64
+ const value = quality?.slice(2).trim();
65
+ const q = void 0 === value ? 1 : QVALUE.test(value) ? Number(value) : 0 / 0;
66
+ return {
67
+ tag: tag.trim(),
68
+ q,
69
+ index
70
+ };
71
+ }).filter((item)=>item.tag && !Number.isNaN(item.q));
72
+ const ranked = languages.flatMap((language, position)=>{
73
+ let best;
74
+ for (const item of items){
75
+ const specificity = rangeSpecificity(item.tag, language);
76
+ if (void 0 !== specificity && (!best || specificity > best.specificity)) best = {
77
+ q: item.q,
78
+ index: item.index,
79
+ specificity
80
+ };
81
+ }
82
+ return best && best.q > 0 ? [
83
+ {
84
+ language,
85
+ position,
86
+ ...best
87
+ }
88
+ ] : [];
89
+ });
90
+ return ranked.sort((left, right)=>right.q - left.q || left.index - right.index || right.specificity - left.specificity || left.position - right.position).map((entry)=>entry.language);
91
+ };
92
+ function detectRequestLanguage(request, languages, detection = {}) {
93
+ const url = new URL(request.url);
94
+ for (const detector of detection.order ?? DEFAULT_ORDER){
95
+ let language;
96
+ if ('querystring' === detector) language = matchLanguage(url.searchParams.get(detection.lookupQuerystring ?? 'lng'), languages);
97
+ else if ('cookie' === detector) language = matchLanguage(readCookie(request.headers.get('cookie'), detection.lookupCookie ?? 'i18next'), languages);
98
+ else if ('header' === detector) language = acceptedLanguages(request.headers.get(detection.lookupHeader ?? 'accept-language'), languages).find(Boolean);
99
+ if (language) return language;
100
+ }
101
+ }
102
+ function resolveRequestLanguage(request, options) {
103
+ const { languages, fallbackLanguage } = options;
104
+ const url = new URL(request.url);
105
+ const fromPath = languageFromPathname(url.pathname, languages, options.basePath);
106
+ if (fromPath) return {
107
+ kind: 'language',
108
+ language: fromPath
109
+ };
110
+ const detected = (false === options.detect ? void 0 : detectRequestLanguage(request, languages, options.detection)) ?? fallbackLanguage;
111
+ const remaining = entryPathname(url.pathname, options.basePath);
112
+ if (void 0 === remaining || shouldSkipLocaleRedirect(remaining, [
113
+ ...languages
114
+ ], options.ignoreRedirectRoutes)) {
115
+ const vary = (false === options.detect ? [] : options.detection?.order ?? DEFAULT_ORDER).flatMap((detector)=>'header' === detector ? [
116
+ options.detection?.lookupHeader ?? 'Accept-Language'
117
+ ] : 'cookie' === detector ? [
118
+ 'Cookie'
119
+ ] : []);
120
+ return vary.length ? {
121
+ kind: 'language',
122
+ language: detected,
123
+ vary
124
+ } : {
125
+ kind: 'language',
126
+ language: detected
127
+ };
128
+ }
129
+ const localized = localiseTargetPathname(remaining, detected, [
130
+ ...languages
131
+ ], options.localisedUrls);
132
+ return {
133
+ kind: 'redirect',
134
+ language: detected,
135
+ location: `${normaliseBasePath(options.basePath)}${localized}${url.search}`
136
+ };
137
+ }
138
+ function createRequestLanguageRedirect(location) {
139
+ return new Response(null, {
140
+ status: 302,
141
+ headers: {
142
+ 'cache-control': 'private, no-store',
143
+ location,
144
+ vary: 'Accept-Language, Cookie'
145
+ }
146
+ });
147
+ }
148
+ export { createRequestLanguageRedirect, detectRequestLanguage, languageFromPathname, resolveRequestLanguage };
@@ -0,0 +1,55 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { RuntimeContext } from "@modern-js/runtime";
3
+ import { InternalRuntimeContext } from "@modern-js/runtime/context";
4
+ import { getRouterRuntimeState, subscribeRouterRuntimeState } from "@modern-js/runtime-extensions/router-state";
5
+ import { useCallback, useContext, useSyncExternalStore } from "react";
6
+ const subscribeNone = ()=>()=>{};
7
+ const getEmptySnapshot = ()=>null;
8
+ const createI18nRouterNavigation = ({ I18nNavigationProvider, useNativeI18nRouterAdapter })=>{
9
+ const NavigationProvider = I18nNavigationProvider;
10
+ const useIntegratedRouterAdapter = ()=>{
11
+ const runtimeContext = useContext(RuntimeContext);
12
+ const internalContext = useContext(InternalRuntimeContext);
13
+ const native = useNativeI18nRouterAdapter();
14
+ const getCapability = useCallback(()=>getRouterRuntimeState(internalContext)?.navigation ?? getRouterRuntimeState(runtimeContext)?.navigation, [
15
+ internalContext,
16
+ runtimeContext
17
+ ]);
18
+ const subscribeCapability = useCallback((update)=>{
19
+ const stopInternal = subscribeRouterRuntimeState(internalContext, update);
20
+ const stopPublic = internalContext === runtimeContext ? void 0 : subscribeRouterRuntimeState(runtimeContext, update);
21
+ return ()=>{
22
+ stopInternal();
23
+ stopPublic?.();
24
+ };
25
+ }, [
26
+ internalContext,
27
+ runtimeContext
28
+ ]);
29
+ const capability = useSyncExternalStore(subscribeCapability, getCapability, getCapability);
30
+ const snapshot = useSyncExternalStore(capability?.subscribe ?? subscribeNone, capability?.getSnapshot ?? getEmptySnapshot, capability?.getSnapshot ?? getEmptySnapshot);
31
+ if (native.hasRouter || !capability || !snapshot) return native;
32
+ const internalState = getRouterRuntimeState(internalContext);
33
+ return {
34
+ framework: internalState?.navigation === capability ? internalState.framework : getRouterRuntimeState(runtimeContext)?.framework,
35
+ hasRouter: true,
36
+ ...snapshot,
37
+ navigate: capability.navigate,
38
+ Link: capability.Link,
39
+ createLinkProps: capability.createLinkProps
40
+ };
41
+ };
42
+ const I18nRouterNavigationProvider = ({ children })=>{
43
+ const value = useIntegratedRouterAdapter();
44
+ const native = useNativeI18nRouterAdapter();
45
+ return /*#__PURE__*/ jsx(NavigationProvider, {
46
+ value: value.hasRouter && !native.hasRouter ? value : null,
47
+ children: children
48
+ });
49
+ };
50
+ return {
51
+ useIntegratedRouterAdapter,
52
+ I18nRouterNavigationProvider
53
+ };
54
+ };
55
+ export { createI18nRouterNavigation };
@@ -0,0 +1 @@
1
+ export { createI18nRouterNavigation } from "./adapter.mjs";
@@ -0,0 +1,44 @@
1
+ import { parsePublicData, serializePublicData } from "@modern-js/renderer-core/data";
2
+ import { serializeInlineData } from "@modern-js/renderer-core/document";
3
+ const I18N_SSR_HANDOFF_ELEMENT_ID = '__modernjs_i18n_ssr__';
4
+ function assertPayload(payload) {
5
+ if ('string' != typeof payload.language || 0 === payload.language.length) throw new TypeError('An i18n SSR handoff payload requires a nonempty language.');
6
+ if (void 0 !== payload.resources && ('object' != typeof payload.resources || null === payload.resources)) throw new TypeError('An i18n SSR handoff payload resources field must be a plain object.');
7
+ }
8
+ function createI18nSsrHandoffInlineData(payload, options = {}) {
9
+ assertPayload(payload);
10
+ return {
11
+ id: options.id ?? I18N_SSR_HANDOFF_ELEMENT_ID,
12
+ payload: serializePublicData({
13
+ language: payload.language,
14
+ ...payload.resources ? {
15
+ resources: payload.resources
16
+ } : {}
17
+ })
18
+ };
19
+ }
20
+ function serializeI18nSsrHandoff(payload, options = {}) {
21
+ return serializeInlineData({
22
+ ...createI18nSsrHandoffInlineData(payload, options),
23
+ nonce: options.nonce
24
+ });
25
+ }
26
+ function isHandoffPayload(value) {
27
+ return Boolean(value) && 'object' == typeof value && 'string' == typeof value.language;
28
+ }
29
+ function readI18nSsrHandoffFromText(text) {
30
+ if (!text) return;
31
+ try {
32
+ const result = parsePublicData(text);
33
+ return isHandoffPayload(result) ? result : void 0;
34
+ } catch {
35
+ return;
36
+ }
37
+ }
38
+ function readI18nSsrHandoff(options = {}) {
39
+ const doc = options.doc ?? ("u" < typeof document ? void 0 : document);
40
+ if (!doc) return;
41
+ const element = doc.getElementById(options.id ?? I18N_SSR_HANDOFF_ELEMENT_ID);
42
+ return readI18nSsrHandoffFromText(element?.textContent ?? null);
43
+ }
44
+ export { I18N_SSR_HANDOFF_ELEMENT_ID, createI18nSsrHandoffInlineData, readI18nSsrHandoff, readI18nSsrHandoffFromText, serializeI18nSsrHandoff };
@@ -0,0 +1,26 @@
1
+ import { canonicalTargetPathname, localiseTargetPathname } from "@modern-js/runtime-extensions/localised-urls";
2
+ import { languageFromPathname } from "./requestLanguage.mjs";
3
+ const createI18nUrlRewrite = (options)=>{
4
+ const { languages, getLanguage, localisedUrls, onLanguageDetected } = options;
5
+ const languageList = [
6
+ ...languages
7
+ ];
8
+ return {
9
+ input: ({ url })=>{
10
+ const pathname = url.pathname;
11
+ if (onLanguageDetected) {
12
+ const segments = pathname.split('/').filter(Boolean);
13
+ const firstSegment = segments[0];
14
+ const detected = firstSegment ? languageList.find((language)=>language.toLowerCase() === firstSegment.toLowerCase()) : void 0;
15
+ if (detected) onLanguageDetected(detected);
16
+ }
17
+ url.pathname = canonicalTargetPathname(pathname, languageList, localisedUrls);
18
+ return url;
19
+ },
20
+ output: ({ url })=>{
21
+ url.pathname = localiseTargetPathname(url.pathname, languageFromPathname(url.pathname, languageList) ?? getLanguage(), languageList, localisedUrls);
22
+ return url;
23
+ }
24
+ };
25
+ };
26
+ export { createI18nUrlRewrite };
@@ -1,5 +1,5 @@
1
1
  import "node:module";
2
+ export * from "@modern-js/runtime-extensions/localised-urls";
2
3
  export * from "./language-sync/controller.mjs";
3
4
  export * from "./language-sync/react.mjs";
4
5
  export * from "./localeRedirectPolicy.mjs";
5
- export * from "./localisedUrls/index.mjs";
@@ -1,5 +1,5 @@
1
1
  import "node:module";
2
- import { isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, shouldSkipLocaleRedirect, stripLanguagePrefix } from "./localisedUrls/index.mjs";
2
+ import { isDefaultLocaleRedirectSkipPath, localiseTargetPathname, matchesPathPrefix, shouldSkipLocaleRedirect, stripLanguagePrefix } from "@modern-js/runtime-extensions/localised-urls";
3
3
  const stripUrlPathPrefix = (pathname, urlPath)=>{
4
4
  const basePath = urlPath.replace('/*', '');
5
5
  if (!basePath || '/' === basePath) return pathname;