@bleedingdev/modern-js-i18n-runtime-extensions 3.9.3-ultramodern.1 → 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.
@@ -4,6 +4,242 @@ import { createLatestLanguageSyncBinding } from "./language-sync/controller.mjs"
4
4
  import { createRequestLanguageRedirect, languageFromPathname, resolveRequestLanguage } from "./requestLanguage.mjs";
5
5
  import { createI18nSsrHandoffInlineData, readI18nSsrHandoff } from "./ssrLanguageHandoff.mjs";
6
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
+ }
7
243
  function unwrapResource(module) {
8
244
  const resource = 'object' == typeof module && null !== module && 'default' in module ? module.default : module;
9
245
  return resource;
@@ -36,7 +272,7 @@ function createNativeI18n(options, loaders) {
36
272
  async create (language, resources) {
37
273
  const instance = i18next.createInstance();
38
274
  instance.use(backend);
39
- await instance.init({
275
+ const seed = {
40
276
  ...initOptions,
41
277
  lng: language,
42
278
  fallbackLng: routing.fallbackLanguage,
@@ -53,14 +289,44 @@ function createNativeI18n(options, loaders) {
53
289
  escapeValue: false,
54
290
  ...initOptions.interpolation
55
291
  }
56
- });
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);
57
301
  return instance;
58
302
  },
59
- rewrite: (getLanguage)=>createI18nUrlRewrite({
60
- languages: routing.languages,
303
+ rewrite: (getLanguage)=>{
304
+ const languages = [
305
+ ...routing.languages
306
+ ];
307
+ const localisedUrls = detachLocalisedUrls(routing.localisedUrls);
308
+ const rewrite = createI18nUrlRewrite({
309
+ languages,
61
310
  getLanguage,
62
- localisedUrls: routing.localisedUrls
63
- }),
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
+ },
64
330
  handoff (language, instance) {
65
331
  return createI18nSsrHandoffInlineData({
66
332
  language,
@@ -110,4 +376,4 @@ function createNativeI18n(options, loaders) {
110
376
  }
111
377
  };
112
378
  }
113
- export { createNativeI18n };
379
+ export { createNativeI18n, createNativeI18nInstance, getNativeI18nRewriteSnapshot, getNativeI18nSnapshotSeed, invalidateNativeI18nSnapshot };
@@ -3,6 +3,19 @@ import type { LocalisedUrlsOption } from '@modern-js/runtime-extensions/localise
3
3
  import { type i18n as I18nInstance, type InitOptions } from 'i18next';
4
4
  import { type ResolveRequestLanguageOptions } from './requestLanguage.js';
5
5
  export type { I18nInstance };
6
+ /** Retire the native proof when its owning request or plugin contract ends. */
7
+ export declare function invalidateNativeI18nSnapshot(instance: unknown): void;
8
+ export interface NativeI18nRewriteSnapshot {
9
+ language: string;
10
+ languages: readonly string[];
11
+ localisedUrls?: LocalisedUrlsOption;
12
+ }
13
+ /** Only rewrites created by the native entry have a portable routing profile. */
14
+ export declare function getNativeI18nRewriteSnapshot(rewrite: unknown): NativeI18nRewriteSnapshot | undefined;
15
+ /** A real fully bundled instance for request-local synchronous providers. */
16
+ export declare function createNativeI18nInstance(initOptions: InitOptions): I18nInstance;
17
+ /** The original effective seed, only while the initialized native proof holds. */
18
+ export declare function getNativeI18nSnapshotSeed(instance: unknown): InitOptions;
6
19
  export interface NativeI18nOptions extends ResolveRequestLanguageOptions {
7
20
  /**
8
21
  * JSON-serializable i18next `init` options. `lng`, `fallbackLng`,
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern.js",
18
18
  "i18n"
19
19
  ],
20
- "version": "3.9.3-ultramodern.1",
20
+ "version": "3.9.3-ultramodern.10",
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "main": "./dist/cjs/index.js",
23
23
  "module": "./dist/esm/index.mjs",
@@ -118,18 +118,18 @@
118
118
  }
119
119
  },
120
120
  "engines": {
121
- "node": ">=26.10.0"
121
+ "node": ">=26.12.0"
122
122
  },
123
123
  "dependencies": {
124
- "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.1",
125
- "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.3-ultramodern.1",
126
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.3-ultramodern.1",
124
+ "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.10",
125
+ "@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.3-ultramodern.10",
126
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.9.3-ultramodern.10",
127
127
  "i18next-browser-languagedetector": "^8.2.1"
128
128
  },
129
129
  "peerDependencies": {
130
- "@modern-js/runtime": "3.9.3-ultramodern.1",
130
+ "@modern-js/runtime": "3.9.3-ultramodern.10",
131
131
  "i18next": ">=25.7.4",
132
- "react": "^19.2.8"
132
+ "react": "^19.3.1"
133
133
  },
134
134
  "peerDependenciesMeta": {
135
135
  "@modern-js/runtime": {
@@ -143,13 +143,13 @@
143
143
  }
144
144
  },
145
145
  "devDependencies": {
146
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.3-ultramodern.1",
146
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.3-ultramodern.10",
147
147
  "@rslib/core": "1.0.3",
148
148
  "@scripts/rstest-config": "2.66.0",
149
- "@types/node": "^26.6.3",
150
- "i18next": "26.4.2",
151
- "react": "^19.3.0",
152
- "react-dom": "^19.3.0"
149
+ "@types/node": "^26.6.5",
150
+ "i18next": "26.4.6",
151
+ "react": "^19.3.1",
152
+ "react-dom": "^19.3.1"
153
153
  },
154
154
  "sideEffects": false,
155
155
  "publishConfig": {