@genrwork/laravel-i18next 0.1.0 → 0.1.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.
@@ -1,4 +1,24 @@
1
1
  import i18next from 'i18next';
2
+ import { g as getDeclaredNamespaces, t as trackInstance } from './demand-6YnTqSM4.mjs';
3
+
4
+ /**
5
+ * The modules lazy loaders (`import.meta.glob()` functions) have already
6
+ * resolved, keyed by the loader itself. Loading a language file is the one
7
+ * asynchronous step of translating; once a loader has been awaited, its
8
+ * module never changes, so every later read of it can be synchronous. That is
9
+ * what lets `preloadI18n()` load translations during page resolution --
10
+ * before anything renders -- and lets `createI18n()` and the backend read
11
+ * them without waiting, so a render never has to suspend.
12
+ *
13
+ * Module scoped on purpose, so it is shared by every i18next instance of the
14
+ * process: a server keeps one copy of each catalog across requests.
15
+ */ const modules = new WeakMap();
16
+ function getCachedModule(loader) {
17
+ return modules.get(loader);
18
+ }
19
+ function setCachedModule(loader, module) {
20
+ modules.set(loader, module);
21
+ }
2
22
 
3
23
  /**
4
24
  * Normalize a `LocaleFileSources` (a single `LocaleFiles` map, or an ordered
@@ -136,6 +156,9 @@ import i18next from 'i18next';
136
156
  * module; eager files (`import.meta.glob(..., { eager: true })`) resolve to a
137
157
  * module directly; a plain object (hand-built maps, tests) is wrapped as a
138
158
  * module's `default`.
159
+ *
160
+ * A loader that was awaited before resolves to its module directly, from the
161
+ * cache (see `src/utils/cache.ts`): reading it is synchronous from then on.
139
162
  */ function toModule(entry) {
140
163
  const type = Object.prototype.toString.call(entry);
141
164
  if (type === '[object Promise]' || type === '[object Module]') {
@@ -146,8 +169,14 @@ import i18next from 'i18next';
146
169
  default: entry
147
170
  };
148
171
  }
149
- if (type === '[object Function]') {
150
- return entry();
172
+ if (typeof entry === 'function') {
173
+ const loader = entry;
174
+ const cached = getCachedModule(loader);
175
+ if (cached) return cached;
176
+ return Promise.resolve(loader()).then((module)=>{
177
+ setCachedModule(loader, module);
178
+ return module;
179
+ });
151
180
  }
152
181
  return undefined;
153
182
  }
@@ -242,335 +271,36 @@ function isPromiseLike(value) {
242
271
  }
243
272
  LaravelBackend.type = 'backend';
244
273
 
245
- /* eslint-disable */ /**
246
- * Get the index to use for pluralization.
247
- * The plural rules are derived from code of the Zend Framework.
248
- *
249
- * @category Zend
250
- * @package Zend_Locale
251
- * @public https://github.com/zendframework/zf1/blob/master/library/Zend/Translate/Plural.php
252
- * @copyright 2005-2015 Zend Technologies USA Inc. http://www.zend.com
253
- * @license http://framework.zend.com/license New BSD License
254
- *
255
- * @param {String} locale
256
- * @param {Number} number
257
- * @return {Number}
258
- */ function getPluralIndex(locale, number) {
259
- locale = locale.replace('-', '_');
260
- if (locale === 'pt_BR') {
261
- // temporary set a locale for brazilian
262
- locale = 'xbr';
263
- }
264
- if (locale.length > 3) {
265
- locale = locale.substring(0, locale.lastIndexOf('_'));
266
- }
267
- switch(locale){
268
- case 'az':
269
- case 'bo':
270
- case 'dz':
271
- case 'id':
272
- case 'ja':
273
- case 'jv':
274
- case 'ka':
275
- case 'km':
276
- case 'kn':
277
- case 'ko':
278
- case 'ms':
279
- case 'th':
280
- case 'tr':
281
- case 'vi':
282
- case 'zh':
283
- return 0;
284
- case 'af':
285
- case 'bn':
286
- case 'bg':
287
- case 'ca':
288
- case 'da':
289
- case 'de':
290
- case 'el':
291
- case 'en':
292
- case 'eo':
293
- case 'es':
294
- case 'et':
295
- case 'eu':
296
- case 'fa':
297
- case 'fi':
298
- case 'fo':
299
- case 'fur':
300
- case 'fy':
301
- case 'gl':
302
- case 'gu':
303
- case 'ha':
304
- case 'he':
305
- case 'hu':
306
- case 'is':
307
- case 'it':
308
- case 'ku':
309
- case 'lb':
310
- case 'ml':
311
- case 'mn':
312
- case 'mr':
313
- case 'nah':
314
- case 'nb':
315
- case 'ne':
316
- case 'nl':
317
- case 'nn':
318
- case 'no':
319
- case 'om':
320
- case 'or':
321
- case 'pa':
322
- case 'pap':
323
- case 'ps':
324
- case 'pt':
325
- case 'so':
326
- case 'sq':
327
- case 'sv':
328
- case 'sw':
329
- case 'ta':
330
- case 'te':
331
- case 'tk':
332
- case 'ur':
333
- case 'zu':
334
- return number === 1 ? 0 : 1;
335
- case 'am':
336
- case 'bh':
337
- case 'fil':
338
- case 'fr':
339
- case 'gun':
340
- case 'hi':
341
- case 'ln':
342
- case 'mg':
343
- case 'nso':
344
- case 'xbr':
345
- case 'ti':
346
- case 'wa':
347
- return number === 0 || number === 1 ? 0 : 1;
348
- case 'be':
349
- case 'bs':
350
- case 'hr':
351
- case 'ru':
352
- case 'sr':
353
- case 'uk':
354
- return number % 10 === 1 && number % 100 !== 11 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
355
- case 'cs':
356
- case 'sk':
357
- return number === 1 ? 0 : number >= 2 && number <= 4 ? 1 : 2;
358
- case 'ga':
359
- return number === 1 ? 0 : number === 2 ? 1 : 2;
360
- case 'lt':
361
- return number % 10 === 1 && number % 100 !== 11 ? 0 : number % 10 >= 2 && (number % 100 < 10 || number % 100 >= 20) ? 1 : 2;
362
- case 'sl':
363
- return number % 100 === 1 ? 0 : number % 100 === 2 ? 1 : number % 100 === 3 || number % 100 === 4 ? 2 : 3;
364
- case 'mk':
365
- return number % 10 === 1 ? 0 : 1;
366
- case 'mt':
367
- return number === 1 ? 0 : number === 0 || number % 100 > 1 && number % 100 < 11 ? 1 : number % 100 > 10 && number % 100 < 20 ? 2 : 3;
368
- case 'lv':
369
- return number === 0 ? 0 : number % 10 === 1 && number % 100 !== 11 ? 1 : 2;
370
- case 'pl':
371
- return number === 1 ? 0 : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14) ? 1 : 2;
372
- case 'cy':
373
- return number === 1 ? 0 : number === 2 ? 1 : number === 8 || number === 11 ? 2 : 3;
374
- case 'ro':
375
- return number === 1 ? 0 : number === 0 || number % 100 > 0 && number % 100 < 20 ? 1 : 2;
376
- case 'ar':
377
- return number === 0 ? 0 : number === 1 ? 1 : number === 2 ? 2 : number >= 3 && number <= 10 ? 3 : number >= 11 && number <= 99 ? 4 : 5;
378
- default:
379
- return 0;
380
- }
381
- }
382
-
383
274
  /**
384
- * Select a proper translation string based on the given number,
385
- * using the same rules as Laravel's `trans_choice()`.
275
+ * The i18next options Laravel translations need. `createI18n()` applies them;
276
+ * pass them to `init()` yourself when you set i18next up by hand.
386
277
  *
387
- * @param message
388
- * @param number
389
- * @param locale
390
- */ function pluralization(message, number, locale) {
391
- let segments = message.split('|');
392
- const extracted = extract(segments, number);
393
- if (extracted !== null) {
394
- return extracted.trim();
278
+ * - no key or namespace separator: Laravel keys are flat and contain dots
279
+ * and colons (`auth.failed`, `Email:`);
280
+ * - an empty message counts as missing and falls back to the next language;
281
+ * - no escaping of interpolated values: React, Vue and Svelte already escape
282
+ * what they render, and Laravel's `:name` replacements were never escaped.
283
+ */ const laravelOptions = {
284
+ keySeparator: false,
285
+ nsSeparator: false,
286
+ returnEmptyString: false,
287
+ interpolation: {
288
+ escapeValue: false
395
289
  }
396
- segments = stripConditions(segments);
397
- const pluralIndex = getPluralIndex(locale, number);
398
- return segments.length === 1 || !segments[pluralIndex] ? segments[0] : segments[pluralIndex];
399
- }
290
+ };
400
291
  /**
401
- * Extract a translation string using inline conditions.
402
- *
403
- * @param segments
404
- * @param number
405
- */ function extract(segments, number) {
406
- for (const segment of segments){
407
- const result = extractFromString(segment, number);
408
- if (result !== null) return result;
409
- }
410
- return null;
411
- }
412
- /**
413
- * Get the translation string if the condition matches.
414
- *
415
- * @param part
416
- * @param number
417
- */ function extractFromString(part, number) {
418
- const matches = part.match(/^[{[]([^,{}[\]]*),?([^{}[\]]*)[}\]]([\s\S]*)/);
419
- if (!matches) return null;
420
- const [, from, to, value] = matches;
421
- if ((from === '*' || number >= parseFloat(from)) && (to === '*' || number <= parseFloat(to))) {
422
- return value;
423
- }
424
- return from && parseFloat(from) === number ? value : null;
425
- }
426
- /**
427
- * Strip the inline conditions from each segment, just leaving the text.
428
- *
429
- * @param segments
430
- */ function stripConditions(segments) {
431
- return segments.map((part)=>part.replace(/^[{[]([^[\]{}]*)[}\]]/, ''));
432
- }
433
-
434
- /**
435
- * Make the place-holder replacements on a line, the Laravel way:
436
- * `:name` as is, `:NAME` upper-cased and `:Name` capitalized.
437
- *
438
- * @param message
439
- * @param replacements
440
- */ function replacer(message, replacements) {
441
- if (!replacements) return message;
442
- const patterns = Object.entries(replacements).flatMap(([key, value])=>[
443
- {
444
- pattern: new RegExp(`:${key}`, 'g'),
445
- replacement: value.toString()
446
- },
447
- {
448
- pattern: new RegExp(`:${key.toUpperCase()}`, 'g'),
449
- replacement: value.toString().toUpperCase()
450
- },
451
- {
452
- pattern: new RegExp(`:${capitalize(key)}`, 'g'),
453
- replacement: capitalize(value.toString())
454
- }
455
- ]);
456
- return patterns.reduce((result, { pattern, replacement })=>result.replace(pattern, replacement), message);
457
- }
458
- /**
459
- * Capitalizing string.
460
- *
461
- * @param str
462
- */ function capitalize(str) {
463
- return str ? str[0].toUpperCase() + str.slice(1) : '';
464
- }
465
-
466
- /**
467
- * i18next `t()` options that are not Laravel replacements. Use the `replace`
468
- * option to pass replacements having one of these names.
469
- */ const I18NEXT_OPTIONS = new Set([
470
- 'appendNamespaceToMissingKey',
471
- 'applyPostProcessor',
472
- 'context',
473
- 'fallbackLng',
474
- 'i18nResolved',
475
- 'interpolation',
476
- 'joinArrays',
477
- 'keyPrefix',
478
- 'keySeparator',
479
- 'lng',
480
- 'lngs',
481
- 'missingKeyNoValueFallbackToKey',
482
- 'ns',
483
- 'nsSeparator',
484
- 'nest',
485
- 'ordinal',
486
- 'postProcess',
487
- 'replace',
488
- 'returnDetails',
489
- 'returnObjects',
490
- 'scopeNs',
491
- 'skipInterpolation'
492
- ]);
493
- function getReplacements(options) {
494
- const fromReplace = options.replace !== null && typeof options.replace === 'object';
495
- const source = fromReplace ? options.replace : options;
496
- const replacements = {};
497
- Object.entries(source).forEach(([key, value])=>{
498
- if (typeof value !== 'string' && typeof value !== 'number') return;
499
- // A numeric count selects the plural form, it is replaced after pluralization.
500
- if (key === 'count' && typeof value === 'number') return;
501
- if (!fromReplace && (I18NEXT_OPTIONS.has(key) || key.startsWith('defaultValue'))) return;
502
- replacements[key] = value;
292
+ * Register the `uppercase`, `lowercase` and `capitalize` interpolation formats.
293
+ * The Vite plugin writes Laravel's `:NAME` as `{{name, uppercase}}` and `:Name`
294
+ * as `{{name, capitalize}}`; call this once the instance is initialized.
295
+ */ function registerCaseFormats(instance) {
296
+ const formatter = instance.services.formatter;
297
+ formatter === null || formatter === void 0 ? void 0 : formatter.add('uppercase', (value)=>String(value).toUpperCase());
298
+ formatter === null || formatter === void 0 ? void 0 : formatter.add('lowercase', (value)=>String(value).toLowerCase());
299
+ formatter === null || formatter === void 0 ? void 0 : formatter.add('capitalize', (value)=>{
300
+ const text = String(value);
301
+ return text ? text[0].toUpperCase() + text.slice(1) : text;
503
302
  });
504
- return replacements;
505
303
  }
506
- /**
507
- * i18next format plugin translating the way Laravel does:
508
- *
509
- * - flat keys (`auth.failed`, `Welcome, :name!`), no key or namespace separators;
510
- * - `:name`, `:NAME` and `:Name` replacements instead of `{{name}}` interpolation and `$t()` nesting;
511
- * - a numeric `count` selects the message with Laravel's `trans_choice()` rules
512
- * (`one|many`, `{0} none|[1,19] some|[20,*] many`) instead of `_one`/`_other` suffixed keys;
513
- * - empty messages fall back to the next language.
514
- */ class LaravelFormat {
515
- init(i18next) {
516
- this.i18next = i18next;
517
- // Laravel keys contain dots and colons: disable the separators, unless given to `init()`.
518
- const options = i18next.options;
519
- if (options.userDefinedKeySeparator === undefined) options.keySeparator = false;
520
- if (options.userDefinedNsSeparator === undefined) options.nsSeparator = false;
521
- }
522
- getResource(language, namespace, key) {
523
- var _a;
524
- const value = (_a = this.i18next) === null || _a === void 0 ? void 0 : _a.getResource(language, namespace, key, {
525
- keySeparator: false,
526
- ignoreJSONStructure: false
527
- });
528
- return value || undefined;
529
- }
530
- addLookupKeys(finalKeys) {
531
- return finalKeys;
532
- }
533
- parse(message, options, language, namespace, _key, info) {
534
- if (typeof message !== 'string') return message;
535
- const replacements = getReplacements(options);
536
- const translated = replacer(message, replacements);
537
- const count = options.count;
538
- if (typeof count !== 'number') return translated;
539
- return replacer(pluralization(translated, count, this.getPluralLocale(language, namespace, info)), {
540
- ...replacements,
541
- count: count.toString()
542
- });
543
- }
544
- /**
545
- * The language the message actually resolved in, so pluralization always
546
- * matches the text being pluralized -- never merely a language that has
547
- * translations for the namespace, which is not the same thing once several
548
- * sources can contribute to one namespace (see LaravelBackend): a single
549
- * unrelated key from a higher-priority source is enough to make
550
- * `hasResourceBundle(language, namespace)` true for a namespace whose
551
- * actual message still came from the fallback language.
552
- *
553
- * i18next passes the language the lookup succeeded at as `resolved.usedLng`
554
- * in `parse()`'s 6th argument (`extendTranslation()` in i18next's own
555
- * `translator.js`); prefer it. Older i18next in the `>=24` peer range that
556
- * does not pass it falls back to a heuristic: the current language when it
557
- * has translations for the namespace, else the fallback language.
558
- */ getPluralLocale(language, namespace, info) {
559
- var _a;
560
- const usedLng = (_a = info === null || info === void 0 ? void 0 : info.resolved) === null || _a === void 0 ? void 0 : _a.usedLng;
561
- if (usedLng) return usedLng;
562
- if (!this.i18next || !language || this.i18next.hasResourceBundle(language, namespace)) return language;
563
- const [fallbackLanguage] = this.i18next.services.languageUtils.getFallbackCodes(this.i18next.options.fallbackLng, language);
564
- return fallbackLanguage !== null && fallbackLanguage !== void 0 ? fallbackLanguage : language;
565
- }
566
- constructor(){
567
- this.type = 'i18nFormat';
568
- /**
569
- * Non-string translations (e.g. an empty PHP array) are returned as is.
570
- */ this.handleAsObject = false;
571
- }
572
- }
573
- LaravelFormat.type = 'i18nFormat';
574
304
 
575
305
  /**
576
306
  * Locale of the `<html lang="">` attribute (set by Laravel's `app.blade.php`), or `en`.
@@ -608,26 +338,39 @@ LaravelFormat.type = 'i18nFormat';
608
338
  return ()=>instance.off('languageChanged', setDocumentLang);
609
339
  }
610
340
  /**
611
- * Create an i18next instance translating Laravel language files: the default
341
+ * Create an i18next instance reading Laravel language files as i18next JSON: the default
612
342
  * namespace from `lang/{locale}.json`, and one namespace per `lang/{locale}/{namespace}.json`
613
- * file (generated by the Vite plugin, or hand-written).
343
+ * file (converted from PHP by the Vite plugin, or hand-written the i18next way).
614
344
  *
615
345
  * With eager files, every known namespace of the locale and fallback locale is
616
346
  * preloaded synchronously, so a first render never has to wait for translations.
617
- * With lazy files, namespaces are loaded on demand as components request them.
347
+ *
348
+ * With lazy files, only the namespaces the evaluated modules declared are loaded
349
+ * up front (see `declareNamespaces()`), plus the default one; any other is loaded
350
+ * when a component first asks for it, which suspends until it arrives. Loaded
351
+ * beforehand with `preloadI18n()` -- or `withI18nPreload()` around the page
352
+ * resolver -- the up front ones are already in memory, so the instance is ready
353
+ * synchronously and neither the first render nor a later page ever waits.
618
354
  */ function createI18n({ files, locale, fallbackLocale }) {
619
355
  const instance = i18next.createInstance();
620
356
  const sources = toSources(files);
621
- const namespaces = recognizer(sources).getAllNamespaces();
357
+ const known = recognizer(sources).getAllNamespaces();
622
358
  const resolvedLocale = locale || documentLocale();
623
359
  const resolvedFallbackLocale = fallbackLocale || documentLocale();
624
360
  const eager = isEagerSources(sources);
625
- instance.use(LaravelBackend).use(LaravelFormat).init({
361
+ const namespaces = eager ? known : Array.from(new Set([
362
+ DEFAULT_NAMESPACE,
363
+ ...getDeclaredNamespaces()
364
+ ])).filter((namespace)=>known.includes(namespace));
365
+ instance.use(LaravelBackend).init({
366
+ ...laravelOptions,
626
367
  lng: resolvedLocale,
627
368
  fallbackLng: resolvedFallbackLocale,
628
369
  initAsync: false,
629
- ns: namespaces.length > 0 ? namespaces : undefined,
630
- preload: eager ? Array.from(new Set([
370
+ ...namespaces.length > 0 ? {
371
+ ns: namespaces
372
+ } : {},
373
+ preload: namespaces.length > 0 ? Array.from(new Set([
631
374
  resolvedLocale,
632
375
  resolvedFallbackLocale
633
376
  ])) : undefined,
@@ -635,7 +378,9 @@ LaravelFormat.type = 'i18nFormat';
635
378
  files: sources
636
379
  }
637
380
  });
381
+ registerCaseFormats(instance);
382
+ trackInstance(instance);
638
383
  return instance;
639
384
  }
640
385
 
641
- export { LaravelBackend as L, LaravelFormat as a, createI18n as c, documentLocale as d, syncDocumentLang as s };
386
+ export { DEFAULT_NAMESPACE as D, LaravelBackend as L, resolver as a, registerCaseFormats as b, createI18n as c, documentLocale as d, laravelOptions as l, recognizer as r, syncDocumentLang as s, toSources as t };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Namespaces the application's modules asked for, by evaluating.
3
+ *
4
+ * The Vite plugin adds one `declareNamespaces([...])` call to every module
5
+ * calling `useTranslation('ns')` (or `withTranslation`, `<Trans ns>`), so
6
+ * evaluating a module -- a page, a layout, any component it statically
7
+ * imports -- records which namespaces it will translate with. A bundler loads
8
+ * a page together with everything it imports, so by the time a page is
9
+ * resolved this set holds exactly the namespaces that page needs, however
10
+ * deeply they are nested, with nothing to declare by hand. `preloadI18n()`
11
+ * reads it.
12
+ *
13
+ * Only ever grows, and is shared by every i18next instance of the process.
14
+ *
15
+ * It lives on `globalThis`, under a registered symbol, and the code the Vite
16
+ * plugin injects writes to it directly instead of importing this module: a
17
+ * module can be evaluated before this one (a bundler is free to order a
18
+ * package's chunks around import cycles), can sit in a package that does not
19
+ * depend on this one, and a page bundle and a server bundle can each carry
20
+ * their own copy of this module -- none of that may lose a declaration.
21
+ */ const DECLARED_NAMESPACES_KEY = 'laravel-i18next.declared';
22
+ function declaredNamespaces() {
23
+ var _a;
24
+ const store = globalThis;
25
+ const key = Symbol.for(DECLARED_NAMESPACES_KEY);
26
+ return (_a = store[key]) !== null && _a !== void 0 ? _a : store[key] = new Set();
27
+ }
28
+ /**
29
+ * Record namespaces a module translates with. Called by the code the Vite
30
+ * plugin injects; call it yourself for a namespace requested dynamically
31
+ * (`useTranslation(name)`), which the plugin cannot see.
32
+ */ function declareNamespaces(namespaces) {
33
+ (typeof namespaces === 'string' ? [
34
+ namespaces
35
+ ] : namespaces).forEach((namespace)=>declaredNamespaces().add(namespace));
36
+ }
37
+ function getDeclaredNamespaces() {
38
+ return Array.from(declaredNamespaces());
39
+ }
40
+ /**
41
+ * The instances living in this process, weakly held. Only browser instances
42
+ * are tracked (one per app, living as long as the page): a server creates one
43
+ * instance per request, which nothing may keep alive, and needs no tracking
44
+ * anyway since each request's instance is created after its translations are
45
+ * preloaded.
46
+ */ const instances = new Set();
47
+ function trackInstance(instance) {
48
+ if (typeof window === 'undefined' || typeof WeakRef === 'undefined') return;
49
+ instances.add(new WeakRef(instance));
50
+ }
51
+ function reportOf(instance) {
52
+ return instance.reportNamespaces;
53
+ }
54
+ /**
55
+ * The namespaces the components of the live browser instances have rendered
56
+ * with since `forgetRenderedNamespaces()`, or none when nothing recorded them
57
+ * (another framework, or nothing has rendered yet).
58
+ */ function getRenderedNamespaces() {
59
+ return Array.from(new Set(getLiveInstances().flatMap((instance)=>{
60
+ var _a, _b;
61
+ return Object.keys((_b = (_a = reportOf(instance)) === null || _a === void 0 ? void 0 : _a.usedNamespaces) !== null && _b !== void 0 ? _b : {});
62
+ })));
63
+ }
64
+ function forgetRenderedNamespaces() {
65
+ getLiveInstances().forEach((instance)=>{
66
+ const report = reportOf(instance);
67
+ if (report) report.usedNamespaces = {};
68
+ });
69
+ }
70
+ /**
71
+ * Make the live instances load only these namespaces the next time their
72
+ * language changes. i18next remembers every namespace it ever loaded and
73
+ * loads them all again for a new language; this forgets the others, which a
74
+ * later `loadNamespaces()` (see `preloadI18n()`) brings back when a page
75
+ * asks for them.
76
+ */ function keepOnlyNamespaces(namespaces) {
77
+ getLiveInstances().forEach((instance)=>{
78
+ const loaded = instance.options.ns;
79
+ if (Array.isArray(loaded)) instance.options.ns = loaded.filter((namespace)=>namespaces.includes(namespace));
80
+ });
81
+ }
82
+ function getLiveInstances() {
83
+ const live = [];
84
+ instances.forEach((reference)=>{
85
+ const instance = reference.deref();
86
+ if (instance) live.push(instance);
87
+ else instances.delete(reference);
88
+ });
89
+ return live;
90
+ }
91
+
92
+ export { DECLARED_NAMESPACES_KEY as D, getLiveInstances as a, getRenderedNamespaces as b, declareNamespaces as d, forgetRenderedNamespaces as f, getDeclaredNamespaces as g, keepOnlyNamespaces as k, trackInstance as t };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Namespaces the application's modules asked for, by evaluating.
5
+ *
6
+ * The Vite plugin adds one `declareNamespaces([...])` call to every module
7
+ * calling `useTranslation('ns')` (or `withTranslation`, `<Trans ns>`), so
8
+ * evaluating a module -- a page, a layout, any component it statically
9
+ * imports -- records which namespaces it will translate with. A bundler loads
10
+ * a page together with everything it imports, so by the time a page is
11
+ * resolved this set holds exactly the namespaces that page needs, however
12
+ * deeply they are nested, with nothing to declare by hand. `preloadI18n()`
13
+ * reads it.
14
+ *
15
+ * Only ever grows, and is shared by every i18next instance of the process.
16
+ *
17
+ * It lives on `globalThis`, under a registered symbol, and the code the Vite
18
+ * plugin injects writes to it directly instead of importing this module: a
19
+ * module can be evaluated before this one (a bundler is free to order a
20
+ * package's chunks around import cycles), can sit in a package that does not
21
+ * depend on this one, and a page bundle and a server bundle can each carry
22
+ * their own copy of this module -- none of that may lose a declaration.
23
+ */ const DECLARED_NAMESPACES_KEY = 'laravel-i18next.declared';
24
+ function declaredNamespaces() {
25
+ var _a;
26
+ const store = globalThis;
27
+ const key = Symbol.for(DECLARED_NAMESPACES_KEY);
28
+ return (_a = store[key]) !== null && _a !== void 0 ? _a : store[key] = new Set();
29
+ }
30
+ /**
31
+ * Record namespaces a module translates with. Called by the code the Vite
32
+ * plugin injects; call it yourself for a namespace requested dynamically
33
+ * (`useTranslation(name)`), which the plugin cannot see.
34
+ */ function declareNamespaces(namespaces) {
35
+ (typeof namespaces === 'string' ? [
36
+ namespaces
37
+ ] : namespaces).forEach((namespace)=>declaredNamespaces().add(namespace));
38
+ }
39
+ function getDeclaredNamespaces() {
40
+ return Array.from(declaredNamespaces());
41
+ }
42
+ /**
43
+ * The instances living in this process, weakly held. Only browser instances
44
+ * are tracked (one per app, living as long as the page): a server creates one
45
+ * instance per request, which nothing may keep alive, and needs no tracking
46
+ * anyway since each request's instance is created after its translations are
47
+ * preloaded.
48
+ */ const instances = new Set();
49
+ function trackInstance(instance) {
50
+ if (typeof window === 'undefined' || typeof WeakRef === 'undefined') return;
51
+ instances.add(new WeakRef(instance));
52
+ }
53
+ function reportOf(instance) {
54
+ return instance.reportNamespaces;
55
+ }
56
+ /**
57
+ * The namespaces the components of the live browser instances have rendered
58
+ * with since `forgetRenderedNamespaces()`, or none when nothing recorded them
59
+ * (another framework, or nothing has rendered yet).
60
+ */ function getRenderedNamespaces() {
61
+ return Array.from(new Set(getLiveInstances().flatMap((instance)=>{
62
+ var _a, _b;
63
+ return Object.keys((_b = (_a = reportOf(instance)) === null || _a === void 0 ? void 0 : _a.usedNamespaces) !== null && _b !== void 0 ? _b : {});
64
+ })));
65
+ }
66
+ function forgetRenderedNamespaces() {
67
+ getLiveInstances().forEach((instance)=>{
68
+ const report = reportOf(instance);
69
+ if (report) report.usedNamespaces = {};
70
+ });
71
+ }
72
+ /**
73
+ * Make the live instances load only these namespaces the next time their
74
+ * language changes. i18next remembers every namespace it ever loaded and
75
+ * loads them all again for a new language; this forgets the others, which a
76
+ * later `loadNamespaces()` (see `preloadI18n()`) brings back when a page
77
+ * asks for them.
78
+ */ function keepOnlyNamespaces(namespaces) {
79
+ getLiveInstances().forEach((instance)=>{
80
+ const loaded = instance.options.ns;
81
+ if (Array.isArray(loaded)) instance.options.ns = loaded.filter((namespace)=>namespaces.includes(namespace));
82
+ });
83
+ }
84
+ function getLiveInstances() {
85
+ const live = [];
86
+ instances.forEach((reference)=>{
87
+ const instance = reference.deref();
88
+ if (instance) live.push(instance);
89
+ else instances.delete(reference);
90
+ });
91
+ return live;
92
+ }
93
+
94
+ exports.DECLARED_NAMESPACES_KEY = DECLARED_NAMESPACES_KEY;
95
+ exports.declareNamespaces = declareNamespaces;
96
+ exports.forgetRenderedNamespaces = forgetRenderedNamespaces;
97
+ exports.getDeclaredNamespaces = getDeclaredNamespaces;
98
+ exports.getLiveInstances = getLiveInstances;
99
+ exports.getRenderedNamespaces = getRenderedNamespaces;
100
+ exports.keepOnlyNamespaces = keepOnlyNamespaces;
101
+ exports.trackInstance = trackInstance;
package/dist/svelte.cjs CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  var svelte = require('svelte');
4
4
  var store = require('svelte/store');
5
- var createI18n = require('./shared/create-i18n-WsDK4Z8L.cjs');
5
+ var createI18n = require('./shared/create-i18n-DI7Eog6O.cjs');
6
6
  require('i18next');
7
+ require('./shared/demand-sbjTXVnl.cjs');
7
8
 
8
9
  const CONTEXT_KEY = 'laravel-i18next';
9
10
  /**
@@ -54,6 +55,11 @@ function getI18nextFromContext() {
54
55
  const instance = i18nextInstance !== null && i18nextInstance !== void 0 ? i18nextInstance : getI18nextFromContext();
55
56
  const revision = store.writable(0);
56
57
  const bump = ()=>revision.update((n)=>n + 1);
58
+ // Not preloaded (see `preloadI18n()`): load it now, `ready` turns true once it arrives.
59
+ const current = instance.language || instance.options.lng;
60
+ if (namespace && (!current || !instance.hasResourceBundle(current, namespace))) {
61
+ void instance.loadNamespaces(namespace);
62
+ }
57
63
  instance.on('languageChanged', bump);
58
64
  instance.on('loaded', bump);
59
65
  instance.on('added', bump);