@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.
@@ -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;AAQ/B,MAAM,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CACjD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC5B,OAAO,EAAE,eAAe,GAAG;IAC1B,mBAAmB,CAAC,EAAE,iBAAiB,CAAC;CACxC,GACC,OAAO,CAAC,iBAAiB,CAAC,CA+D5B"}
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"}
@@ -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;AAIvE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAE1E;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,CAoDxB"}
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
- return `${toSafeModuleId(locale)}.${toSafeModuleId(bundleId)}`;
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
- compiledBundles.map(({ bundle }) => bundle.code).join("\n"),
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 { ${bundleModuleId} } from "./${fallbackLocale}.js"`;
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} */ */\nexport const ${bundleModuleId} = () => '${bundleId}'`;
100
+ file += `\n/** @type {(inputs: ${inputsType(inputs)}) => string} */\nexport const ${uniqueModuleId} = () => '${bundleId}'`;
35
101
  }
36
102
  continue;
37
103
  }
38
- file += `\n\nexport const ${bundleModuleId} = ${compiledMessage.code}`;
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
+ });
@@ -1,5 +1,5 @@
1
1
  export const ENV_VARIABLES = {
2
2
  PARJS_APP_ID: "library.inlang.paraglideJs",
3
3
  PARJS_POSTHOG_TOKEN: "phc_m5yJZCxjOGxF8CJvP5sQ3H0d76xpnLrsmiZHduT4jDz",
4
- PARJS_PACKAGE_VERSION: "2.0.5",
4
+ PARJS_PACKAGE_VERSION: "2.0.6",
5
5
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/paraglide-js",
3
3
  "type": "module",
4
- "version": "2.0.5",
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.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.5",
54
+ "@inlang/paraglide-js": "2.0.6",
55
55
  "@opral/tsconfig": "1.1.0"
56
56
  },
57
57
  "keywords": [