@zachhandley/ez-i18n 0.2.0 → 0.2.2
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.
- package/dist/index.js +48 -14
- package/dist/runtime/react-plugin.d.ts +1 -2
- package/dist/runtime/react-plugin.js +1 -44
- package/dist/runtime/vue-plugin.d.ts +1 -2
- package/dist/runtime/vue-plugin.js +4 -47
- package/package.json +1 -1
- package/src/runtime/react-plugin.ts +2 -1
- package/src/runtime/vue-plugin.ts +2 -1
- package/src/vite-plugin.ts +58 -17
package/dist/index.js
CHANGED
|
@@ -435,6 +435,7 @@ export function t(key, params) {
|
|
|
435
435
|
function generateDevTranslationsModule(translationInfo, projectRoot, pathBasedNamespacing) {
|
|
436
436
|
const imports = [];
|
|
437
437
|
const loaderEntries = [];
|
|
438
|
+
let needsPublicLoader = false;
|
|
438
439
|
imports.push(getDeepMergeCode());
|
|
439
440
|
if (pathBasedNamespacing) {
|
|
440
441
|
imports.push(generateNamespaceWrapperCode());
|
|
@@ -443,24 +444,29 @@ function generateDevTranslationsModule(translationInfo, projectRoot, pathBasedNa
|
|
|
443
444
|
if (info.files.length === 0) {
|
|
444
445
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => ({})`);
|
|
445
446
|
} else if (info.isPublic) {
|
|
447
|
+
needsPublicLoader = true;
|
|
446
448
|
if (pathBasedNamespacing && info.localeBaseDir) {
|
|
447
|
-
const localeBaseDirForNs = getLocaleBaseDirForNamespace(info.localeBaseDir, projectRoot);
|
|
448
449
|
const fileEntries = info.files.map((f) => {
|
|
449
450
|
const url = toPublicUrl(f, projectRoot);
|
|
451
|
+
const absolutePath = f.replace(/\\/g, "/");
|
|
450
452
|
const namespace = getNamespaceFromPath(f, info.localeBaseDir);
|
|
451
|
-
return `{ url: ${JSON.stringify(url)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
453
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
452
454
|
});
|
|
453
455
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
454
456
|
const fileInfos = [${fileEntries.join(", ")}];
|
|
455
|
-
const responses = await Promise.all(fileInfos.map(f =>
|
|
457
|
+
const responses = await Promise.all(fileInfos.map(f => __loadPublicJson(f.url, f.path)));
|
|
456
458
|
const wrapped = responses.map((content, i) => __wrapWithNamespace(fileInfos[i].namespace, content));
|
|
457
459
|
return __deepMerge({}, ...wrapped);
|
|
458
460
|
}`);
|
|
459
461
|
} else {
|
|
460
|
-
const
|
|
462
|
+
const fileEntries = info.files.map((f) => {
|
|
463
|
+
const url = toPublicUrl(f, projectRoot);
|
|
464
|
+
const absolutePath = f.replace(/\\/g, "/");
|
|
465
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)} }`;
|
|
466
|
+
});
|
|
461
467
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
462
|
-
const
|
|
463
|
-
const responses = await Promise.all(
|
|
468
|
+
const files = [${fileEntries.join(", ")}];
|
|
469
|
+
const responses = await Promise.all(files.map(f => __loadPublicJson(f.url, f.path)));
|
|
464
470
|
if (responses.length === 1) return responses[0];
|
|
465
471
|
return __deepMerge({}, ...responses);
|
|
466
472
|
}`);
|
|
@@ -531,6 +537,9 @@ function generateDevTranslationsModule(translationInfo, projectRoot, pathBasedNa
|
|
|
531
537
|
}
|
|
532
538
|
}
|
|
533
539
|
}
|
|
540
|
+
if (needsPublicLoader) {
|
|
541
|
+
imports.push(getPublicLoaderCode());
|
|
542
|
+
}
|
|
534
543
|
return `
|
|
535
544
|
${imports.join("\n")}
|
|
536
545
|
|
|
@@ -562,32 +571,42 @@ function generateBuildTranslationsModule(translationInfo, projectRoot, pathBased
|
|
|
562
571
|
const loaderEntries = [];
|
|
563
572
|
let needsDeepMerge = false;
|
|
564
573
|
let needsNamespaceWrapper = false;
|
|
574
|
+
let needsPublicLoader = false;
|
|
565
575
|
for (const [locale, info] of translationInfo) {
|
|
566
576
|
if (info.files.length === 0) {
|
|
567
577
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => ({})`);
|
|
568
578
|
} else if (info.isPublic) {
|
|
579
|
+
needsPublicLoader = true;
|
|
569
580
|
needsDeepMerge = info.files.length > 1;
|
|
570
581
|
if (pathBasedNamespacing && info.localeBaseDir) {
|
|
571
582
|
needsNamespaceWrapper = true;
|
|
572
583
|
const fileEntries = info.files.map((f) => {
|
|
573
584
|
const url = toPublicUrl(f, projectRoot);
|
|
585
|
+
const absolutePath = f.replace(/\\/g, "/");
|
|
574
586
|
const namespace = getNamespaceFromPath(f, info.localeBaseDir);
|
|
575
|
-
return `{ url: ${JSON.stringify(url)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
587
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
576
588
|
});
|
|
577
589
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
578
590
|
const fileInfos = [${fileEntries.join(", ")}];
|
|
579
|
-
const responses = await Promise.all(fileInfos.map(f =>
|
|
591
|
+
const responses = await Promise.all(fileInfos.map(f => __loadPublicJson(f.url, f.path)));
|
|
580
592
|
const wrapped = responses.map((content, i) => __wrapWithNamespace(fileInfos[i].namespace, content));
|
|
581
593
|
return __deepMerge({}, ...wrapped);
|
|
582
594
|
}`);
|
|
583
595
|
} else {
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
596
|
+
const fileEntries = info.files.map((f) => {
|
|
597
|
+
const url = toPublicUrl(f, projectRoot);
|
|
598
|
+
const absolutePath = f.replace(/\\/g, "/");
|
|
599
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)} }`;
|
|
600
|
+
});
|
|
601
|
+
if (fileEntries.length === 1) {
|
|
602
|
+
const f = info.files[0];
|
|
603
|
+
const url = toPublicUrl(f, projectRoot);
|
|
604
|
+
const absolutePath = f.replace(/\\/g, "/");
|
|
605
|
+
loaderEntries.push(` ${JSON.stringify(locale)}: () => __loadPublicJson(${JSON.stringify(url)}, ${JSON.stringify(absolutePath)})`);
|
|
587
606
|
} else {
|
|
588
607
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
589
|
-
const
|
|
590
|
-
const responses = await Promise.all(
|
|
608
|
+
const files = [${fileEntries.join(", ")}];
|
|
609
|
+
const responses = await Promise.all(files.map(f => __loadPublicJson(f.url, f.path)));
|
|
591
610
|
return __deepMerge({}, ...responses);
|
|
592
611
|
}`);
|
|
593
612
|
}
|
|
@@ -632,7 +651,8 @@ function generateBuildTranslationsModule(translationInfo, projectRoot, pathBased
|
|
|
632
651
|
}
|
|
633
652
|
const helperCode = [
|
|
634
653
|
needsDeepMerge ? getDeepMergeCode() : "",
|
|
635
|
-
needsNamespaceWrapper ? generateNamespaceWrapperCode() : ""
|
|
654
|
+
needsNamespaceWrapper ? generateNamespaceWrapperCode() : "",
|
|
655
|
+
needsPublicLoader ? getPublicLoaderCode() : ""
|
|
636
656
|
].filter(Boolean).join("\n");
|
|
637
657
|
return `
|
|
638
658
|
${helperCode}
|
|
@@ -676,6 +696,20 @@ function __deepMerge(target, ...sources) {
|
|
|
676
696
|
return result;
|
|
677
697
|
}`;
|
|
678
698
|
}
|
|
699
|
+
function getPublicLoaderCode() {
|
|
700
|
+
return `
|
|
701
|
+
async function __loadPublicJson(url, absolutePath) {
|
|
702
|
+
if (typeof window !== 'undefined') {
|
|
703
|
+
// Browser - use fetch with relative URL
|
|
704
|
+
return fetch(url).then(r => r.json());
|
|
705
|
+
} else {
|
|
706
|
+
// SSR/Node - read from filesystem
|
|
707
|
+
const fs = await import('node:fs');
|
|
708
|
+
const content = fs.readFileSync(absolutePath, 'utf-8');
|
|
709
|
+
return JSON.parse(content);
|
|
710
|
+
}
|
|
711
|
+
}`;
|
|
712
|
+
}
|
|
679
713
|
function resolveConfig(config) {
|
|
680
714
|
const isAutoDiscovery = !config.translations || typeof config.translations === "string";
|
|
681
715
|
return {
|
|
@@ -1,49 +1,6 @@
|
|
|
1
1
|
// src/runtime/react-plugin.ts
|
|
2
2
|
import { useStore } from "@nanostores/react";
|
|
3
|
-
|
|
4
|
-
// src/runtime/store.ts
|
|
5
|
-
import { atom, computed } from "nanostores";
|
|
6
|
-
import { persistentAtom } from "@nanostores/persistent";
|
|
7
|
-
var serverLocale = atom(null);
|
|
8
|
-
var localePreference = persistentAtom("ez-locale", "en", {
|
|
9
|
-
encode: (value) => value,
|
|
10
|
-
decode: (value) => value
|
|
11
|
-
});
|
|
12
|
-
var effectiveLocale = computed(
|
|
13
|
-
[serverLocale, localePreference],
|
|
14
|
-
(server, client) => server ?? client
|
|
15
|
-
);
|
|
16
|
-
var translations = atom({});
|
|
17
|
-
var localeLoading = atom(false);
|
|
18
|
-
async function setLocale(locale, options = {}) {
|
|
19
|
-
const opts = typeof options === "string" ? { cookieName: options } : options;
|
|
20
|
-
const { cookieName = "ez-locale", loadTranslations } = opts;
|
|
21
|
-
localeLoading.set(true);
|
|
22
|
-
try {
|
|
23
|
-
if (loadTranslations) {
|
|
24
|
-
const mod = await loadTranslations();
|
|
25
|
-
const trans = "default" in mod ? mod.default : mod;
|
|
26
|
-
translations.set(trans);
|
|
27
|
-
}
|
|
28
|
-
localePreference.set(locale);
|
|
29
|
-
serverLocale.set(locale);
|
|
30
|
-
if (typeof document !== "undefined") {
|
|
31
|
-
document.cookie = `${cookieName}=${locale}; path=/; max-age=31536000; samesite=lax`;
|
|
32
|
-
}
|
|
33
|
-
if (typeof document !== "undefined") {
|
|
34
|
-
document.dispatchEvent(
|
|
35
|
-
new CustomEvent("ez-i18n:locale-changed", {
|
|
36
|
-
detail: { locale },
|
|
37
|
-
bubbles: true
|
|
38
|
-
})
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
} finally {
|
|
42
|
-
localeLoading.set(false);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// src/runtime/react-plugin.ts
|
|
3
|
+
import { effectiveLocale, translations, setLocale } from "@zachhandley/ez-i18n/runtime";
|
|
47
4
|
function getNestedValue(obj, path) {
|
|
48
5
|
const keys = path.split(".");
|
|
49
6
|
let value = obj;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
2
|
import { Plugin } from 'vue';
|
|
3
|
-
import { setLocale } from '
|
|
3
|
+
import { setLocale } from '@zachhandley/ez-i18n/runtime';
|
|
4
4
|
import { T as TranslateFunction } from '../types-Cd9e7Lkc.js';
|
|
5
|
-
import 'nanostores';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Vue plugin that provides global $t(), $locale, and $setLocale
|
|
@@ -1,50 +1,7 @@
|
|
|
1
1
|
// src/runtime/vue-plugin.ts
|
|
2
|
-
import { computed
|
|
2
|
+
import { computed } from "vue";
|
|
3
3
|
import { useStore } from "@nanostores/vue";
|
|
4
|
-
|
|
5
|
-
// src/runtime/store.ts
|
|
6
|
-
import { atom, computed } from "nanostores";
|
|
7
|
-
import { persistentAtom } from "@nanostores/persistent";
|
|
8
|
-
var serverLocale = atom(null);
|
|
9
|
-
var localePreference = persistentAtom("ez-locale", "en", {
|
|
10
|
-
encode: (value) => value,
|
|
11
|
-
decode: (value) => value
|
|
12
|
-
});
|
|
13
|
-
var effectiveLocale = computed(
|
|
14
|
-
[serverLocale, localePreference],
|
|
15
|
-
(server, client) => server ?? client
|
|
16
|
-
);
|
|
17
|
-
var translations = atom({});
|
|
18
|
-
var localeLoading = atom(false);
|
|
19
|
-
async function setLocale(locale, options = {}) {
|
|
20
|
-
const opts = typeof options === "string" ? { cookieName: options } : options;
|
|
21
|
-
const { cookieName = "ez-locale", loadTranslations } = opts;
|
|
22
|
-
localeLoading.set(true);
|
|
23
|
-
try {
|
|
24
|
-
if (loadTranslations) {
|
|
25
|
-
const mod = await loadTranslations();
|
|
26
|
-
const trans = "default" in mod ? mod.default : mod;
|
|
27
|
-
translations.set(trans);
|
|
28
|
-
}
|
|
29
|
-
localePreference.set(locale);
|
|
30
|
-
serverLocale.set(locale);
|
|
31
|
-
if (typeof document !== "undefined") {
|
|
32
|
-
document.cookie = `${cookieName}=${locale}; path=/; max-age=31536000; samesite=lax`;
|
|
33
|
-
}
|
|
34
|
-
if (typeof document !== "undefined") {
|
|
35
|
-
document.dispatchEvent(
|
|
36
|
-
new CustomEvent("ez-i18n:locale-changed", {
|
|
37
|
-
detail: { locale },
|
|
38
|
-
bubbles: true
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
} finally {
|
|
43
|
-
localeLoading.set(false);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// src/runtime/vue-plugin.ts
|
|
4
|
+
import { effectiveLocale, translations, setLocale } from "@zachhandley/ez-i18n/runtime";
|
|
48
5
|
function getNestedValue(obj, path) {
|
|
49
6
|
const keys = path.split(".");
|
|
50
7
|
let value = obj;
|
|
@@ -79,7 +36,7 @@ var ezI18nVue = {
|
|
|
79
36
|
install(app) {
|
|
80
37
|
const locale = useStore(effectiveLocale);
|
|
81
38
|
const trans = useStore(translations);
|
|
82
|
-
const transComputed =
|
|
39
|
+
const transComputed = computed(() => trans.value);
|
|
83
40
|
const t = createTranslateFunction(transComputed);
|
|
84
41
|
app.config.globalProperties.$t = t;
|
|
85
42
|
app.config.globalProperties.$locale = locale;
|
|
@@ -94,7 +51,7 @@ var ezI18nVue = {
|
|
|
94
51
|
function useI18n() {
|
|
95
52
|
const locale = useStore(effectiveLocale);
|
|
96
53
|
const trans = useStore(translations);
|
|
97
|
-
const transComputed =
|
|
54
|
+
const transComputed = computed(() => trans.value);
|
|
98
55
|
const t = createTranslateFunction(transComputed);
|
|
99
56
|
return {
|
|
100
57
|
t,
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useStore } from '@nanostores/react';
|
|
2
|
-
|
|
2
|
+
// Import from package path (not relative) to ensure shared store instance
|
|
3
|
+
import { effectiveLocale, translations, setLocale } from '@zachhandley/ez-i18n/runtime';
|
|
3
4
|
import type { TranslateFunction } from '../types';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { App, Plugin, ComputedRef } from 'vue';
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { useStore } from '@nanostores/vue';
|
|
4
|
-
|
|
4
|
+
// Import from package path (not relative) to ensure shared store instance
|
|
5
|
+
import { effectiveLocale, translations, setLocale } from '@zachhandley/ez-i18n/runtime';
|
|
5
6
|
import type { TranslateFunction } from '../types';
|
|
6
7
|
|
|
7
8
|
/**
|
package/src/vite-plugin.ts
CHANGED
|
@@ -346,7 +346,7 @@ export function t(key, params) {
|
|
|
346
346
|
/**
|
|
347
347
|
* Generate the translations virtual module for dev mode.
|
|
348
348
|
* Uses import.meta.glob where possible for HMR support.
|
|
349
|
-
* Uses fetch() for files in public/ directory.
|
|
349
|
+
* Uses fetch() for files in public/ directory (with fs fallback for SSR).
|
|
350
350
|
*/
|
|
351
351
|
function generateDevTranslationsModule(
|
|
352
352
|
translationInfo: Map<string, TranslationInfo>,
|
|
@@ -355,6 +355,7 @@ function generateDevTranslationsModule(
|
|
|
355
355
|
): string {
|
|
356
356
|
const imports: string[] = [];
|
|
357
357
|
const loaderEntries: string[] = [];
|
|
358
|
+
let needsPublicLoader = false;
|
|
358
359
|
|
|
359
360
|
// Add deepMerge inline for runtime merging
|
|
360
361
|
imports.push(getDeepMergeCode());
|
|
@@ -369,26 +370,31 @@ function generateDevTranslationsModule(
|
|
|
369
370
|
// No files - return empty object
|
|
370
371
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => ({})`);
|
|
371
372
|
} else if (info.isPublic) {
|
|
372
|
-
// Public directory files - use fetch
|
|
373
|
+
// Public directory files - use fetch in browser, fs in SSR
|
|
374
|
+
needsPublicLoader = true;
|
|
373
375
|
if (pathBasedNamespacing && info.localeBaseDir) {
|
|
374
|
-
const localeBaseDirForNs = getLocaleBaseDirForNamespace(info.localeBaseDir, projectRoot);
|
|
375
376
|
const fileEntries = info.files.map(f => {
|
|
376
377
|
const url = toPublicUrl(f, projectRoot);
|
|
378
|
+
const absolutePath = f.replace(/\\/g, '/');
|
|
377
379
|
const namespace = getNamespaceFromPath(f, info.localeBaseDir!);
|
|
378
|
-
return `{ url: ${JSON.stringify(url)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
380
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
379
381
|
});
|
|
380
382
|
|
|
381
383
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
382
384
|
const fileInfos = [${fileEntries.join(', ')}];
|
|
383
|
-
const responses = await Promise.all(fileInfos.map(f =>
|
|
385
|
+
const responses = await Promise.all(fileInfos.map(f => __loadPublicJson(f.url, f.path)));
|
|
384
386
|
const wrapped = responses.map((content, i) => __wrapWithNamespace(fileInfos[i].namespace, content));
|
|
385
387
|
return __deepMerge({}, ...wrapped);
|
|
386
388
|
}`);
|
|
387
389
|
} else {
|
|
388
|
-
const
|
|
390
|
+
const fileEntries = info.files.map(f => {
|
|
391
|
+
const url = toPublicUrl(f, projectRoot);
|
|
392
|
+
const absolutePath = f.replace(/\\/g, '/');
|
|
393
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)} }`;
|
|
394
|
+
});
|
|
389
395
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
390
|
-
const
|
|
391
|
-
const responses = await Promise.all(
|
|
396
|
+
const files = [${fileEntries.join(', ')}];
|
|
397
|
+
const responses = await Promise.all(files.map(f => __loadPublicJson(f.url, f.path)));
|
|
392
398
|
if (responses.length === 1) return responses[0];
|
|
393
399
|
return __deepMerge({}, ...responses);
|
|
394
400
|
}`);
|
|
@@ -470,6 +476,11 @@ function generateDevTranslationsModule(
|
|
|
470
476
|
}
|
|
471
477
|
}
|
|
472
478
|
|
|
479
|
+
// Add public loader helper if needed
|
|
480
|
+
if (needsPublicLoader) {
|
|
481
|
+
imports.push(getPublicLoaderCode());
|
|
482
|
+
}
|
|
483
|
+
|
|
473
484
|
return `
|
|
474
485
|
${imports.join('\n')}
|
|
475
486
|
|
|
@@ -501,7 +512,7 @@ export async function loadTranslations(locale) {
|
|
|
501
512
|
/**
|
|
502
513
|
* Generate the translations virtual module for production builds.
|
|
503
514
|
* Pre-resolves all imports for optimal bundling.
|
|
504
|
-
* Uses fetch() for files in public/ directory.
|
|
515
|
+
* Uses fetch() for files in public/ directory (with fs fallback for SSR).
|
|
505
516
|
*/
|
|
506
517
|
function generateBuildTranslationsModule(
|
|
507
518
|
translationInfo: Map<string, TranslationInfo>,
|
|
@@ -511,35 +522,45 @@ function generateBuildTranslationsModule(
|
|
|
511
522
|
const loaderEntries: string[] = [];
|
|
512
523
|
let needsDeepMerge = false;
|
|
513
524
|
let needsNamespaceWrapper = false;
|
|
525
|
+
let needsPublicLoader = false;
|
|
514
526
|
|
|
515
527
|
for (const [locale, info] of translationInfo) {
|
|
516
528
|
if (info.files.length === 0) {
|
|
517
529
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => ({})`);
|
|
518
530
|
} else if (info.isPublic) {
|
|
519
|
-
// Public directory files - use fetch
|
|
531
|
+
// Public directory files - use fetch in browser, fs in SSR
|
|
532
|
+
needsPublicLoader = true;
|
|
520
533
|
needsDeepMerge = info.files.length > 1;
|
|
521
534
|
if (pathBasedNamespacing && info.localeBaseDir) {
|
|
522
535
|
needsNamespaceWrapper = true;
|
|
523
536
|
const fileEntries = info.files.map(f => {
|
|
524
537
|
const url = toPublicUrl(f, projectRoot);
|
|
538
|
+
const absolutePath = f.replace(/\\/g, '/');
|
|
525
539
|
const namespace = getNamespaceFromPath(f, info.localeBaseDir!);
|
|
526
|
-
return `{ url: ${JSON.stringify(url)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
540
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)}, namespace: ${JSON.stringify(namespace)} }`;
|
|
527
541
|
});
|
|
528
542
|
|
|
529
543
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
530
544
|
const fileInfos = [${fileEntries.join(', ')}];
|
|
531
|
-
const responses = await Promise.all(fileInfos.map(f =>
|
|
545
|
+
const responses = await Promise.all(fileInfos.map(f => __loadPublicJson(f.url, f.path)));
|
|
532
546
|
const wrapped = responses.map((content, i) => __wrapWithNamespace(fileInfos[i].namespace, content));
|
|
533
547
|
return __deepMerge({}, ...wrapped);
|
|
534
548
|
}`);
|
|
535
549
|
} else {
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
550
|
+
const fileEntries = info.files.map(f => {
|
|
551
|
+
const url = toPublicUrl(f, projectRoot);
|
|
552
|
+
const absolutePath = f.replace(/\\/g, '/');
|
|
553
|
+
return `{ url: ${JSON.stringify(url)}, path: ${JSON.stringify(absolutePath)} }`;
|
|
554
|
+
});
|
|
555
|
+
if (fileEntries.length === 1) {
|
|
556
|
+
const f = info.files[0];
|
|
557
|
+
const url = toPublicUrl(f, projectRoot);
|
|
558
|
+
const absolutePath = f.replace(/\\/g, '/');
|
|
559
|
+
loaderEntries.push(` ${JSON.stringify(locale)}: () => __loadPublicJson(${JSON.stringify(url)}, ${JSON.stringify(absolutePath)})`);
|
|
539
560
|
} else {
|
|
540
561
|
loaderEntries.push(` ${JSON.stringify(locale)}: async () => {
|
|
541
|
-
const
|
|
542
|
-
const responses = await Promise.all(
|
|
562
|
+
const files = [${fileEntries.join(', ')}];
|
|
563
|
+
const responses = await Promise.all(files.map(f => __loadPublicJson(f.url, f.path)));
|
|
543
564
|
return __deepMerge({}, ...responses);
|
|
544
565
|
}`);
|
|
545
566
|
}
|
|
@@ -593,6 +614,7 @@ function generateBuildTranslationsModule(
|
|
|
593
614
|
const helperCode = [
|
|
594
615
|
needsDeepMerge ? getDeepMergeCode() : '',
|
|
595
616
|
needsNamespaceWrapper ? generateNamespaceWrapperCode() : '',
|
|
617
|
+
needsPublicLoader ? getPublicLoaderCode() : '',
|
|
596
618
|
].filter(Boolean).join('\n');
|
|
597
619
|
|
|
598
620
|
return `
|
|
@@ -642,6 +664,25 @@ function __deepMerge(target, ...sources) {
|
|
|
642
664
|
}`;
|
|
643
665
|
}
|
|
644
666
|
|
|
667
|
+
/**
|
|
668
|
+
* Inline public JSON loader for the virtual module.
|
|
669
|
+
* Uses fetch in browser, fs.readFileSync in SSR/Node.
|
|
670
|
+
*/
|
|
671
|
+
function getPublicLoaderCode(): string {
|
|
672
|
+
return `
|
|
673
|
+
async function __loadPublicJson(url, absolutePath) {
|
|
674
|
+
if (typeof window !== 'undefined') {
|
|
675
|
+
// Browser - use fetch with relative URL
|
|
676
|
+
return fetch(url).then(r => r.json());
|
|
677
|
+
} else {
|
|
678
|
+
// SSR/Node - read from filesystem
|
|
679
|
+
const fs = await import('node:fs');
|
|
680
|
+
const content = fs.readFileSync(absolutePath, 'utf-8');
|
|
681
|
+
return JSON.parse(content);
|
|
682
|
+
}
|
|
683
|
+
}`;
|
|
684
|
+
}
|
|
685
|
+
|
|
645
686
|
// Re-export resolveConfig for backwards compatibility
|
|
646
687
|
export function resolveConfig(config: EzI18nConfig): ResolvedEzI18nConfig {
|
|
647
688
|
// This is now a simplified version - full resolution happens in buildStart
|