@inlang/paraglide-js 2.0.5 → 2.0.6
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/compiler/compile-project.test.js +45 -0
- package/dist/compiler/compile.d.ts.map +1 -1
- package/dist/compiler/compile.js +6 -0
- package/dist/compiler/compile.test.js +53 -0
- package/dist/compiler/output-structure/locale-modules.d.ts.map +1 -1
- package/dist/compiler/output-structure/locale-modules.js +72 -6
- package/dist/compiler/output-structure/locale-modules.test.js +44 -0
- package/dist/services/env-variables/index.js +1 -1
- package/package.json +3 -3
|
@@ -545,6 +545,30 @@ describe.each([
|
|
|
545
545
|
runtime.setLocale("en-US");
|
|
546
546
|
expect(m.missing_in_en_US()).toBe("Fallback message.");
|
|
547
547
|
});
|
|
548
|
+
test("arbitrary module identifiers", async () => {
|
|
549
|
+
const project = await loadProjectInMemory({
|
|
550
|
+
blob: await newProject({
|
|
551
|
+
settings: { locales: ["en"], baseLocale: "en" },
|
|
552
|
+
}),
|
|
553
|
+
});
|
|
554
|
+
await insertBundleNested(project.db, createBundleNested({
|
|
555
|
+
id: "happy🍌",
|
|
556
|
+
messages: [
|
|
557
|
+
{
|
|
558
|
+
locale: "en",
|
|
559
|
+
variants: [{ pattern: [{ type: "text", value: "Hello" }] }],
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
}));
|
|
563
|
+
const output = await compileProject({
|
|
564
|
+
project,
|
|
565
|
+
compilerOptions,
|
|
566
|
+
});
|
|
567
|
+
const code = await bundleCode(output, `export * as m from "./paraglide/messages.js"
|
|
568
|
+
export * as runtime from "./paraglide/runtime.js"`);
|
|
569
|
+
const { m } = await importCode(code);
|
|
570
|
+
expect(m["happy🍌"]()).toBe("Hello");
|
|
571
|
+
});
|
|
548
572
|
});
|
|
549
573
|
test("case sensitivity handling for bundle IDs", async () => {
|
|
550
574
|
// skip local modules for now because the option might get removed in the future
|
|
@@ -719,6 +743,27 @@ const mockBundles = [
|
|
|
719
743
|
},
|
|
720
744
|
],
|
|
721
745
|
}),
|
|
746
|
+
createBundleNested({
|
|
747
|
+
id: "Sad_penguin_bundle",
|
|
748
|
+
messages: [
|
|
749
|
+
{
|
|
750
|
+
locale: "en",
|
|
751
|
+
variants: [
|
|
752
|
+
{ pattern: [{ type: "text", value: "Capital Sad penguin" }] },
|
|
753
|
+
],
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
locale: "de",
|
|
757
|
+
variants: [
|
|
758
|
+
{
|
|
759
|
+
pattern: [
|
|
760
|
+
{ type: "text", value: "Grossgeschriebenes Sad penguin" },
|
|
761
|
+
],
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
}),
|
|
722
767
|
{
|
|
723
768
|
id: "depressed_dog",
|
|
724
769
|
declarations: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compiler/compile.ts"],"names":[],"mappings":"AASA,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compiler/compile.ts"],"names":[],"mappings":"AASA,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAS/B,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CACjD,CAAC;AAIF;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC5B,OAAO,EAAE,eAAe,GAAG;IAC1B,mBAAmB,CAAC,EAAE,iBAAiB,CAAC;CACxC,GACC,OAAO,CAAC,iBAAiB,CAAC,CAqE5B"}
|
package/dist/compiler/compile.js
CHANGED
|
@@ -5,9 +5,11 @@ import { compileProject } from "./compile-project.js";
|
|
|
5
5
|
import { writeOutput } from "../services/file-handling/write-output.js";
|
|
6
6
|
import { getLocalAccount, saveLocalAccount, } from "../services/account/index.js";
|
|
7
7
|
import { defaultCompilerOptions, } from "./compiler-options.js";
|
|
8
|
+
import { Logger } from "../services/logger/index.js";
|
|
8
9
|
// This is a workaround to prevent multiple compilations from running at the same time.
|
|
9
10
|
// https://github.com/opral/inlang-paraglide-js/issues/320#issuecomment-2596951222
|
|
10
11
|
let compilationInProgress = null;
|
|
12
|
+
const logger = new Logger();
|
|
11
13
|
/**
|
|
12
14
|
* Loads, compiles, and writes the output to disk.
|
|
13
15
|
*
|
|
@@ -58,6 +60,10 @@ export async function compile(options) {
|
|
|
58
60
|
.executeTakeFirstOrThrow();
|
|
59
61
|
saveLocalAccount({ fs, account: activeAccount });
|
|
60
62
|
}
|
|
63
|
+
const warningsAndErrors = await project.errors.get();
|
|
64
|
+
for (const warningOrError of warningsAndErrors) {
|
|
65
|
+
logger.warn(warningOrError);
|
|
66
|
+
}
|
|
61
67
|
await project.close();
|
|
62
68
|
return { outputHashes };
|
|
63
69
|
}
|
|
@@ -4,6 +4,7 @@ import { test, expect, vi } from "vitest";
|
|
|
4
4
|
import { compile } from "./compile.js";
|
|
5
5
|
import { getAccountFilePath } from "../services/account/index.js";
|
|
6
6
|
import { defaultCompilerOptions } from "./compiler-options.js";
|
|
7
|
+
import consola from "consola";
|
|
7
8
|
test("loads a project and compiles it", async () => {
|
|
8
9
|
const project = await loadProjectInMemory({
|
|
9
10
|
blob: await newProject({
|
|
@@ -258,3 +259,55 @@ test("default compiler options should include cookied, variable and baseLocale t
|
|
|
258
259
|
"baseLocale",
|
|
259
260
|
]);
|
|
260
261
|
});
|
|
262
|
+
test("emits warnings for modules that couldn't be imported locally", async () => {
|
|
263
|
+
const project = await loadProjectInMemory({
|
|
264
|
+
blob: await newProject({
|
|
265
|
+
settings: {
|
|
266
|
+
baseLocale: "en",
|
|
267
|
+
locales: ["en", "de", "fr"],
|
|
268
|
+
modules: ["./non-existent-paraglide-plugin.js"],
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
});
|
|
272
|
+
const mock = vi.fn();
|
|
273
|
+
consola.mockTypes(() => mock);
|
|
274
|
+
const fs = memfs().fs;
|
|
275
|
+
// save project to directory to test loading
|
|
276
|
+
await saveProjectToDirectory({
|
|
277
|
+
project,
|
|
278
|
+
path: "/project.inlang",
|
|
279
|
+
fs: fs.promises,
|
|
280
|
+
});
|
|
281
|
+
await compile({
|
|
282
|
+
project: "/project.inlang",
|
|
283
|
+
outdir: "/output",
|
|
284
|
+
fs: fs,
|
|
285
|
+
});
|
|
286
|
+
expect(mock).toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
test("emits warnings for modules that couldn't be imported via http", async () => {
|
|
289
|
+
const project = await loadProjectInMemory({
|
|
290
|
+
blob: await newProject({
|
|
291
|
+
settings: {
|
|
292
|
+
baseLocale: "en",
|
|
293
|
+
locales: ["en", "de", "fr"],
|
|
294
|
+
modules: ["https://example.com/non-existent-paraglide-plugin.js"],
|
|
295
|
+
},
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
298
|
+
const mock = vi.fn();
|
|
299
|
+
consola.mockTypes(() => mock);
|
|
300
|
+
const fs = memfs().fs;
|
|
301
|
+
// save project to directory to test loading
|
|
302
|
+
await saveProjectToDirectory({
|
|
303
|
+
project,
|
|
304
|
+
path: "/project.inlang",
|
|
305
|
+
fs: fs.promises,
|
|
306
|
+
});
|
|
307
|
+
await compile({
|
|
308
|
+
project: "/project.inlang",
|
|
309
|
+
outdir: "/output",
|
|
310
|
+
fs: fs,
|
|
311
|
+
});
|
|
312
|
+
expect(mock).toHaveBeenCalled();
|
|
313
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locale-modules.d.ts","sourceRoot":"","sources":["../../../src/compiler/output-structure/locale-modules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"locale-modules.d.ts","sourceRoot":"","sources":["../../../src/compiler/output-structure/locale-modules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAavE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAY1E;AAED,wBAAgB,cAAc,CAC7B,eAAe,EAAE,0BAA0B,EAAE,EAC7C,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,YAAY,CAAC,EACzD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAC7C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgIxB"}
|
|
@@ -1,15 +1,66 @@
|
|
|
1
1
|
import { toSafeModuleId } from "../safe-module-id.js";
|
|
2
2
|
import { inputsType } from "../jsdoc-types.js";
|
|
3
|
+
// Helper function to escape special characters in a string for use in a regular expression
|
|
4
|
+
function escapeRegExp(string) {
|
|
5
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6
|
+
}
|
|
7
|
+
// This map will be used to track which bundle IDs have been renamed to which unique IDs
|
|
8
|
+
// It will be populated during the generateOutput function and used in messageReferenceExpression
|
|
9
|
+
const bundleIdToUniqueIdMap = new Map();
|
|
3
10
|
export function messageReferenceExpression(locale, bundleId) {
|
|
4
|
-
|
|
11
|
+
// First convert to safe module ID
|
|
12
|
+
const safeModuleId = toSafeModuleId(bundleId);
|
|
13
|
+
// Check if this bundleId has been mapped to a unique identifier
|
|
14
|
+
const uniqueId = bundleIdToUniqueIdMap.get(bundleId);
|
|
15
|
+
if (uniqueId) {
|
|
16
|
+
return `${toSafeModuleId(locale)}.${uniqueId}`;
|
|
17
|
+
}
|
|
18
|
+
// Otherwise, return the default safe module ID
|
|
19
|
+
return `${toSafeModuleId(locale)}.${safeModuleId}`;
|
|
5
20
|
}
|
|
6
21
|
export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
22
|
+
// Create a map to track module IDs in the index file to avoid duplicates
|
|
23
|
+
const indexModuleIdMap = new Map();
|
|
24
|
+
// Process the bundles to ensure no duplicate bundle IDs
|
|
25
|
+
// Generate unique moduleIds for duplicate IDs
|
|
26
|
+
const processedBundleCodes = compiledBundles
|
|
27
|
+
.map(({ bundle }) => {
|
|
28
|
+
const bundleId = bundle.node.id;
|
|
29
|
+
const bundleModuleId = toSafeModuleId(bundleId);
|
|
30
|
+
// Check if this safe module ID has been used before
|
|
31
|
+
if (indexModuleIdMap.has(bundleModuleId)) {
|
|
32
|
+
// Create a unique identifier by adding a counter
|
|
33
|
+
let counter = 1;
|
|
34
|
+
let uniqueModuleId = `${bundleModuleId}${counter}`;
|
|
35
|
+
while (indexModuleIdMap.has(uniqueModuleId)) {
|
|
36
|
+
counter++;
|
|
37
|
+
uniqueModuleId = `${bundleModuleId}${counter}`;
|
|
38
|
+
}
|
|
39
|
+
// Modify the code to use the unique identifier
|
|
40
|
+
const modifiedCode = bundle.code
|
|
41
|
+
.replace(new RegExp(`const ${bundleModuleId} =`, "g"), `const ${uniqueModuleId} =`)
|
|
42
|
+
.replace(new RegExp(`export const ${bundleModuleId} =`, "g"), `export const ${uniqueModuleId} =`)
|
|
43
|
+
.replace(new RegExp(`export { ${bundleModuleId}`, "g"), `export { ${uniqueModuleId}`)
|
|
44
|
+
.replace(
|
|
45
|
+
// Also update the trackMessageCall to use the new identifier
|
|
46
|
+
new RegExp(`trackMessageCall\\("${escapeRegExp(bundleId)}"`, "g"), `trackMessageCall("${bundleId}"`);
|
|
47
|
+
// Store the unique ID mapping
|
|
48
|
+
indexModuleIdMap.set(uniqueModuleId, bundleId);
|
|
49
|
+
// Also store in the global map for messageReferenceExpression to use
|
|
50
|
+
bundleIdToUniqueIdMap.set(bundleId, uniqueModuleId);
|
|
51
|
+
return modifiedCode;
|
|
52
|
+
}
|
|
53
|
+
// Store the mapping
|
|
54
|
+
indexModuleIdMap.set(bundleModuleId, bundleId);
|
|
55
|
+
return bundle.code;
|
|
56
|
+
})
|
|
57
|
+
.join("\n");
|
|
7
58
|
const indexFile = [
|
|
8
59
|
`import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"`,
|
|
9
60
|
settings.locales
|
|
10
61
|
.map((locale) => `import * as ${toSafeModuleId(locale)} from "./${locale}.js"`)
|
|
11
62
|
.join("\n"),
|
|
12
|
-
|
|
63
|
+
processedBundleCodes,
|
|
13
64
|
].join("\n");
|
|
14
65
|
const output = {
|
|
15
66
|
["messages/_index.js"]: indexFile,
|
|
@@ -18,24 +69,39 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
18
69
|
for (const locale of settings.locales) {
|
|
19
70
|
const filename = `messages/${locale}.js`;
|
|
20
71
|
let file = "";
|
|
72
|
+
// Keep track of module IDs to avoid duplicates
|
|
73
|
+
const moduleIdMap = new Map();
|
|
21
74
|
for (const compiledBundle of compiledBundles) {
|
|
22
75
|
const compiledMessage = compiledBundle.messages[locale];
|
|
23
|
-
const bundleModuleId = toSafeModuleId(compiledBundle.bundle.node.id);
|
|
24
76
|
const bundleId = compiledBundle.bundle.node.id;
|
|
77
|
+
const bundleModuleId = toSafeModuleId(compiledBundle.bundle.node.id);
|
|
78
|
+
// Check if this module ID has already been used
|
|
79
|
+
let uniqueModuleId = bundleModuleId;
|
|
80
|
+
if (moduleIdMap.has(bundleModuleId)) {
|
|
81
|
+
// If it has, create a unique ID by adding an index
|
|
82
|
+
let counter = 1;
|
|
83
|
+
uniqueModuleId = `${bundleModuleId}${counter}`;
|
|
84
|
+
while (moduleIdMap.has(uniqueModuleId)) {
|
|
85
|
+
counter++;
|
|
86
|
+
uniqueModuleId = `${bundleModuleId}${counter}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Store this module ID
|
|
90
|
+
moduleIdMap.set(uniqueModuleId, bundleId);
|
|
25
91
|
const inputs = compiledBundle.bundle.node.declarations?.filter((decl) => decl.type === "input-variable") ?? [];
|
|
26
92
|
if (!compiledMessage) {
|
|
27
93
|
const fallbackLocale = fallbackMap[locale];
|
|
28
94
|
if (fallbackLocale) {
|
|
29
95
|
// use the fall back locale e.g. render the message in English if the German message is missing
|
|
30
|
-
file += `\nexport { ${
|
|
96
|
+
file += `\nexport { ${uniqueModuleId} } from "./${fallbackLocale}.js"`;
|
|
31
97
|
}
|
|
32
98
|
else {
|
|
33
99
|
// no fallback exists, render the bundleId
|
|
34
|
-
file += `\n/** @type {(inputs: ${inputsType(inputs)}) => string}
|
|
100
|
+
file += `\n/** @type {(inputs: ${inputsType(inputs)}) => string} */\nexport const ${uniqueModuleId} = () => '${bundleId}'`;
|
|
35
101
|
}
|
|
36
102
|
continue;
|
|
37
103
|
}
|
|
38
|
-
file += `\n\nexport const ${
|
|
104
|
+
file += `\n\nexport const ${uniqueModuleId} = ${compiledMessage.code}`;
|
|
39
105
|
}
|
|
40
106
|
// add import if used
|
|
41
107
|
if (file.includes("registry.")) {
|
|
@@ -59,3 +59,47 @@ test("the files should include files for each locale, even if there are no messa
|
|
|
59
59
|
expect(output).toHaveProperty("messages/de.js");
|
|
60
60
|
expect(output).toHaveProperty("messages/fr.js");
|
|
61
61
|
});
|
|
62
|
+
test("should handle case sensitivity in message IDs correctly", () => {
|
|
63
|
+
const bundles = [
|
|
64
|
+
{
|
|
65
|
+
bundle: {
|
|
66
|
+
code: 'console.log("bundle code");',
|
|
67
|
+
node: {
|
|
68
|
+
id: "sad_penguin_bundle",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
messages: {
|
|
72
|
+
en: {
|
|
73
|
+
code: 'console.log("sad_penguin_bundle");',
|
|
74
|
+
node: {},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
bundle: {
|
|
80
|
+
code: 'console.log("bundle code");',
|
|
81
|
+
node: {
|
|
82
|
+
id: "Sad_penguin_bundle",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
messages: {
|
|
86
|
+
en: {
|
|
87
|
+
code: 'console.log("Sad_penguin_bundle");',
|
|
88
|
+
node: {},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
const settings = {
|
|
94
|
+
locales: ["en"],
|
|
95
|
+
baseLocale: "en",
|
|
96
|
+
};
|
|
97
|
+
const fallbackMap = {};
|
|
98
|
+
const output = generateOutput(bundles, settings, fallbackMap);
|
|
99
|
+
// Check that the output exists
|
|
100
|
+
expect(output).toHaveProperty("messages/en.js");
|
|
101
|
+
// The exported constants should not conflict
|
|
102
|
+
const content = output["messages/en.js"];
|
|
103
|
+
expect(content).toContain("export const sad_penguin_bundle");
|
|
104
|
+
expect(content).toContain("export const sad_penguin_bundle1"); // or some other unique name
|
|
105
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inlang/paraglide-js",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.6",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"unplugin": "^2.1.2",
|
|
33
33
|
"urlpattern-polyfill": "^10.0.0",
|
|
34
34
|
"@inlang/recommend-sherlock": "0.2.1",
|
|
35
|
-
"@inlang/sdk": "2.4.
|
|
35
|
+
"@inlang/sdk": "2.4.5"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@eslint/js": "^9.18.0",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"typescript-eslint": "^8.20.0",
|
|
52
52
|
"vitest": "2.1.8",
|
|
53
53
|
"@inlang/plugin-message-format": "4.0.0",
|
|
54
|
-
"@inlang/paraglide-js": "2.0.
|
|
54
|
+
"@inlang/paraglide-js": "2.0.6",
|
|
55
55
|
"@opral/tsconfig": "1.1.0"
|
|
56
56
|
},
|
|
57
57
|
"keywords": [
|