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

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 +438 -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 +378 -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 +379 -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 +34 -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 +102 -15
  38. package/src/index.ts +1 -1
  39. package/src/localeRedirectPolicy.ts +2 -2
  40. package/src/native.ts +624 -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
@@ -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;
@@ -0,0 +1,379 @@
1
+ import "node:module";
2
+ import i18next from "i18next";
3
+ import { createLatestLanguageSyncBinding } from "./language-sync/controller.mjs";
4
+ import { createRequestLanguageRedirect, languageFromPathname, resolveRequestLanguage } from "./requestLanguage.mjs";
5
+ import { createI18nSsrHandoffInlineData, readI18nSsrHandoff } from "./ssrLanguageHandoff.mjs";
6
+ import { createI18nUrlRewrite } from "./urlRewrite.mjs";
7
+ const nativeSnapshotOrigins = new WeakMap();
8
+ function invalidateNativeI18nSnapshot(instance) {
9
+ if ('object' == typeof instance && null !== instance) nativeSnapshotOrigins.delete(instance);
10
+ }
11
+ const nativeRewriteOrigins = new WeakMap();
12
+ function getNativeI18nRewriteSnapshot(rewrite) {
13
+ return 'object' == typeof rewrite && null !== rewrite ? nativeRewriteOrigins.get(rewrite)?.() : void 0;
14
+ }
15
+ function detachLocalisedUrls(value) {
16
+ if (void 0 === value || 'boolean' == typeof value) return value;
17
+ return Object.fromEntries(Object.entries(value).map(([path, languages])=>[
18
+ path,
19
+ Object.fromEntries(Object.entries(languages))
20
+ ]));
21
+ }
22
+ function plainRecord(value) {
23
+ return 'object' == typeof value && null !== value && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
24
+ }
25
+ function detachOption(value, ancestors = new Set()) {
26
+ if (null === value || 'object' != typeof value) return value;
27
+ if (ancestors.has(value) || (Array.isArray(value) ? Object.getPrototypeOf(value) !== Array.prototype : !plainRecord(value))) throw new Error('Native i18n snapshot options must be plain acyclic data');
28
+ ancestors.add(value);
29
+ const copy = Array.isArray(value) ? [] : {};
30
+ for (const key of Reflect.ownKeys(value)){
31
+ if (Array.isArray(value) && 'length' === key) continue;
32
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
33
+ if ('string' != typeof key || !descriptor || !('value' in descriptor)) throw new Error('Native i18n snapshot options cannot contain accessors');
34
+ Object.defineProperty(copy, key, {
35
+ value: detachOption(descriptor.value, ancestors),
36
+ enumerable: descriptor.enumerable,
37
+ writable: true,
38
+ configurable: true
39
+ });
40
+ }
41
+ ancestors.delete(value);
42
+ return copy;
43
+ }
44
+ function detachInitOptions(options) {
45
+ const copy = detachOption(options);
46
+ if (!plainRecord(copy)) throw new Error('Invalid native i18n options');
47
+ return copy;
48
+ }
49
+ function descriptorProof(object, ignore = new Set(), ancestors = new Set()) {
50
+ if (ancestors.has(object)) return ()=>true;
51
+ ancestors.add(object);
52
+ const keys = Reflect.ownKeys(object).filter((key)=>'string' != typeof key || !ignore.has(key));
53
+ const proofs = keys.map((key)=>{
54
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
55
+ if (!descriptor || !('value' in descriptor)) throw new Error('Native i18n snapshot provenance cannot contain accessors');
56
+ const value = descriptor.value;
57
+ const nested = Array.isArray(value) || plainRecord(value) ? descriptorProof(value, new Set(), ancestors) : ()=>true;
58
+ return ()=>{
59
+ const current = Object.getOwnPropertyDescriptor(object, key);
60
+ return void 0 !== current && 'value' in current && current.value === value && current.enumerable === descriptor.enumerable && current.configurable === descriptor.configurable && current.writable === descriptor.writable && nested();
61
+ };
62
+ });
63
+ ancestors.delete(object);
64
+ return ()=>{
65
+ const currentKeys = Reflect.ownKeys(object).filter((key)=>'string' != typeof key || !ignore.has(key));
66
+ return currentKeys.length === keys.length && currentKeys.every((key, index)=>key === keys[index]) && proofs.every((proof)=>proof());
67
+ };
68
+ }
69
+ function methodProof(object) {
70
+ const prototype = Object.getPrototypeOf(object);
71
+ const methods = new Map();
72
+ for(let current = object; current && current !== Object.prototype; current = Object.getPrototypeOf(current))for (const key of Reflect.ownKeys(current)){
73
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
74
+ if (descriptor && 'value' in descriptor) {
75
+ if ('function' == typeof descriptor.value && !methods.has(key)) methods.set(key, descriptor.value);
76
+ }
77
+ }
78
+ return ()=>{
79
+ if (Object.getPrototypeOf(object) !== prototype) return false;
80
+ for (const [key, method] of methods){
81
+ let current = object;
82
+ while(current){
83
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
84
+ if (descriptor) {
85
+ if (!('value' in descriptor) || descriptor.value !== method) return false;
86
+ break;
87
+ }
88
+ current = Object.getPrototypeOf(current);
89
+ }
90
+ if (!current) return false;
91
+ }
92
+ return true;
93
+ };
94
+ }
95
+ function ownValue(object, key) {
96
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
97
+ if (descriptor && !('value' in descriptor)) throw new Error('Native i18n snapshot provenance cannot contain accessors');
98
+ return descriptor?.value;
99
+ }
100
+ function semanticProof(object, fields) {
101
+ const proofs = fields.map((key)=>{
102
+ const value = ownValue(object, key);
103
+ const nested = Array.isArray(value) || plainRecord(value) ? descriptorProof(value, new Set([
104
+ 'resources'
105
+ ])) : ()=>true;
106
+ if (value instanceof RegExp) {
107
+ const source = value.source;
108
+ const flags = value.flags;
109
+ return ()=>{
110
+ const current = ownValue(object, key);
111
+ return current instanceof RegExp && current.source === source && current.flags === flags;
112
+ };
113
+ }
114
+ return ()=>ownValue(object, key) === value && nested();
115
+ });
116
+ return ()=>proofs.every((proof)=>proof());
117
+ }
118
+ function recordNativeSnapshotOrigin(instance, original, seed) {
119
+ try {
120
+ const useDescriptor = Object.getOwnPropertyDescriptor(instance, 'use');
121
+ if (!useDescriptor || !('value' in useDescriptor)) throw new Error('Native plugin registration requires an own method');
122
+ const use = instance.use;
123
+ Object.defineProperty(instance, 'use', {
124
+ ...useDescriptor,
125
+ value: function(plugin) {
126
+ invalidateNativeI18nSnapshot(instance);
127
+ return use(plugin);
128
+ }
129
+ });
130
+ const services = instance.services;
131
+ const store = instance.store;
132
+ const options = instance.options;
133
+ const modules = instance.modules;
134
+ const translator = Object.getOwnPropertyDescriptor(instance, 'translator')?.value;
135
+ const formatter = instance.services.formatter;
136
+ const formats = formatter && 'object' == typeof formatter ? Object.getOwnPropertyDescriptor(formatter, 'formats')?.value : void 0;
137
+ const proofs = [
138
+ descriptorProof(instance.options, new Set([
139
+ 'resources'
140
+ ])),
141
+ descriptorProof(instance.modules),
142
+ descriptorProof(services),
143
+ methodProof(instance),
144
+ methodProof(instance.store),
145
+ ...Object.values(instance.services).filter((value)=>'object' == typeof value && null !== value).map((value)=>methodProof(value)),
146
+ ...translator && 'object' == typeof translator ? [
147
+ methodProof(translator)
148
+ ] : [],
149
+ ...formats && 'object' == typeof formats ? [
150
+ descriptorProof(formats)
151
+ ] : []
152
+ ];
153
+ const serviceFields = {
154
+ languageUtils: [
155
+ 'options',
156
+ 'supportedLngs'
157
+ ],
158
+ pluralResolver: [
159
+ 'languageUtils',
160
+ 'options'
161
+ ],
162
+ interpolator: [
163
+ 'options',
164
+ 'escape',
165
+ 'escapeValue',
166
+ 'useRawValueToEscape',
167
+ 'prefix',
168
+ 'suffix',
169
+ 'format',
170
+ 'formatSeparator',
171
+ 'unescapePrefix',
172
+ 'unescapeSuffix',
173
+ 'nestingPrefix',
174
+ 'nestingSuffix',
175
+ 'nestingOptionsSeparator',
176
+ 'maxReplaces',
177
+ 'alwaysFormat',
178
+ 'regexp',
179
+ 'regexpUnescape',
180
+ 'nestingRegexp'
181
+ ],
182
+ formatter: [
183
+ 'formatSeparator'
184
+ ],
185
+ resourceStore: [
186
+ 'options'
187
+ ],
188
+ backendConnector: [
189
+ 'backend',
190
+ 'store',
191
+ 'services',
192
+ 'options'
193
+ ]
194
+ };
195
+ const links = Object.entries(instance.services).map(([key, value])=>{
196
+ if ('object' == typeof value && null !== value) proofs.push(semanticProof(value, serviceFields[key] ?? []));
197
+ return ()=>ownValue(services, key) === value;
198
+ });
199
+ if (translator && 'object' == typeof translator) proofs.push(semanticProof(translator, [
200
+ 'options',
201
+ 'resourceStore',
202
+ 'languageUtils',
203
+ 'pluralResolver',
204
+ 'interpolator',
205
+ 'backendConnector',
206
+ 'i18nFormat',
207
+ 'utils'
208
+ ]));
209
+ nativeSnapshotOrigins.set(instance, {
210
+ instance,
211
+ original: detachInitOptions(original),
212
+ seed: detachInitOptions(seed),
213
+ unchanged: ()=>ownValue(instance, 'services') === services && ownValue(instance, 'store') === store && ownValue(instance, 'options') === options && ownValue(instance, 'modules') === modules && ownValue(services, 'formatter') === formatter && (formatter && 'object' == typeof formatter ? ownValue(formatter, 'formats') === formats : true) && Object.getOwnPropertyDescriptor(instance, 'translator')?.value === translator && (translator && 'object' == typeof translator ? ownValue(translator, 'language') === ownValue(instance, 'language') : true) && links.every((proof)=>proof()) && proofs.every((proof)=>proof())
214
+ });
215
+ } catch {}
216
+ }
217
+ function createNativeI18nInstance(initOptions) {
218
+ const original = detachInitOptions(initOptions);
219
+ const seed = {
220
+ ...detachInitOptions(original),
221
+ initAsync: false,
222
+ partialBundledLanguages: false,
223
+ interpolation: {
224
+ escapeValue: false,
225
+ ...original.interpolation
226
+ }
227
+ };
228
+ const instance = i18next.createInstance();
229
+ instance.init(detachInitOptions(seed));
230
+ if (!instance.isInitialized || instance.isInitializing) throw new Error('Native i18n instances require complete bundled resources');
231
+ recordNativeSnapshotOrigin(instance, original, seed);
232
+ return instance;
233
+ }
234
+ function getNativeI18nSnapshotSeed(instance) {
235
+ const origin = 'object' == typeof instance && null !== instance ? nativeSnapshotOrigins.get(instance) : void 0;
236
+ const services = origin ? ownValue(origin.instance, 'services') : void 0;
237
+ const connector = services && 'object' == typeof services ? ownValue(services, 'backendConnector') : void 0;
238
+ const state = connector && 'object' == typeof connector ? ownValue(connector, 'state') : void 0;
239
+ const pending = plainRecord(state) && Object.values(Object.getOwnPropertyDescriptors(state)).some((descriptor)=>!('value' in descriptor) || 1 === descriptor.value);
240
+ if (!origin || true !== ownValue(origin.instance, 'isInitialized') || true === ownValue(origin.instance, 'isInitializing') || ownValue(origin.instance, 'isLanguageChangingTo') || pending || !origin.unchanged()) throw new Error('This i18n instance requires an explicit Worker profile');
241
+ return detachInitOptions(origin.seed);
242
+ }
243
+ function unwrapResource(module) {
244
+ const resource = 'object' == typeof module && null !== module && 'default' in module ? module.default : module;
245
+ return resource;
246
+ }
247
+ function createNativeI18n(options, loaders) {
248
+ const { initOptions = {}, ...routing } = options;
249
+ const namespaces = [
250
+ ...new Set(Object.values(loaders).flatMap(Object.keys))
251
+ ].sort();
252
+ const defaultNamespace = 'string' == typeof initOptions.defaultNS ? initOptions.defaultNS : namespaces.includes('translation') ? 'translation' : namespaces[0] ?? 'translation';
253
+ const cookie = routing.detection?.lookupCookie ?? 'i18next';
254
+ const backend = {
255
+ type: 'backend',
256
+ init () {},
257
+ read (language, namespace, callback) {
258
+ const bundles = Object.hasOwn(loaders, language) ? loaders[language] : void 0;
259
+ const load = bundles && Object.hasOwn(bundles, namespace) ? bundles[namespace] : void 0;
260
+ if (!load) return void callback(null, {});
261
+ load().then((module)=>callback(null, unwrapResource(module)), (error)=>callback(error, false));
262
+ }
263
+ };
264
+ const currentLanguage = ()=>languageFromPathname(window.location.pathname, routing.languages, routing.basePath);
265
+ return {
266
+ languages: routing.languages,
267
+ ...void 0 === routing.localisedUrls ? {} : {
268
+ localisedUrls: routing.localisedUrls
269
+ },
270
+ resolveRequest: (request)=>resolveRequestLanguage(request, routing),
271
+ redirect: createRequestLanguageRedirect,
272
+ async create (language, resources) {
273
+ const instance = i18next.createInstance();
274
+ instance.use(backend);
275
+ const seed = {
276
+ ...initOptions,
277
+ lng: language,
278
+ fallbackLng: routing.fallbackLanguage,
279
+ supportedLngs: [
280
+ ...routing.languages
281
+ ],
282
+ ns: namespaces,
283
+ defaultNS: defaultNamespace,
284
+ partialBundledLanguages: true,
285
+ resources: resources ? {
286
+ [language]: resources
287
+ } : {},
288
+ interpolation: {
289
+ escapeValue: false,
290
+ ...initOptions.interpolation
291
+ }
292
+ };
293
+ let original;
294
+ let detachedSeed;
295
+ try {
296
+ original = detachInitOptions(initOptions);
297
+ detachedSeed = detachInitOptions(seed);
298
+ } catch {}
299
+ await instance.init(seed);
300
+ if (original && detachedSeed) recordNativeSnapshotOrigin(instance, original, detachedSeed);
301
+ return instance;
302
+ },
303
+ rewrite: (getLanguage)=>{
304
+ const languages = [
305
+ ...routing.languages
306
+ ];
307
+ const localisedUrls = detachLocalisedUrls(routing.localisedUrls);
308
+ const rewrite = createI18nUrlRewrite({
309
+ languages,
310
+ getLanguage,
311
+ localisedUrls
312
+ });
313
+ const unchanged = descriptorProof(rewrite);
314
+ nativeRewriteOrigins.set(rewrite, ()=>{
315
+ if (!unchanged()) throw new Error('Native i18n rewrite was modified');
316
+ const language = getLanguage();
317
+ if (!languages.includes(language)) throw new Error('Native i18n rewrite language is unsupported');
318
+ return {
319
+ language,
320
+ languages: [
321
+ ...languages
322
+ ],
323
+ ...void 0 === localisedUrls ? {} : {
324
+ localisedUrls: detachLocalisedUrls(localisedUrls)
325
+ }
326
+ };
327
+ });
328
+ return rewrite;
329
+ },
330
+ handoff (language, instance) {
331
+ return createI18nSsrHandoffInlineData({
332
+ language,
333
+ ...instance ? {
334
+ resources: Object.fromEntries(namespaces.map((namespace)=>[
335
+ namespace,
336
+ instance.getResourceBundle(language, namespace) ?? {}
337
+ ]))
338
+ } : {}
339
+ });
340
+ },
341
+ clientHandoff () {
342
+ const handoff = readI18nSsrHandoff();
343
+ if (handoff && routing.languages.includes(handoff.language)) return handoff;
344
+ return {
345
+ language: currentLanguage() ?? routing.fallbackLanguage
346
+ };
347
+ },
348
+ syncWithRouter (router, instance) {
349
+ const sync = createLatestLanguageSyncBinding();
350
+ sync.updateCallbacks({
351
+ changeLanguage: (target, language)=>target.changeLanguage(language),
352
+ commitLanguage () {},
353
+ readLanguage: (target)=>target.language,
354
+ reportFailure: ()=>window.location.reload()
355
+ });
356
+ sync.activate(instance);
357
+ const unsubscribe = router.subscribe('onBeforeLoad', ({ toLocation })=>{
358
+ const publicHref = toLocation.maskedLocation?.publicHref ?? toLocation.publicHref;
359
+ const pathname = new URL(publicHref, window.location.origin).pathname;
360
+ const language = languageFromPathname(pathname, routing.languages, routing.basePath);
361
+ if (language) sync.request(language);
362
+ });
363
+ return ()=>{
364
+ unsubscribe();
365
+ sync.deactivate();
366
+ };
367
+ },
368
+ persist (instance) {
369
+ const persist = (language)=>{
370
+ document.documentElement.lang = language;
371
+ document.cookie = `${cookie}=${encodeURIComponent(language)}; path=/; max-age=31536000; samesite=lax`;
372
+ };
373
+ if (instance.language) persist(instance.language);
374
+ instance.on('languageChanged', persist);
375
+ return ()=>instance.off('languageChanged', persist);
376
+ }
377
+ };
378
+ }
379
+ export { createNativeI18n, createNativeI18nInstance, getNativeI18nRewriteSnapshot, getNativeI18nSnapshotSeed, invalidateNativeI18nSnapshot };
@@ -1,6 +1,11 @@
1
1
  import "node:module";
2
+ import { canonicalTargetPathname, localiseTargetPathname } from "@modern-js/runtime-extensions/localised-urls";
2
3
  import { splitUrlTarget } from "@modern-js/runtime-utils/url";
3
- import { canonicalTargetPathname, localiseTargetPathname } from "./localisedUrls/index.mjs";
4
+ const configuredLanguage = (requested, languages)=>{
5
+ const language = languages.find((candidate)=>candidate.toLowerCase() === String(requested).toLowerCase());
6
+ if (void 0 === language) throw new RangeError(`Unsupported language "${requested}"; expected one of: ${languages.join(', ')}`);
7
+ return language;
8
+ };
4
9
  const buildLocalizedUrl = (target, language, languages, localisedUrls)=>{
5
10
  const { pathname, search, hash } = splitUrlTarget(target);
6
11
  return `${localiseTargetPathname(pathname, language, languages, localisedUrls)}${search}${hash}`;
@@ -10,4 +15,4 @@ const canonicalPath = (target, config)=>{
10
15
  const { pathname, search, hash } = splitUrlTarget(target);
11
16
  return `${canonicalTargetPathname(pathname, config.languages, config.localisedUrls)}${search}${hash}`;
12
17
  };
13
- export { buildLocalizedUrl, canonicalPath, localizePath };
18
+ export { buildLocalizedUrl, canonicalPath, configuredLanguage, localizePath };
@@ -0,0 +1,149 @@
1
+ import "node:module";
2
+ import { localiseTargetPathname, shouldSkipLocaleRedirect } from "@modern-js/runtime-extensions/localised-urls";
3
+ const DEFAULT_ORDER = [
4
+ 'querystring',
5
+ 'cookie',
6
+ 'header'
7
+ ];
8
+ const normaliseBasePath = (basePath)=>{
9
+ if (!basePath || '/' === basePath) return '';
10
+ return `/${basePath.split('/').filter(Boolean).join('/')}`;
11
+ };
12
+ const entryPathname = (pathname, basePath)=>{
13
+ const base = normaliseBasePath(basePath);
14
+ if (!base) return pathname || '/';
15
+ if (pathname === base) return '/';
16
+ return pathname.startsWith(`${base}/`) ? pathname.slice(base.length) : void 0;
17
+ };
18
+ const baseLanguage = (tag)=>tag.toLowerCase().split(/[-_]/u)[0];
19
+ const compatibleLanguages = (candidate, languages)=>{
20
+ const value = candidate?.trim().toLowerCase();
21
+ if (!value) return [];
22
+ const base = baseLanguage(value);
23
+ return [
24
+ ...languages.filter((language)=>language.toLowerCase() === value),
25
+ ...languages.filter((language)=>language.toLowerCase() === base),
26
+ ...base === value ? languages.filter((language)=>baseLanguage(language) === value) : []
27
+ ].filter((language, index, all)=>all.indexOf(language) === index);
28
+ };
29
+ const matchLanguage = (candidate, languages)=>compatibleLanguages(candidate, languages)[0];
30
+ function languageFromPathname(pathname, languages, basePath) {
31
+ const remaining = entryPathname(pathname, basePath);
32
+ if (void 0 === remaining) return;
33
+ const first = remaining.split('/').filter(Boolean)[0];
34
+ if (!first) return;
35
+ return languages.find((language)=>language.toLowerCase() === first.toLowerCase());
36
+ }
37
+ const readCookie = (header, name)=>{
38
+ if (!header) return null;
39
+ for (const part of header.split(';')){
40
+ const separator = part.indexOf('=');
41
+ if (separator < 0) continue;
42
+ if (part.slice(0, separator).trim() !== name) continue;
43
+ const raw = part.slice(separator + 1).trim();
44
+ try {
45
+ return decodeURIComponent(raw);
46
+ } catch {
47
+ return raw;
48
+ }
49
+ }
50
+ return null;
51
+ };
52
+ const QVALUE = /^(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)$/;
53
+ const rangeSpecificity = (range, language)=>{
54
+ const value = range.toLowerCase().replaceAll('_', '-');
55
+ const supported = language.toLowerCase().replaceAll('_', '-');
56
+ if ('*' === value) return 0;
57
+ if (value === supported) return Number.MAX_SAFE_INTEGER;
58
+ if (supported.startsWith(`${value}-`)) return 1 + value.split('-').length;
59
+ if (baseLanguage(value) === supported) return 1;
60
+ };
61
+ const acceptedLanguages = (header, languages)=>{
62
+ const items = (header ?? '').split(',').map((item, index)=>{
63
+ const [tag, ...parameters] = item.trim().split(';');
64
+ const quality = parameters.map((parameter)=>parameter.trim()).find((parameter)=>parameter.toLowerCase().startsWith('q='));
65
+ const value = quality?.slice(2).trim();
66
+ const q = void 0 === value ? 1 : QVALUE.test(value) ? Number(value) : 0 / 0;
67
+ return {
68
+ tag: tag.trim(),
69
+ q,
70
+ index
71
+ };
72
+ }).filter((item)=>item.tag && !Number.isNaN(item.q));
73
+ const ranked = languages.flatMap((language, position)=>{
74
+ let best;
75
+ for (const item of items){
76
+ const specificity = rangeSpecificity(item.tag, language);
77
+ if (void 0 !== specificity && (!best || specificity > best.specificity)) best = {
78
+ q: item.q,
79
+ index: item.index,
80
+ specificity
81
+ };
82
+ }
83
+ return best && best.q > 0 ? [
84
+ {
85
+ language,
86
+ position,
87
+ ...best
88
+ }
89
+ ] : [];
90
+ });
91
+ 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);
92
+ };
93
+ function detectRequestLanguage(request, languages, detection = {}) {
94
+ const url = new URL(request.url);
95
+ for (const detector of detection.order ?? DEFAULT_ORDER){
96
+ let language;
97
+ if ('querystring' === detector) language = matchLanguage(url.searchParams.get(detection.lookupQuerystring ?? 'lng'), languages);
98
+ else if ('cookie' === detector) language = matchLanguage(readCookie(request.headers.get('cookie'), detection.lookupCookie ?? 'i18next'), languages);
99
+ else if ('header' === detector) language = acceptedLanguages(request.headers.get(detection.lookupHeader ?? 'accept-language'), languages).find(Boolean);
100
+ if (language) return language;
101
+ }
102
+ }
103
+ function resolveRequestLanguage(request, options) {
104
+ const { languages, fallbackLanguage } = options;
105
+ const url = new URL(request.url);
106
+ const fromPath = languageFromPathname(url.pathname, languages, options.basePath);
107
+ if (fromPath) return {
108
+ kind: 'language',
109
+ language: fromPath
110
+ };
111
+ const detected = (false === options.detect ? void 0 : detectRequestLanguage(request, languages, options.detection)) ?? fallbackLanguage;
112
+ const remaining = entryPathname(url.pathname, options.basePath);
113
+ if (void 0 === remaining || shouldSkipLocaleRedirect(remaining, [
114
+ ...languages
115
+ ], options.ignoreRedirectRoutes)) {
116
+ const vary = (false === options.detect ? [] : options.detection?.order ?? DEFAULT_ORDER).flatMap((detector)=>'header' === detector ? [
117
+ options.detection?.lookupHeader ?? 'Accept-Language'
118
+ ] : 'cookie' === detector ? [
119
+ 'Cookie'
120
+ ] : []);
121
+ return vary.length ? {
122
+ kind: 'language',
123
+ language: detected,
124
+ vary
125
+ } : {
126
+ kind: 'language',
127
+ language: detected
128
+ };
129
+ }
130
+ const localized = localiseTargetPathname(remaining, detected, [
131
+ ...languages
132
+ ], options.localisedUrls);
133
+ return {
134
+ kind: 'redirect',
135
+ language: detected,
136
+ location: `${normaliseBasePath(options.basePath)}${localized}${url.search}`
137
+ };
138
+ }
139
+ function createRequestLanguageRedirect(location) {
140
+ return new Response(null, {
141
+ status: 302,
142
+ headers: {
143
+ 'cache-control': 'private, no-store',
144
+ location,
145
+ vary: 'Accept-Language, Cookie'
146
+ }
147
+ });
148
+ }
149
+ export { createRequestLanguageRedirect, detectRequestLanguage, languageFromPathname, resolveRequestLanguage };
@@ -0,0 +1,56 @@
1
+ import "node:module";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { RuntimeContext } from "@modern-js/runtime";
4
+ import { InternalRuntimeContext } from "@modern-js/runtime/context";
5
+ import { getRouterRuntimeState, subscribeRouterRuntimeState } from "@modern-js/runtime-extensions/router-state";
6
+ import { useCallback, useContext, useSyncExternalStore } from "react";
7
+ const subscribeNone = ()=>()=>{};
8
+ const getEmptySnapshot = ()=>null;
9
+ const createI18nRouterNavigation = ({ I18nNavigationProvider, useNativeI18nRouterAdapter })=>{
10
+ const NavigationProvider = I18nNavigationProvider;
11
+ const useIntegratedRouterAdapter = ()=>{
12
+ const runtimeContext = useContext(RuntimeContext);
13
+ const internalContext = useContext(InternalRuntimeContext);
14
+ const native = useNativeI18nRouterAdapter();
15
+ const getCapability = useCallback(()=>getRouterRuntimeState(internalContext)?.navigation ?? getRouterRuntimeState(runtimeContext)?.navigation, [
16
+ internalContext,
17
+ runtimeContext
18
+ ]);
19
+ const subscribeCapability = useCallback((update)=>{
20
+ const stopInternal = subscribeRouterRuntimeState(internalContext, update);
21
+ const stopPublic = internalContext === runtimeContext ? void 0 : subscribeRouterRuntimeState(runtimeContext, update);
22
+ return ()=>{
23
+ stopInternal();
24
+ stopPublic?.();
25
+ };
26
+ }, [
27
+ internalContext,
28
+ runtimeContext
29
+ ]);
30
+ const capability = useSyncExternalStore(subscribeCapability, getCapability, getCapability);
31
+ const snapshot = useSyncExternalStore(capability?.subscribe ?? subscribeNone, capability?.getSnapshot ?? getEmptySnapshot, capability?.getSnapshot ?? getEmptySnapshot);
32
+ if (native.hasRouter || !capability || !snapshot) return native;
33
+ const internalState = getRouterRuntimeState(internalContext);
34
+ return {
35
+ framework: internalState?.navigation === capability ? internalState.framework : getRouterRuntimeState(runtimeContext)?.framework,
36
+ hasRouter: true,
37
+ ...snapshot,
38
+ navigate: capability.navigate,
39
+ Link: capability.Link,
40
+ createLinkProps: capability.createLinkProps
41
+ };
42
+ };
43
+ const I18nRouterNavigationProvider = ({ children })=>{
44
+ const value = useIntegratedRouterAdapter();
45
+ const native = useNativeI18nRouterAdapter();
46
+ return /*#__PURE__*/ jsx(NavigationProvider, {
47
+ value: value.hasRouter && !native.hasRouter ? value : null,
48
+ children: children
49
+ });
50
+ };
51
+ return {
52
+ useIntegratedRouterAdapter,
53
+ I18nRouterNavigationProvider
54
+ };
55
+ };
56
+ export { createI18nRouterNavigation };
@@ -0,0 +1,2 @@
1
+ import "node:module";
2
+ export { createI18nRouterNavigation } from "./adapter.mjs";
@@ -0,0 +1,45 @@
1
+ import "node:module";
2
+ import { parsePublicData, serializePublicData } from "@modern-js/renderer-core/data";
3
+ import { serializeInlineData } from "@modern-js/renderer-core/document";
4
+ const I18N_SSR_HANDOFF_ELEMENT_ID = '__modernjs_i18n_ssr__';
5
+ function assertPayload(payload) {
6
+ if ('string' != typeof payload.language || 0 === payload.language.length) throw new TypeError('An i18n SSR handoff payload requires a nonempty language.');
7
+ 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.');
8
+ }
9
+ function createI18nSsrHandoffInlineData(payload, options = {}) {
10
+ assertPayload(payload);
11
+ return {
12
+ id: options.id ?? I18N_SSR_HANDOFF_ELEMENT_ID,
13
+ payload: serializePublicData({
14
+ language: payload.language,
15
+ ...payload.resources ? {
16
+ resources: payload.resources
17
+ } : {}
18
+ })
19
+ };
20
+ }
21
+ function serializeI18nSsrHandoff(payload, options = {}) {
22
+ return serializeInlineData({
23
+ ...createI18nSsrHandoffInlineData(payload, options),
24
+ nonce: options.nonce
25
+ });
26
+ }
27
+ function isHandoffPayload(value) {
28
+ return Boolean(value) && 'object' == typeof value && 'string' == typeof value.language;
29
+ }
30
+ function readI18nSsrHandoffFromText(text) {
31
+ if (!text) return;
32
+ try {
33
+ const result = parsePublicData(text);
34
+ return isHandoffPayload(result) ? result : void 0;
35
+ } catch {
36
+ return;
37
+ }
38
+ }
39
+ function readI18nSsrHandoff(options = {}) {
40
+ const doc = options.doc ?? ("u" < typeof document ? void 0 : document);
41
+ if (!doc) return;
42
+ const element = doc.getElementById(options.id ?? I18N_SSR_HANDOFF_ELEMENT_ID);
43
+ return readI18nSsrHandoffFromText(element?.textContent ?? null);
44
+ }
45
+ export { I18N_SSR_HANDOFF_ELEMENT_ID, createI18nSsrHandoffInlineData, readI18nSsrHandoff, readI18nSsrHandoffFromText, serializeI18nSsrHandoff };