@inlang/paraglide-js 2.5.0 → 2.7.0
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/cli/commands/compile/command.d.ts.map +1 -1
- package/dist/cli/commands/compile/command.js +3 -0
- package/dist/compiler/compile-bundle.js +1 -1
- package/dist/compiler/compile-bundle.test.js +27 -25
- package/dist/compiler/compile-local-variable.js +1 -1
- package/dist/compiler/compile-local-variable.test.js +2 -2
- package/dist/compiler/compile-message.js +7 -7
- package/dist/compiler/compile-message.test.js +147 -1
- package/dist/compiler/compile-pattern.d.ts +1 -1
- package/dist/compiler/compile-pattern.js +2 -2
- package/dist/compiler/compile-pattern.test.js +1 -1
- package/dist/compiler/compile-project.d.ts.map +1 -1
- package/dist/compiler/compile-project.js +5 -0
- package/dist/compiler/compile-project.test.js +12 -0
- package/dist/compiler/compiler-options.d.ts +24 -0
- package/dist/compiler/compiler-options.d.ts.map +1 -1
- package/dist/compiler/compiler-options.js +1 -0
- package/dist/compiler/emit-ts-declarations.d.ts +12 -0
- package/dist/compiler/emit-ts-declarations.d.ts.map +1 -0
- package/dist/compiler/emit-ts-declarations.js +100 -0
- package/dist/compiler/index.d.ts +1 -1
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/jsdoc-types.js +1 -1
- package/dist/compiler/jsdoc-types.test.js +9 -0
- package/dist/compiler/output-structure/locale-modules.d.ts.map +1 -1
- package/dist/compiler/output-structure/locale-modules.js +8 -1
- package/dist/compiler/output-structure/message-modules.d.ts.map +1 -1
- package/dist/compiler/output-structure/message-modules.js +10 -7
- package/dist/compiler/runtime/create-runtime.d.ts.map +1 -1
- package/dist/compiler/runtime/create-runtime.js +42 -0
- package/dist/compiler/types.d.ts +18 -2
- package/dist/compiler/types.d.ts.map +1 -1
- package/dist/services/env-variables/index.js +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/compile/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/compile/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,cAAc,SA4DzB,CAAC"}
|
|
@@ -16,6 +16,7 @@ export const compileCommand = new Command()
|
|
|
16
16
|
"Read more on https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy",
|
|
17
17
|
].join("\n"))
|
|
18
18
|
.requiredOption("--silent", "Only log errors to the console", false)
|
|
19
|
+
.option("--emit-ts-declarations", "Emit .d.ts files for the generated output (requires the typescript package)", defaultCompilerOptions.emitTsDeclarations)
|
|
19
20
|
.action(async (options) => {
|
|
20
21
|
const logger = new Logger({ silent: options.silent, prefix: true });
|
|
21
22
|
const path = resolve(process.cwd(), options.project);
|
|
@@ -25,6 +26,8 @@ export const compileCommand = new Command()
|
|
|
25
26
|
project: path,
|
|
26
27
|
outdir: options.outdir,
|
|
27
28
|
strategy: options.strategy ?? defaultCompilerOptions.strategy,
|
|
29
|
+
emitTsDeclarations: options.emitTsDeclarations ??
|
|
30
|
+
defaultCompilerOptions.emitTsDeclarations,
|
|
28
31
|
});
|
|
29
32
|
}
|
|
30
33
|
catch (e) {
|
|
@@ -49,7 +49,7 @@ ${isSafeBundleId ? "export " : ""}const ${safeBundleId} = (inputs${hasInputs ? "
|
|
|
49
49
|
trackMessageCall("${safeBundleId}", locale)
|
|
50
50
|
${args.availableLocales
|
|
51
51
|
.map((locale, index) => `${index > 0 ? " " : ""}${!isFullyTranslated || index < args.availableLocales.length - 1 ? `if (locale === "${locale}") ` : ""}return ${args.messageReferenceExpression(locale, args.bundle.id)}(inputs)`)
|
|
52
|
-
.join("\n")}${!isFullyTranslated ? `\n return "${args.bundle.id}"` : ""}
|
|
52
|
+
.join("\n")}${!isFullyTranslated ? `\n return /** @type {LocalizedString} */ ("${args.bundle.id}")` : ""}
|
|
53
53
|
};`;
|
|
54
54
|
if (isSafeBundleId === false) {
|
|
55
55
|
code += `\nexport { ${safeBundleId} as "${escapeForDoubleQuoteString(args.bundle.id)}" }`;
|
|
@@ -49,7 +49,7 @@ test("compiles to jsdoc", async () => {
|
|
|
49
49
|
*
|
|
50
50
|
* @param {{ age: NonNullable<unknown> }} inputs
|
|
51
51
|
* @param {{ locale?: "en" | "en-US" }} options
|
|
52
|
-
* @returns {
|
|
52
|
+
* @returns {LocalizedString}
|
|
53
53
|
*/
|
|
54
54
|
/* @__NO_SIDE_EFFECTS__ */
|
|
55
55
|
export const blue_moon_bottle = (inputs, options = {}) => {
|
|
@@ -100,29 +100,31 @@ test("compiles to jsdoc with missing translation", async () => {
|
|
|
100
100
|
locales: ["en", "en-US", "fr"],
|
|
101
101
|
},
|
|
102
102
|
});
|
|
103
|
-
expect(result.bundle.code).toMatchInlineSnapshot(`
|
|
104
|
-
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* @param {{
|
|
113
|
-
* @
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
103
|
+
expect(result.bundle.code).toMatchInlineSnapshot(`
|
|
104
|
+
"/**
|
|
105
|
+
* This function has been compiled by [Paraglide JS](https://inlang.com/m/gerre34r).
|
|
106
|
+
*
|
|
107
|
+
* - Changing this function will be over-written by the next build.
|
|
108
|
+
*
|
|
109
|
+
* - If you want to change the translations, you can either edit the source files e.g. \`en.json\`, or
|
|
110
|
+
* use another inlang app like [Fink](https://inlang.com/m/tdozzpar) or the [VSCode extension Sherlock](https://inlang.com/m/r7kp499g).
|
|
111
|
+
*
|
|
112
|
+
* @param {{ age: NonNullable<unknown> }} inputs
|
|
113
|
+
* @param {{ locale?: "en" | "en-US" }} options
|
|
114
|
+
* @returns {LocalizedString}
|
|
115
|
+
*/
|
|
116
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
117
|
+
export const blue_moon_bottle = (inputs, options = {}) => {
|
|
118
|
+
if (experimentalMiddlewareLocaleSplitting && isServer === false) {
|
|
119
|
+
return /** @type {any} */ (globalThis).__paraglide_ssr.blue_moon_bottle(inputs)
|
|
120
|
+
}
|
|
121
|
+
const locale = options.locale ?? getLocale()
|
|
122
|
+
trackMessageCall("blue_moon_bottle", locale)
|
|
123
|
+
if (locale === "en") return en.blue_moon_bottle(inputs)
|
|
124
|
+
if (locale === "en-US") return en_us2.blue_moon_bottle(inputs)
|
|
125
|
+
return /** @type {LocalizedString} */ ("blue_moon_bottle")
|
|
126
|
+
};"
|
|
127
|
+
`);
|
|
126
128
|
});
|
|
127
129
|
// https://github.com/opral/inlang-paraglide-js/issues/285
|
|
128
130
|
test("compiles bundles with arbitrary module identifiers", async () => {
|
|
@@ -208,5 +210,5 @@ test("handles message pattern with duplicate variable references", async () => {
|
|
|
208
210
|
// Check that the pattern is compiled correctly
|
|
209
211
|
const enMessage = result.messages?.en;
|
|
210
212
|
expect(enMessage).toBeDefined();
|
|
211
|
-
expect(enMessage?.code).toContain("Last ${i
|
|
213
|
+
expect(enMessage?.code).toContain("Last ${i?.days} days, showing ${i?.days} items");
|
|
212
214
|
});
|
|
@@ -23,7 +23,7 @@ test("compiles a variable reference local variable", () => {
|
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
|
-
expect(code).toEqual("const myVar = i
|
|
26
|
+
expect(code).toEqual("const myVar = i?.name;");
|
|
27
27
|
});
|
|
28
28
|
test("compiles a local variable with an annotation and empty options", () => {
|
|
29
29
|
const code = compileLocalVariable({
|
|
@@ -67,5 +67,5 @@ test("compiles a local variable with an annotation and options", () => {
|
|
|
67
67
|
},
|
|
68
68
|
},
|
|
69
69
|
});
|
|
70
|
-
expect(code).toEqual('const myVar = registry.myFunction("en", "Hello", { option1: "value1", option2: i
|
|
70
|
+
expect(code).toEqual('const myVar = registry.myFunction("en", "Hello", { option1: "value1", option2: i?.varRef });');
|
|
71
71
|
});
|
|
@@ -33,8 +33,8 @@ function compileMessageWithOneVariant(declarations, message, variants) {
|
|
|
33
33
|
compiledLocalVariables.push(compileLocalVariable({ declaration, locale: message.locale }));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
const code = `/** @type {(inputs: ${inputsType(inputs)}) =>
|
|
37
|
-
${compiledLocalVariables.join("\n\t")}return ${compiledPattern.code}
|
|
36
|
+
const code = `/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */ (${hasInputs ? "i" : ""}) => {
|
|
37
|
+
${compiledLocalVariables.join("\n\t")}return /** @type {LocalizedString} */ (${compiledPattern.code})
|
|
38
38
|
};`;
|
|
39
39
|
return { code, node: message };
|
|
40
40
|
}
|
|
@@ -54,7 +54,7 @@ function compileMessageWithMultipleVariants(declarations, message, variants) {
|
|
|
54
54
|
});
|
|
55
55
|
const isCatchAll = variant.matches.every((match) => match.type === "catchall-match");
|
|
56
56
|
if (isCatchAll) {
|
|
57
|
-
compiledVariants.push(`return ${compiledPattern.code}`);
|
|
57
|
+
compiledVariants.push(`return /** @type {LocalizedString} */ (${compiledPattern.code})`);
|
|
58
58
|
hasCatchAll = true;
|
|
59
59
|
}
|
|
60
60
|
const conditions = [];
|
|
@@ -65,7 +65,7 @@ function compileMessageWithMultipleVariants(declarations, message, variants) {
|
|
|
65
65
|
}
|
|
66
66
|
const variableType = declarations.find((decl) => decl.name === match.key)?.type;
|
|
67
67
|
if (variableType === "input-variable") {
|
|
68
|
-
conditions.push(`i
|
|
68
|
+
conditions.push(`i?.${match.key} == ${doubleQuote(match.value)}`);
|
|
69
69
|
}
|
|
70
70
|
else if (variableType === "local-variable") {
|
|
71
71
|
conditions.push(`${match.key} == ${doubleQuote(match.value)}`);
|
|
@@ -73,7 +73,7 @@ function compileMessageWithMultipleVariants(declarations, message, variants) {
|
|
|
73
73
|
}
|
|
74
74
|
if (conditions.length === 0)
|
|
75
75
|
continue;
|
|
76
|
-
compiledVariants.push(`if (${conditions.join(" && ")}) return ${compiledPattern.code};`);
|
|
76
|
+
compiledVariants.push(`if (${conditions.join(" && ")}) return /** @type {LocalizedString} */ (${compiledPattern.code});`);
|
|
77
77
|
}
|
|
78
78
|
const compiledLocalVariables = [];
|
|
79
79
|
for (const declaration of declarations) {
|
|
@@ -81,9 +81,9 @@ function compileMessageWithMultipleVariants(declarations, message, variants) {
|
|
|
81
81
|
compiledLocalVariables.push(compileLocalVariable({ declaration, locale: message.locale }));
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
const code = `/** @type {(inputs: ${inputsType(inputs)}) =>
|
|
84
|
+
const code = `/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */ (${hasInputs ? "i" : ""}) => {${compiledLocalVariables.join("\n\t")}
|
|
85
85
|
${compiledVariants.join("\n\t")}
|
|
86
|
-
${hasCatchAll ? "" : `return "${message.bundleId}";`}
|
|
86
|
+
${hasCatchAll ? "" : `return /** @type {LocalizedString} */ ("${message.bundleId}");`}
|
|
87
87
|
};`;
|
|
88
88
|
return { code, node: message };
|
|
89
89
|
}
|
|
@@ -142,7 +142,7 @@ test("only emits input arguments when inputs exist", async () => {
|
|
|
142
142
|
},
|
|
143
143
|
];
|
|
144
144
|
const compiled = compileMessage(declarations, message, variants);
|
|
145
|
-
expect(compiled.code).toBe("/** @type {(inputs: {}) =>
|
|
145
|
+
expect(compiled.code).toBe("/** @type {(inputs: {}) => LocalizedString} */ () => {\n\treturn /** @type {LocalizedString} */ (`Hello`)\n};");
|
|
146
146
|
});
|
|
147
147
|
// https://github.com/opral/inlang-paraglide-js/issues/379
|
|
148
148
|
test("compiles messages that use plural()", async () => {
|
|
@@ -198,6 +198,90 @@ test("compiles messages that use plural()", async () => {
|
|
|
198
198
|
// INTL.plural will match "other" for undefined
|
|
199
199
|
expect(plural_test({ count: undefined })).toBe("There are many cats.");
|
|
200
200
|
});
|
|
201
|
+
test("compiles messages that use plural() with ordinal type", async () => {
|
|
202
|
+
const declarations = [
|
|
203
|
+
{ type: "input-variable", name: "count" },
|
|
204
|
+
{
|
|
205
|
+
type: "local-variable",
|
|
206
|
+
name: "countOrdinal",
|
|
207
|
+
value: {
|
|
208
|
+
arg: { type: "variable-reference", name: "count" },
|
|
209
|
+
annotation: {
|
|
210
|
+
type: "function-reference",
|
|
211
|
+
name: "plural",
|
|
212
|
+
options: [
|
|
213
|
+
{ name: "type", value: { type: "literal", value: "ordinal" } },
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
type: "expression",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
const message = {
|
|
221
|
+
locale: "en",
|
|
222
|
+
bundleId: "ordinal_test",
|
|
223
|
+
id: "message_id",
|
|
224
|
+
selectors: [{ type: "variable-reference", name: "countOrdinal" }],
|
|
225
|
+
};
|
|
226
|
+
const variants = [
|
|
227
|
+
{
|
|
228
|
+
id: "1",
|
|
229
|
+
messageId: "message_id",
|
|
230
|
+
matches: [{ type: "literal-match", value: "one", key: "countOrdinal" }],
|
|
231
|
+
pattern: [
|
|
232
|
+
{
|
|
233
|
+
type: "expression",
|
|
234
|
+
arg: { type: "variable-reference", name: "count" },
|
|
235
|
+
},
|
|
236
|
+
{ type: "text", value: "st place" },
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "2",
|
|
241
|
+
messageId: "message_id",
|
|
242
|
+
matches: [{ type: "literal-match", value: "two", key: "countOrdinal" }],
|
|
243
|
+
pattern: [
|
|
244
|
+
{
|
|
245
|
+
type: "expression",
|
|
246
|
+
arg: { type: "variable-reference", name: "count" },
|
|
247
|
+
},
|
|
248
|
+
{ type: "text", value: "nd place" },
|
|
249
|
+
],
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "3",
|
|
253
|
+
messageId: "message_id",
|
|
254
|
+
matches: [{ type: "literal-match", value: "few", key: "countOrdinal" }],
|
|
255
|
+
pattern: [
|
|
256
|
+
{
|
|
257
|
+
type: "expression",
|
|
258
|
+
arg: { type: "variable-reference", name: "count" },
|
|
259
|
+
},
|
|
260
|
+
{ type: "text", value: "rd place" },
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: "4",
|
|
265
|
+
messageId: "message_id",
|
|
266
|
+
matches: [{ type: "literal-match", value: "other", key: "countOrdinal" }],
|
|
267
|
+
pattern: [
|
|
268
|
+
{
|
|
269
|
+
type: "expression",
|
|
270
|
+
arg: { type: "variable-reference", name: "count" },
|
|
271
|
+
},
|
|
272
|
+
{ type: "text", value: "th place" },
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
];
|
|
276
|
+
const compiled = compileMessage(declarations, message, variants);
|
|
277
|
+
const { ordinal_test } = await import("data:text/javascript;base64," +
|
|
278
|
+
btoa(createRegistry()) +
|
|
279
|
+
btoa("export const ordinal_test = " + compiled.code.replace("registry.", "")));
|
|
280
|
+
expect(ordinal_test({ count: 1 })).toBe("1st place");
|
|
281
|
+
expect(ordinal_test({ count: 2 })).toBe("2nd place");
|
|
282
|
+
expect(ordinal_test({ count: 3 })).toBe("3rd place");
|
|
283
|
+
expect(ordinal_test({ count: 4 })).toBe("4th place");
|
|
284
|
+
});
|
|
201
285
|
test("compiles messages that use datetime()", async () => {
|
|
202
286
|
const createMessage = async (locale) => {
|
|
203
287
|
const declarations = [
|
|
@@ -306,3 +390,65 @@ test("compiles messages that use datetime a function with options", async () =>
|
|
|
306
390
|
expect(enMessage({ date: "2022-03-31" })).toMatch(/Today is March \d{1,2}\./);
|
|
307
391
|
expect(deMessage({ date: "2022-03-31" })).toMatch(/Today is \d{1,2}\. März\./);
|
|
308
392
|
});
|
|
393
|
+
test("does not throw when input is omitted for a single-variant message", async () => {
|
|
394
|
+
const declarations = [
|
|
395
|
+
{ type: "input-variable", name: "name" },
|
|
396
|
+
];
|
|
397
|
+
const message = {
|
|
398
|
+
locale: "en",
|
|
399
|
+
bundleId: "greeting",
|
|
400
|
+
id: "greeting",
|
|
401
|
+
selectors: [{ type: "variable-reference", name: "name" }],
|
|
402
|
+
};
|
|
403
|
+
const variants = [
|
|
404
|
+
{
|
|
405
|
+
id: "1",
|
|
406
|
+
messageId: "greeting",
|
|
407
|
+
matches: [{ type: "catchall-match", key: "name" }],
|
|
408
|
+
pattern: [
|
|
409
|
+
{ type: "text", value: "Hello " },
|
|
410
|
+
{
|
|
411
|
+
type: "expression",
|
|
412
|
+
arg: { type: "variable-reference", name: "name" },
|
|
413
|
+
},
|
|
414
|
+
{ type: "text", value: "!" },
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
];
|
|
418
|
+
const compiled = compileMessage(declarations, message, variants);
|
|
419
|
+
const { greeting } = await import("data:text/javascript;base64," +
|
|
420
|
+
btoa("export const greeting = " + compiled.code));
|
|
421
|
+
expect(() => greeting()).not.toThrow();
|
|
422
|
+
expect(greeting()).toBe("Hello undefined!");
|
|
423
|
+
});
|
|
424
|
+
test("does not throw when input is omitted for multi-variant message", async () => {
|
|
425
|
+
const declarations = [
|
|
426
|
+
{ type: "input-variable", name: "status" },
|
|
427
|
+
];
|
|
428
|
+
const message = {
|
|
429
|
+
locale: "en",
|
|
430
|
+
bundleId: "status_message",
|
|
431
|
+
id: "status_message",
|
|
432
|
+
selectors: [{ type: "variable-reference", name: "status" }],
|
|
433
|
+
};
|
|
434
|
+
const variants = [
|
|
435
|
+
{
|
|
436
|
+
id: "1",
|
|
437
|
+
messageId: "status_message",
|
|
438
|
+
matches: [{ type: "literal-match", key: "status", value: "ready" }],
|
|
439
|
+
pattern: [{ type: "text", value: "Ready to go" }],
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "2",
|
|
443
|
+
messageId: "status_message",
|
|
444
|
+
matches: [{ type: "catchall-match", key: "status" }],
|
|
445
|
+
pattern: [{ type: "text", value: "Unknown status" }],
|
|
446
|
+
},
|
|
447
|
+
];
|
|
448
|
+
const compiled = compileMessage(declarations, message, variants);
|
|
449
|
+
const { status_message } = await import("data:text/javascript;base64," +
|
|
450
|
+
btoa("export const status_message = " + compiled.code));
|
|
451
|
+
expect(status_message({ status: "ready" })).toBe("Ready to go");
|
|
452
|
+
expect(() => status_message()).not.toThrow();
|
|
453
|
+
expect(status_message()).toBe("Unknown status");
|
|
454
|
+
});
|
|
@@ -11,7 +11,7 @@ import type { Compiled } from "./types.js";
|
|
|
11
11
|
*
|
|
12
12
|
* const { code } = compilePattern({ pattern, declarations: [{ type: "input-variable", name: "age" }] });
|
|
13
13
|
*
|
|
14
|
-
* // code will be: `Your age is ${i
|
|
14
|
+
* // code will be: `Your age is ${i?.age}`
|
|
15
15
|
*/
|
|
16
16
|
export declare const compilePattern: (args: {
|
|
17
17
|
pattern: Pattern;
|
|
@@ -10,7 +10,7 @@ import { escapeForTemplateLiteral } from "../services/codegen/escape.js";
|
|
|
10
10
|
*
|
|
11
11
|
* const { code } = compilePattern({ pattern, declarations: [{ type: "input-variable", name: "age" }] });
|
|
12
12
|
*
|
|
13
|
-
* // code will be: `Your age is ${i
|
|
13
|
+
* // code will be: `Your age is ${i?.age}`
|
|
14
14
|
*/
|
|
15
15
|
export const compilePattern = (args) => {
|
|
16
16
|
let result = "";
|
|
@@ -22,7 +22,7 @@ export const compilePattern = (args) => {
|
|
|
22
22
|
if (part.arg.type === "variable-reference") {
|
|
23
23
|
const declaration = args.declarations.find((decl) => decl.name === part.arg.name);
|
|
24
24
|
if (declaration?.type === "input-variable") {
|
|
25
|
-
result += `\${i
|
|
25
|
+
result += `\${i?.${part.arg.name}}`;
|
|
26
26
|
}
|
|
27
27
|
else if (declaration?.type === "local-variable") {
|
|
28
28
|
result += `\${${part.arg.name}}`;
|
|
@@ -26,7 +26,7 @@ test("should compile a pattern with multiple VariableReference's", () => {
|
|
|
26
26
|
{ type: "input-variable", name: "count" },
|
|
27
27
|
],
|
|
28
28
|
});
|
|
29
|
-
expect(code).toBe("`Hello ${i
|
|
29
|
+
expect(code).toBe("`Hello ${i?.name}! You have ${i?.count} messages.`");
|
|
30
30
|
});
|
|
31
31
|
test("should escape backticks", () => {
|
|
32
32
|
const pattern = [{ type: "text", value: "`Hello world`" }];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile-project.d.ts","sourceRoot":"","sources":["../../src/compiler/compile-project.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAIrE,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"compile-project.d.ts","sourceRoot":"","sources":["../../src/compiler/compile-project.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAIrE,OAAO,EAEN,KAAK,eAAe,EACpB,MAAM,uBAAuB,CAAC;AAW/B;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAU,MAAM;IAC1C,OAAO,EAAE,aAAa,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;CACrE,KAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA4EjC,CAAC;AAEF,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAC9C,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,GACpB,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAY1B"}
|
|
@@ -7,6 +7,7 @@ import { defaultCompilerOptions, } from "./compiler-options.js";
|
|
|
7
7
|
import { createRuntimeFile } from "./runtime/create-runtime.js";
|
|
8
8
|
import { createServerFile } from "./server/create-server-file.js";
|
|
9
9
|
import { createRegistry } from "./registry.js";
|
|
10
|
+
import { emitTsDeclarations } from "./emit-ts-declarations.js";
|
|
10
11
|
const outputStructures = {
|
|
11
12
|
"locale-modules": localeModules,
|
|
12
13
|
"message-modules": messageModules,
|
|
@@ -73,6 +74,10 @@ export const compileProject = async (args) => {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
if (optionsWithDefaults.emitTsDeclarations) {
|
|
78
|
+
const declarations = await emitTsDeclarations(output);
|
|
79
|
+
Object.assign(output, declarations);
|
|
80
|
+
}
|
|
76
81
|
return output;
|
|
77
82
|
};
|
|
78
83
|
export function getFallbackMap(locales, baseLocale) {
|
|
@@ -61,6 +61,18 @@ test("emitPrettierIgnore", async () => {
|
|
|
61
61
|
expect(_true).toHaveProperty(".prettierignore");
|
|
62
62
|
expect(_false).not.toHaveProperty(".prettierignore");
|
|
63
63
|
});
|
|
64
|
+
test("emitTsDeclarations generates declaration files", async () => {
|
|
65
|
+
const output = await compileProject({
|
|
66
|
+
project,
|
|
67
|
+
compilerOptions: {
|
|
68
|
+
emitTsDeclarations: true,
|
|
69
|
+
outputStructure: "locale-modules",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
expect(output).toHaveProperty("messages/_index.d.ts");
|
|
73
|
+
expect(output).toHaveProperty("messages.d.ts");
|
|
74
|
+
expect(output["messages/_index.d.ts"]).toContain("sad_penguin_bundle");
|
|
75
|
+
});
|
|
64
76
|
test("handles message bundles with a : in the id", async () => {
|
|
65
77
|
const project = await loadProjectInMemory({
|
|
66
78
|
blob: await newProject({
|
|
@@ -4,6 +4,7 @@ export declare const defaultCompilerOptions: {
|
|
|
4
4
|
readonly emitGitIgnore: true;
|
|
5
5
|
readonly includeEslintDisableComment: true;
|
|
6
6
|
readonly emitPrettierIgnore: true;
|
|
7
|
+
readonly emitTsDeclarations: false;
|
|
7
8
|
readonly cleanOutdir: true;
|
|
8
9
|
readonly disableAsyncLocalStorage: false;
|
|
9
10
|
readonly experimentalMiddlewareLocaleSplitting: false;
|
|
@@ -164,6 +165,29 @@ export type CompilerOptions = {
|
|
|
164
165
|
* @default true
|
|
165
166
|
*/
|
|
166
167
|
emitPrettierIgnore?: boolean;
|
|
168
|
+
/**
|
|
169
|
+
* Emit `.d.ts` files for the generated output using the TypeScript compiler.
|
|
170
|
+
*
|
|
171
|
+
* Useful when `allowJs: true` cannot be set in your `tsconfig.json`
|
|
172
|
+
* (e.g., due to project constraints or conflicting compiler options).
|
|
173
|
+
*
|
|
174
|
+
* Requires `typescript` to be resolvable in your toolchain.
|
|
175
|
+
*
|
|
176
|
+
* **Note:** Enabling this option reduces compiler speed because TypeScript
|
|
177
|
+
* needs to generate declaration files for all output modules.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* await compile({
|
|
182
|
+
* project: "./project.inlang",
|
|
183
|
+
* outdir: "./src/paraglide",
|
|
184
|
+
* emitTsDeclarations: true,
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @default false
|
|
189
|
+
*/
|
|
190
|
+
emitTsDeclarations?: boolean;
|
|
167
191
|
/**
|
|
168
192
|
* https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#url
|
|
169
193
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compiler-options.d.ts","sourceRoot":"","sources":["../../src/compiler/compiler-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"compiler-options.d.ts","sourceRoot":"","sources":["../../src/compiler/compiler-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;CAeU,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC7B;;;;;;;;;;OAUG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;OAUG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/B;;;;;;;;;;;;;;OAcG;IACH,qCAAqC,CAAC,EAAE,OAAO,CAAC;IAChD;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IACrC;;;;OAIG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;;;;;OASG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiDG;IACH,eAAe,CAAC,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACvD;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;OAIG;IACH,EAAE,CAAC,EAAE,GAAG,CAAC;CACT,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates `.d.ts` files for the compiled Paraglide output using the TypeScript compiler.
|
|
3
|
+
*
|
|
4
|
+
* @param output - The generated compiler output keyed by relative file path.
|
|
5
|
+
* @returns The generated declaration files keyed by relative path.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const declarations = await emitTsDeclarations(output);
|
|
9
|
+
* // Merge them into the compiler output before writing to disk
|
|
10
|
+
*/
|
|
11
|
+
export declare function emitTsDeclarations(output: Record<string, string>): Promise<Record<string, string>>;
|
|
12
|
+
//# sourceMappingURL=emit-ts-declarations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-ts-declarations.d.ts","sourceRoot":"","sources":["../../src/compiler/emit-ts-declarations.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACvC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA8HjC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Generates `.d.ts` files for the compiled Paraglide output using the TypeScript compiler.
|
|
4
|
+
*
|
|
5
|
+
* @param output - The generated compiler output keyed by relative file path.
|
|
6
|
+
* @returns The generated declaration files keyed by relative path.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const declarations = await emitTsDeclarations(output);
|
|
10
|
+
* // Merge them into the compiler output before writing to disk
|
|
11
|
+
*/
|
|
12
|
+
export async function emitTsDeclarations(output) {
|
|
13
|
+
const ts = await import("typescript");
|
|
14
|
+
const jsEntries = Object.entries(output).filter(([fileName]) => fileName.endsWith(".js"));
|
|
15
|
+
if (jsEntries.length === 0) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
const virtualRoot = path.join(process.cwd(), "__paraglide_virtual_output");
|
|
19
|
+
const normalizeFileName = (fileName) => path.normalize(path.isAbsolute(fileName) ? fileName : path.join(virtualRoot, fileName));
|
|
20
|
+
const files = new Map(jsEntries.map(([fileName, content]) => [
|
|
21
|
+
normalizeFileName(fileName),
|
|
22
|
+
content,
|
|
23
|
+
]));
|
|
24
|
+
const virtualDirectories = new Set(Array.from(files.keys()).flatMap((filePath) => {
|
|
25
|
+
const directories = [];
|
|
26
|
+
let current = path.dirname(filePath);
|
|
27
|
+
while (current.startsWith(virtualRoot) && current !== virtualRoot) {
|
|
28
|
+
directories.push(current);
|
|
29
|
+
const parent = path.dirname(current);
|
|
30
|
+
if (parent === current)
|
|
31
|
+
break;
|
|
32
|
+
current = parent;
|
|
33
|
+
}
|
|
34
|
+
return directories;
|
|
35
|
+
}));
|
|
36
|
+
// Ensure the virtual root itself is treated as existing
|
|
37
|
+
virtualDirectories.add(virtualRoot);
|
|
38
|
+
const compilerOptions = {
|
|
39
|
+
allowJs: true,
|
|
40
|
+
checkJs: true,
|
|
41
|
+
declaration: true,
|
|
42
|
+
emitDeclarationOnly: true,
|
|
43
|
+
isolatedDeclarations: true,
|
|
44
|
+
esModuleInterop: true,
|
|
45
|
+
lib: ["ESNext", "DOM"],
|
|
46
|
+
module: ts.ModuleKind.ESNext,
|
|
47
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
48
|
+
noEmitOnError: false,
|
|
49
|
+
outDir: virtualRoot,
|
|
50
|
+
rootDir: virtualRoot,
|
|
51
|
+
skipLibCheck: true,
|
|
52
|
+
target: ts.ScriptTarget.ESNext,
|
|
53
|
+
};
|
|
54
|
+
const defaultHost = ts.createCompilerHost(compilerOptions, true);
|
|
55
|
+
const declarations = {};
|
|
56
|
+
const host = {
|
|
57
|
+
...defaultHost,
|
|
58
|
+
fileExists: (fileName) => {
|
|
59
|
+
const normalized = normalizeFileName(fileName);
|
|
60
|
+
return files.has(normalized) || defaultHost.fileExists(fileName);
|
|
61
|
+
},
|
|
62
|
+
directoryExists: (directoryName) => {
|
|
63
|
+
const normalized = normalizeFileName(directoryName);
|
|
64
|
+
return (virtualDirectories.has(normalized) ||
|
|
65
|
+
defaultHost.directoryExists?.(directoryName) === true);
|
|
66
|
+
},
|
|
67
|
+
getDirectories: (directoryName) => {
|
|
68
|
+
const normalized = normalizeFileName(directoryName);
|
|
69
|
+
const children = Array.from(virtualDirectories).filter((dir) => path.dirname(dir) === normalized);
|
|
70
|
+
return [
|
|
71
|
+
...(defaultHost.getDirectories?.(directoryName) ?? []),
|
|
72
|
+
...children.map((dir) => path.basename(dir)),
|
|
73
|
+
];
|
|
74
|
+
},
|
|
75
|
+
readFile: (fileName) => {
|
|
76
|
+
const normalized = normalizeFileName(fileName);
|
|
77
|
+
return files.get(normalized) ?? defaultHost.readFile(fileName);
|
|
78
|
+
},
|
|
79
|
+
getSourceFile: (fileName, languageVersion, onError, shouldCreateNewFile) => {
|
|
80
|
+
const normalized = normalizeFileName(fileName);
|
|
81
|
+
const sourceText = files.get(normalized);
|
|
82
|
+
if (sourceText !== undefined) {
|
|
83
|
+
return ts.createSourceFile(fileName, sourceText, languageVersion, true);
|
|
84
|
+
}
|
|
85
|
+
return defaultHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewFile);
|
|
86
|
+
},
|
|
87
|
+
writeFile: (fileName, text) => {
|
|
88
|
+
const relativePath = path
|
|
89
|
+
.relative(virtualRoot, fileName)
|
|
90
|
+
.split(path.sep)
|
|
91
|
+
.join(path.posix.sep);
|
|
92
|
+
if (!relativePath.startsWith("..")) {
|
|
93
|
+
declarations[relativePath] = text;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const program = ts.createProgram(Array.from(files.keys()), compilerOptions, host);
|
|
98
|
+
program.emit(undefined, undefined, undefined, true);
|
|
99
|
+
return declarations;
|
|
100
|
+
}
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { defaultCompilerOptions, type CompilerOptions, } from "./compiler-options.js";
|
|
2
|
-
export type { MessageBundleFunction, MessageFunction } from "./types.js";
|
|
2
|
+
export type { LocalizedString, MessageBundleFunction, MessageFunction, } from "./types.js";
|
|
3
3
|
export type { Runtime } from "./runtime/type.js";
|
|
4
4
|
export type { ServerRuntime } from "./server/type.js";
|
|
5
5
|
export { compile } from "./compile.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compiler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,KAAK,eAAe,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/compiler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,sBAAsB,EACtB,KAAK,eAAe,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACX,eAAe,EACf,qBAAqB,EACrB,eAAe,GACf,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -3,7 +3,7 @@ export function jsDocBundleFunctionTypes(args) {
|
|
|
3
3
|
return `
|
|
4
4
|
* @param {${inputsType(args.inputs)}} inputs
|
|
5
5
|
* @param {{ locale?: ${localesUnion} }} options
|
|
6
|
-
* @returns {
|
|
6
|
+
* @returns {LocalizedString}`;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Returns the types for the input variables.
|
|
@@ -24,3 +24,12 @@ test("jsDocBundleFunctionTypes correctly handles messages with duplicate inputs"
|
|
|
24
24
|
// It should not contain duplicated parameters
|
|
25
25
|
expect(result).not.toContain("@param {{ days: NonNullable<unknown>, days: NonNullable<unknown> }} inputs");
|
|
26
26
|
});
|
|
27
|
+
test("jsDocBundleFunctionTypes returns LocalizedString type", () => {
|
|
28
|
+
const inputs = [];
|
|
29
|
+
const locales = ["en", "de"];
|
|
30
|
+
const result = jsDocBundleFunctionTypes({ inputs, locales });
|
|
31
|
+
// The JSDoc should specify LocalizedString as the return type
|
|
32
|
+
expect(result).toContain("@returns {LocalizedString}");
|
|
33
|
+
// It should not return plain string
|
|
34
|
+
expect(result).not.toContain("@returns {string}");
|
|
35
|
+
});
|
|
@@ -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,
|
|
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,CA4DxB"}
|
|
@@ -6,6 +6,7 @@ export function messageReferenceExpression(locale, bundleId) {
|
|
|
6
6
|
export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
7
7
|
const indexFile = [
|
|
8
8
|
`import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from "../runtime.js"`,
|
|
9
|
+
`/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */`,
|
|
9
10
|
settings.locales
|
|
10
11
|
.map((locale) => `import * as ${toSafeModuleId(locale)} from "./${locale}.js"`)
|
|
11
12
|
.join("\n"),
|
|
@@ -31,7 +32,7 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
31
32
|
}
|
|
32
33
|
else {
|
|
33
34
|
// no fallback exists, render the bundleId
|
|
34
|
-
file += `\n/** @type {(inputs: ${inputsType(inputs)}) =>
|
|
35
|
+
file += `\n/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */\nexport const ${bundleModuleId} = () => /** @type {LocalizedString} */ ('${bundleId}')`;
|
|
35
36
|
}
|
|
36
37
|
continue;
|
|
37
38
|
}
|
|
@@ -41,6 +42,12 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
41
42
|
if (file.includes("registry.")) {
|
|
42
43
|
file = `import * as registry from "../registry.js"\n` + file;
|
|
43
44
|
}
|
|
45
|
+
// add LocalizedString typedef reference if used
|
|
46
|
+
if (file.includes("LocalizedString")) {
|
|
47
|
+
file =
|
|
48
|
+
`/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */\n` +
|
|
49
|
+
file;
|
|
50
|
+
}
|
|
44
51
|
output[filename] = file;
|
|
45
52
|
}
|
|
46
53
|
return output;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-modules.d.ts","sourceRoot":"","sources":["../../../src/compiler/output-structure/message-modules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAKvE,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,
|
|
1
|
+
{"version":3,"file":"message-modules.d.ts","sourceRoot":"","sources":["../../../src/compiler/output-structure/message-modules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAKvE,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,CAkHxB"}
|
|
@@ -56,10 +56,10 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
56
56
|
}
|
|
57
57
|
if (fallbackLocale) {
|
|
58
58
|
const safeFallbackLocale = toSafeModuleId(fallbackLocale);
|
|
59
|
-
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) =>
|
|
59
|
+
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */\nconst ${safeLocale}_${safeModuleId} = ${safeFallbackLocale}_${safeModuleId};`);
|
|
60
60
|
}
|
|
61
61
|
else {
|
|
62
|
-
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) =>
|
|
62
|
+
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */\nconst ${safeLocale}_${safeModuleId} = () => /** @type {LocalizedString} */ ('${escapeForSingleQuoteString(bundleId)}')`);
|
|
63
63
|
}
|
|
64
64
|
emittingFallbacks.delete(locale);
|
|
65
65
|
emittedFallbacks.add(locale);
|
|
@@ -68,9 +68,10 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
68
68
|
emitFallback(locale);
|
|
69
69
|
}
|
|
70
70
|
output[filename] = messages.join("\n\n") + "\n\n" + output[filename];
|
|
71
|
-
// add the imports
|
|
71
|
+
// add the imports and type reference (LocalizedString is defined in runtime.js)
|
|
72
72
|
output[filename] =
|
|
73
|
-
`import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';\n
|
|
73
|
+
`import { getLocale, trackMessageCall, experimentalMiddlewareLocaleSplitting, isServer } from '../runtime.js';\n` +
|
|
74
|
+
`/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */\n\n` +
|
|
74
75
|
output[filename];
|
|
75
76
|
// Add the registry import to the message file
|
|
76
77
|
// if registry is used
|
|
@@ -80,8 +81,10 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
// all messages index file
|
|
83
|
-
output["messages/_index.js"] =
|
|
84
|
-
.
|
|
85
|
-
|
|
84
|
+
output["messages/_index.js"] =
|
|
85
|
+
`/** @typedef {import('../runtime.js').LocalizedString} LocalizedString */\n` +
|
|
86
|
+
Array.from(moduleFilenames)
|
|
87
|
+
.map((filename) => `export * from './${filename}'`)
|
|
88
|
+
.join("\n");
|
|
86
89
|
return output;
|
|
87
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-runtime.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/create-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE;QAChB,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC;QACvD,YAAY,EAAE,WAAW,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3D,YAAY,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;QAC9C,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;QAC7C,qCAAqC,EAAE,eAAe,CAAC,uCAAuC,CAAC,CAAC;QAChG,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;QACtC,eAAe,EAAE,eAAe,CAAC,iBAAiB,CAAC,CAAC;QACpD,wBAAwB,EAAE,WAAW,CACpC,eAAe,CAAC,0BAA0B,CAAC,CAC3C,CAAC;KACF,CAAC;CACF,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"create-runtime.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/create-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE;QAChB,QAAQ,EAAE,WAAW,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC;QACvD,YAAY,EAAE,WAAW,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC;QAC3D,YAAY,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;QAC9C,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;QAC7C,qCAAqC,EAAE,eAAe,CAAC,uCAAuC,CAAC,CAAC;QAChG,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;QACtC,eAAe,EAAE,eAAe,CAAC,iBAAiB,CAAC,CAAC;QACpD,wBAAwB,EAAE,WAAW,CACpC,eAAe,CAAC,0BAA0B,CAAC,CAC3C,CAAC;KACF,CAAC;CACF,GAAG,MAAM,CAsLT"}
|
|
@@ -95,6 +95,48 @@ ${injectCode("./strategy.js")}
|
|
|
95
95
|
* @typedef {(typeof locales)[number]} Locale
|
|
96
96
|
*/
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* A branded type representing a localized string.
|
|
100
|
+
*
|
|
101
|
+
* Message functions return this type instead of \`string\`, enabling TypeScript
|
|
102
|
+
* to distinguish translated strings from regular strings at compile time.
|
|
103
|
+
* This allows you to enforce that only properly localized content is used
|
|
104
|
+
* in your UI components.
|
|
105
|
+
*
|
|
106
|
+
* Since \`LocalizedString\` is a branded subtype of \`string\`, it remains fully
|
|
107
|
+
* backward compatible—you can pass it anywhere a \`string\` is expected.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // Enforce localized strings in your components
|
|
111
|
+
* function PageTitle(props: { title: LocalizedString }) {
|
|
112
|
+
* return <h1>{props.title}</h1>
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* // ✅ Correct: using a message function
|
|
116
|
+
* <PageTitle title={m.welcome_title()} />
|
|
117
|
+
*
|
|
118
|
+
* // ❌ Type error: raw strings are not LocalizedString
|
|
119
|
+
* <PageTitle title="Welcome" />
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // LocalizedString is assignable to string (backward compatible)
|
|
123
|
+
* const localized: LocalizedString = m.greeting()
|
|
124
|
+
* const str: string = localized // ✅ works fine
|
|
125
|
+
*
|
|
126
|
+
* // But string is not assignable to LocalizedString
|
|
127
|
+
* const raw: LocalizedString = "Hello" // ❌ Type error
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* // Catches accidental string concatenation
|
|
131
|
+
* function showMessage(msg: LocalizedString) { ... }
|
|
132
|
+
*
|
|
133
|
+
* showMessage(m.hello()) // ✅
|
|
134
|
+
* showMessage("Hello " + userName) // ❌ Type error
|
|
135
|
+
* showMessage(m.hello_user({ name: userName })) // ✅ use params instead
|
|
136
|
+
*
|
|
137
|
+
* @typedef {string & { readonly __brand: 'LocalizedString' }} LocalizedString
|
|
138
|
+
*/
|
|
139
|
+
|
|
98
140
|
`;
|
|
99
141
|
return code;
|
|
100
142
|
}
|
package/dist/compiler/types.d.ts
CHANGED
|
@@ -4,13 +4,29 @@ export type Compiled<Node> = {
|
|
|
4
4
|
/** The code generated to implement the AST node */
|
|
5
5
|
code: string;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* A branded type representing a localized string.
|
|
9
|
+
* Provides compile-time safety to distinguish translated from untranslated strings.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { m } from './paraglide/messages.js'
|
|
14
|
+
* import type { LocalizedString } from '@inlang/paraglide-js'
|
|
15
|
+
*
|
|
16
|
+
* const greeting: LocalizedString = m.hello() // ✓ Type-safe
|
|
17
|
+
* const raw: LocalizedString = "Hello" // ✗ Type error
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export type LocalizedString = string & {
|
|
21
|
+
readonly __brand: "LocalizedString";
|
|
22
|
+
};
|
|
7
23
|
/**
|
|
8
24
|
* A message function is a message for a specific locale.
|
|
9
25
|
*
|
|
10
26
|
* @example
|
|
11
27
|
* m.hello({ name: 'world' })
|
|
12
28
|
*/
|
|
13
|
-
export type MessageFunction = (inputs?: Record<string, never>) =>
|
|
29
|
+
export type MessageFunction = (inputs?: Record<string, never>) => LocalizedString;
|
|
14
30
|
/**
|
|
15
31
|
* A message bundle function that selects the message to be returned.
|
|
16
32
|
*
|
|
@@ -22,5 +38,5 @@ export type MessageFunction = (inputs?: Record<string, never>) => string;
|
|
|
22
38
|
*/
|
|
23
39
|
export type MessageBundleFunction<T extends string> = (params: Record<string, never>, options: {
|
|
24
40
|
locale: T;
|
|
25
|
-
}) =>
|
|
41
|
+
}) => LocalizedString;
|
|
26
42
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/compiler/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,CAAC,IAAI,IAAI;IAC5B,4BAA4B;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/compiler/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,CAAC,IAAI,IAAI;IAC5B,4BAA4B;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE/E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,KAC1B,eAAe,CAAC;AAErB;;;;;;;;GAQG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,MAAM,IAAI,CACrD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC7B,OAAO,EAAE;IAAE,MAAM,EAAE,CAAC,CAAA;CAAE,KAClB,eAAe,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inlang/paraglide-js",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.7.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"json5": "2.2.3",
|
|
32
32
|
"unplugin": "^2.1.2",
|
|
33
33
|
"urlpattern-polyfill": "^10.0.0",
|
|
34
|
-
"@inlang/
|
|
35
|
-
"@inlang/
|
|
34
|
+
"@inlang/sdk": "2.4.9",
|
|
35
|
+
"@inlang/recommend-sherlock": "0.2.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@rollup/plugin-virtual": "3.0.2",
|