@inlang/paraglide-js 2.3.2 → 2.5.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/steps/update-ts-config.d.ts +13 -0
- package/dist/cli/steps/update-ts-config.d.ts.map +1 -1
- package/dist/cli/steps/update-ts-config.js +131 -16
- package/dist/cli/steps/update-ts-config.test.d.ts +2 -0
- package/dist/cli/steps/update-ts-config.test.d.ts.map +1 -0
- package/dist/cli/steps/update-ts-config.test.js +59 -0
- package/dist/compiler/compile-project.js +1 -1
- package/dist/compiler/compile-project.test.js +13 -13
- package/dist/compiler/compile.test.js +20 -7
- package/dist/compiler/output-file.d.ts +6 -0
- package/dist/compiler/output-file.d.ts.map +1 -0
- package/dist/compiler/output-file.js +1 -0
- package/dist/compiler/output-structure/message-modules.d.ts.map +1 -1
- package/dist/compiler/output-structure/message-modules.js +26 -4
- package/dist/compiler/output-structure/message-modules.test.js +42 -0
- package/dist/compiler/runtime/assert-is-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/assert-is-locale.js +7 -3
- package/dist/compiler/runtime/assert-is-locale.test.js +33 -2
- package/dist/compiler/runtime/create-runtime.d.ts.map +1 -1
- package/dist/compiler/runtime/create-runtime.js +2 -0
- package/dist/compiler/runtime/is-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/is-locale.js +5 -1
- package/dist/compiler/runtime/is-locale.test.d.ts +2 -0
- package/dist/compiler/runtime/is-locale.test.d.ts.map +1 -0
- package/dist/compiler/runtime/is-locale.test.js +31 -0
- package/dist/compiler/runtime/set-locale.d.ts +8 -4
- package/dist/compiler/runtime/set-locale.d.ts.map +1 -1
- package/dist/compiler/runtime/set-locale.js +19 -18
- package/dist/compiler/runtime/set-locale.test.js +25 -0
- package/dist/compiler/runtime/should-redirect.d.ts +80 -0
- package/dist/compiler/runtime/should-redirect.d.ts.map +1 -0
- package/dist/compiler/runtime/should-redirect.js +119 -0
- package/dist/compiler/runtime/should-redirect.test.d.ts +2 -0
- package/dist/compiler/runtime/should-redirect.test.d.ts.map +1 -0
- package/dist/compiler/runtime/should-redirect.test.js +119 -0
- package/dist/compiler/runtime/type.d.ts +1 -0
- package/dist/compiler/runtime/type.d.ts.map +1 -1
- package/dist/compiler/server/middleware.d.ts.map +1 -1
- package/dist/compiler/server/middleware.js +18 -31
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/file-handling/write-output.test.js +7 -10
- package/package.json +9 -11
|
@@ -11,4 +11,17 @@ export declare const maybeUpdateTsConfigAllowJs: CliStep<{
|
|
|
11
11
|
fs: typeof import("node:fs/promises");
|
|
12
12
|
logger: Logger;
|
|
13
13
|
}, unknown>;
|
|
14
|
+
/**
|
|
15
|
+
* Recursively checks whether allowJs is enabled in the provided tsconfig or any
|
|
16
|
+
* referenced configuration files.
|
|
17
|
+
*
|
|
18
|
+
* @param tsconfigPath The path to the tsconfig to inspect.
|
|
19
|
+
* @param fs The file system used to read the configs.
|
|
20
|
+
* @param visited A set of already inspected files to avoid circular lookups.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* await hasAllowJsEnabled("./tsconfig.json", fs);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare const hasAllowJsEnabled: (tsconfigPath: string, fs: typeof import("node:fs/promises"), visited?: Set<string>) => Promise<boolean>;
|
|
14
27
|
//# sourceMappingURL=update-ts-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-ts-config.d.ts","sourceRoot":"","sources":["../../../src/cli/steps/update-ts-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"update-ts-config.d.ts","sourceRoot":"","sources":["../../../src/cli/steps/update-ts-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAS7D,eAAO,MAAM,mBAAmB,EAAE,OAAO,CACxC;IAAE,EAAE,EAAE,cAAc,kBAAkB,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzD,OAAO,CAKP,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,OAAO,CAC/C;IAAE,EAAE,EAAE,cAAc,kBAAkB,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzD,OAAO,CA6CP,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC7B,cAAc,MAAM,EACpB,IAAI,cAAc,kBAAkB,CAAC,EACrC,UAAS,GAAG,CAAC,MAAM,CAAa,KAC9B,OAAO,CAAC,OAAO,CA4CjB,CAAC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { prompt } from "../utils.js";
|
|
2
2
|
import JSON5 from "json5";
|
|
3
3
|
import { pathExists } from "../../services/file-handling/exists.js";
|
|
4
|
-
|
|
4
|
+
import nodePath from "node:path";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
5
7
|
export const maybeUpdateTsConfig = async (ctx) => {
|
|
6
8
|
const ctx1 = await maybeUpdateTsConfigAllowJs(ctx);
|
|
7
9
|
return ctx1;
|
|
@@ -14,10 +16,7 @@ export const maybeUpdateTsConfigAllowJs = async (ctx) => {
|
|
|
14
16
|
if ((await pathExists("./tsconfig.json", ctx.fs)) === false) {
|
|
15
17
|
return ctx;
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
// tsconfig allows comments ... FML
|
|
19
|
-
let tsconfig = JSON5.parse(file);
|
|
20
|
-
if (tsconfig.compilerOptions?.allowJs === true) {
|
|
19
|
+
if (await hasAllowJsEnabled("./tsconfig.json", ctx.fs)) {
|
|
21
20
|
// all clear, allowJs is already set to true
|
|
22
21
|
return ctx;
|
|
23
22
|
}
|
|
@@ -38,17 +37,7 @@ export const maybeUpdateTsConfigAllowJs = async (ctx) => {
|
|
|
38
37
|
ctx.logger.warn("Continuing without adjusting the tsconfig.json. This may lead to type errors.");
|
|
39
38
|
return ctx;
|
|
40
39
|
}
|
|
41
|
-
|
|
42
|
-
// just trust that it's correct.
|
|
43
|
-
if (tsconfig.extends) {
|
|
44
|
-
isValid = true;
|
|
45
|
-
return ctx;
|
|
46
|
-
}
|
|
47
|
-
const file = await ctx.fs.readFile("./tsconfig.json", {
|
|
48
|
-
encoding: "utf-8",
|
|
49
|
-
});
|
|
50
|
-
tsconfig = JSON5.parse(file);
|
|
51
|
-
if (tsconfig?.compilerOptions?.allowJs === true) {
|
|
40
|
+
if (await hasAllowJsEnabled("./tsconfig.json", ctx.fs)) {
|
|
52
41
|
isValid = true;
|
|
53
42
|
return ctx;
|
|
54
43
|
}
|
|
@@ -58,6 +47,132 @@ export const maybeUpdateTsConfigAllowJs = async (ctx) => {
|
|
|
58
47
|
}
|
|
59
48
|
return ctx;
|
|
60
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* Recursively checks whether allowJs is enabled in the provided tsconfig or any
|
|
52
|
+
* referenced configuration files.
|
|
53
|
+
*
|
|
54
|
+
* @param tsconfigPath The path to the tsconfig to inspect.
|
|
55
|
+
* @param fs The file system used to read the configs.
|
|
56
|
+
* @param visited A set of already inspected files to avoid circular lookups.
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* await hasAllowJsEnabled("./tsconfig.json", fs);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export const hasAllowJsEnabled = async (tsconfigPath, fs, visited = new Set()) => {
|
|
63
|
+
const normalizedPath = normalizeConfigPath(tsconfigPath);
|
|
64
|
+
if (visited.has(normalizedPath)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
visited.add(normalizedPath);
|
|
68
|
+
const file = await fs.readFile(normalizedPath, { encoding: "utf-8" });
|
|
69
|
+
const tsconfig = JSON5.parse(file);
|
|
70
|
+
if (tsconfig?.compilerOptions?.allowJs === true) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const baseDir = nodePath.dirname(normalizedPath);
|
|
74
|
+
const extendCandidates = Array.isArray(tsconfig?.extends)
|
|
75
|
+
? tsconfig.extends
|
|
76
|
+
: tsconfig?.extends
|
|
77
|
+
? [tsconfig.extends]
|
|
78
|
+
: [];
|
|
79
|
+
for (const candidate of extendCandidates) {
|
|
80
|
+
if (typeof candidate !== "string")
|
|
81
|
+
continue;
|
|
82
|
+
const resolved = await resolveExtendedConfig(candidate, baseDir, fs);
|
|
83
|
+
if (resolved && (await hasAllowJsEnabled(resolved, fs, visited))) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(tsconfig?.references)) {
|
|
88
|
+
for (const reference of tsconfig.references) {
|
|
89
|
+
const referencePath = reference?.path;
|
|
90
|
+
if (typeof referencePath !== "string")
|
|
91
|
+
continue;
|
|
92
|
+
const resolved = await resolveReferenceConfig(referencePath, baseDir, fs);
|
|
93
|
+
if (resolved && (await hasAllowJsEnabled(resolved, fs, visited))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Normalizes a tsconfig path to an absolute path.
|
|
102
|
+
*/
|
|
103
|
+
const normalizeConfigPath = (configPath) => {
|
|
104
|
+
return nodePath.isAbsolute(configPath)
|
|
105
|
+
? configPath
|
|
106
|
+
: nodePath.resolve(process.cwd(), configPath);
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Resolves the extended tsconfig path relative to the base config.
|
|
110
|
+
*/
|
|
111
|
+
const resolveExtendedConfig = async (extendsSpecifier, baseDir, fs) => {
|
|
112
|
+
const candidates = new Set();
|
|
113
|
+
const resolvedBase = nodePath.isAbsolute(extendsSpecifier)
|
|
114
|
+
? extendsSpecifier
|
|
115
|
+
: nodePath.resolve(baseDir, extendsSpecifier);
|
|
116
|
+
candidates.add(resolvedBase);
|
|
117
|
+
if (nodePath.extname(resolvedBase) === "") {
|
|
118
|
+
candidates.add(`${resolvedBase}.json`);
|
|
119
|
+
candidates.add(nodePath.join(resolvedBase, "tsconfig.json"));
|
|
120
|
+
}
|
|
121
|
+
for (const candidate of candidates) {
|
|
122
|
+
if (await pathExists(candidate, fs)) {
|
|
123
|
+
return candidate;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
return require.resolve(extendsSpecifier, { paths: [baseDir] });
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
if (extendsSpecifier.endsWith(".json") === false) {
|
|
131
|
+
try {
|
|
132
|
+
return require.resolve(`${extendsSpecifier}.json`, {
|
|
133
|
+
paths: [baseDir],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Resolves the tsconfig referenced through the `references` property.
|
|
145
|
+
*/
|
|
146
|
+
const resolveReferenceConfig = async (referenceSpecifier, baseDir, fs) => {
|
|
147
|
+
const candidates = new Set();
|
|
148
|
+
const resolvedBase = nodePath.isAbsolute(referenceSpecifier)
|
|
149
|
+
? referenceSpecifier
|
|
150
|
+
: nodePath.resolve(baseDir, referenceSpecifier);
|
|
151
|
+
candidates.add(resolvedBase);
|
|
152
|
+
if (nodePath.extname(resolvedBase) === "") {
|
|
153
|
+
candidates.add(`${resolvedBase}.json`);
|
|
154
|
+
candidates.add(nodePath.join(resolvedBase, "tsconfig.json"));
|
|
155
|
+
}
|
|
156
|
+
for (const candidate of candidates) {
|
|
157
|
+
if (await pathExists(candidate, fs)) {
|
|
158
|
+
try {
|
|
159
|
+
const stats = await fs.stat(candidate);
|
|
160
|
+
if (stats.isDirectory()) {
|
|
161
|
+
const directoryConfig = nodePath.join(candidate, "tsconfig.json");
|
|
162
|
+
if (await pathExists(directoryConfig, fs)) {
|
|
163
|
+
return directoryConfig;
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// ignore, we'll continue checking other candidates
|
|
170
|
+
}
|
|
171
|
+
return candidate;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
};
|
|
61
176
|
// /**
|
|
62
177
|
// * Ensures that the moduleResolution compiler option is set to "bundler" or similar in the tsconfig.json.
|
|
63
178
|
// *
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-ts-config.test.d.ts","sourceRoot":"","sources":["../../../src/cli/steps/update-ts-config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest";
|
|
2
|
+
import { memfs } from "memfs";
|
|
3
|
+
import { maybeUpdateTsConfigAllowJs } from "./update-ts-config.js";
|
|
4
|
+
import { Logger } from "../../services/logger/index.js";
|
|
5
|
+
const setCwd = (cwd) => {
|
|
6
|
+
const original = process.cwd;
|
|
7
|
+
process.cwd = (() => cwd);
|
|
8
|
+
return () => {
|
|
9
|
+
process.cwd = original;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
// Regression coverage for https://github.com/opral/inlang-paraglide-js/issues/560
|
|
13
|
+
test("skips prompting when allowJs is set in a referenced tsconfig", async () => {
|
|
14
|
+
const fs = memfs({
|
|
15
|
+
"/tsconfig.json": JSON.stringify({
|
|
16
|
+
references: [{ path: "./tsconfig.app.json" }],
|
|
17
|
+
}),
|
|
18
|
+
"/tsconfig.app.json": JSON.stringify({
|
|
19
|
+
compilerOptions: { allowJs: true },
|
|
20
|
+
}),
|
|
21
|
+
}).fs;
|
|
22
|
+
const restoreCwd = setCwd("/");
|
|
23
|
+
const logger = new Logger({ silent: true, prefix: false });
|
|
24
|
+
const infoSpy = vi.spyOn(logger, "info");
|
|
25
|
+
try {
|
|
26
|
+
await maybeUpdateTsConfigAllowJs({
|
|
27
|
+
fs: fs.promises,
|
|
28
|
+
logger,
|
|
29
|
+
});
|
|
30
|
+
expect(infoSpy).not.toHaveBeenCalled();
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
restoreCwd();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
// Regression coverage for https://github.com/opral/inlang-paraglide-js/issues/560
|
|
37
|
+
test("skips prompting when allowJs is provided via extends", async () => {
|
|
38
|
+
const fs = memfs({
|
|
39
|
+
"/tsconfig.json": JSON.stringify({
|
|
40
|
+
extends: "./tsconfig.base.json",
|
|
41
|
+
}),
|
|
42
|
+
"/tsconfig.base.json": JSON.stringify({
|
|
43
|
+
compilerOptions: { allowJs: true },
|
|
44
|
+
}),
|
|
45
|
+
}).fs;
|
|
46
|
+
const restoreCwd = setCwd("/");
|
|
47
|
+
const logger = new Logger({ silent: true, prefix: false });
|
|
48
|
+
const infoSpy = vi.spyOn(logger, "info");
|
|
49
|
+
try {
|
|
50
|
+
await maybeUpdateTsConfigAllowJs({
|
|
51
|
+
fs: fs.promises,
|
|
52
|
+
logger,
|
|
53
|
+
});
|
|
54
|
+
expect(infoSpy).not.toHaveBeenCalled();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
restoreCwd();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -69,7 +69,7 @@ export const compileProject = async (args) => {
|
|
|
69
69
|
for (const [filename, content] of Object.entries(output)) {
|
|
70
70
|
if (optionsWithDefaults.includeEslintDisableComment) {
|
|
71
71
|
if (filename.endsWith(".js")) {
|
|
72
|
-
output[filename] =
|
|
72
|
+
output[filename] = `/* eslint-disable */\n${content}`;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -70,7 +70,7 @@ test("handles message bundles with a : in the id", async () => {
|
|
|
70
70
|
},
|
|
71
71
|
}),
|
|
72
72
|
});
|
|
73
|
-
await insertBundleNested(project
|
|
73
|
+
await insertBundleNested(project, createBundleNested({
|
|
74
74
|
id: "hello:world",
|
|
75
75
|
messages: [
|
|
76
76
|
{
|
|
@@ -97,7 +97,7 @@ test("can emit message bundles with more than 255 characters", async () => {
|
|
|
97
97
|
},
|
|
98
98
|
}),
|
|
99
99
|
});
|
|
100
|
-
await insertBundleNested(project
|
|
100
|
+
await insertBundleNested(project, createBundleNested({
|
|
101
101
|
// 300 characters long id
|
|
102
102
|
id: "a".repeat(300),
|
|
103
103
|
messages: [
|
|
@@ -199,7 +199,7 @@ describe.each([
|
|
|
199
199
|
settings: { locales: ["en", "de"], baseLocale: "en" },
|
|
200
200
|
}),
|
|
201
201
|
});
|
|
202
|
-
await insertBundleNested(project
|
|
202
|
+
await insertBundleNested(project, createBundleNested({
|
|
203
203
|
id: "plural_test",
|
|
204
204
|
declarations: [
|
|
205
205
|
{ type: "input-variable", name: "count" },
|
|
@@ -333,7 +333,7 @@ describe.each([
|
|
|
333
333
|
settings: { locales: ["en", "de", "en-US"], baseLocale: "en" },
|
|
334
334
|
}),
|
|
335
335
|
});
|
|
336
|
-
await insertBundleNested(project
|
|
336
|
+
await insertBundleNested(project, createBundleNested({
|
|
337
337
|
id: "missingInGerman",
|
|
338
338
|
messages: [
|
|
339
339
|
{
|
|
@@ -363,7 +363,7 @@ describe.each([
|
|
|
363
363
|
}),
|
|
364
364
|
});
|
|
365
365
|
// Add test messages
|
|
366
|
-
await insertBundleNested(project
|
|
366
|
+
await insertBundleNested(project, createBundleNested({
|
|
367
367
|
id: "greeting",
|
|
368
368
|
messages: [
|
|
369
369
|
{
|
|
@@ -380,7 +380,7 @@ describe.each([
|
|
|
380
380
|
},
|
|
381
381
|
],
|
|
382
382
|
}));
|
|
383
|
-
await insertBundleNested(project
|
|
383
|
+
await insertBundleNested(project, createBundleNested({
|
|
384
384
|
id: "farewell",
|
|
385
385
|
messages: [
|
|
386
386
|
{
|
|
@@ -473,7 +473,7 @@ describe.each([
|
|
|
473
473
|
settings: { locales: ["en", "de"], baseLocale: "en" },
|
|
474
474
|
}),
|
|
475
475
|
});
|
|
476
|
-
await insertBundleNested(project
|
|
476
|
+
await insertBundleNested(project, createBundleNested({
|
|
477
477
|
id: "$502.23-hello_world",
|
|
478
478
|
messages: [
|
|
479
479
|
{
|
|
@@ -499,7 +499,7 @@ describe.each([
|
|
|
499
499
|
settings: { locales: ["en", "en-US"], baseLocale: "en" },
|
|
500
500
|
}),
|
|
501
501
|
});
|
|
502
|
-
await insertBundleNested(project
|
|
502
|
+
await insertBundleNested(project, createBundleNested({
|
|
503
503
|
id: "exists_in_both",
|
|
504
504
|
messages: [
|
|
505
505
|
{
|
|
@@ -523,7 +523,7 @@ describe.each([
|
|
|
523
523
|
},
|
|
524
524
|
],
|
|
525
525
|
}));
|
|
526
|
-
await insertBundleNested(project
|
|
526
|
+
await insertBundleNested(project, createBundleNested({
|
|
527
527
|
id: "missing_in_en_US",
|
|
528
528
|
messages: [
|
|
529
529
|
{
|
|
@@ -552,7 +552,7 @@ describe.each([
|
|
|
552
552
|
settings: { locales: ["en"], baseLocale: "en" },
|
|
553
553
|
}),
|
|
554
554
|
});
|
|
555
|
-
await insertBundleNested(project
|
|
555
|
+
await insertBundleNested(project, createBundleNested({
|
|
556
556
|
id: "happy🍌",
|
|
557
557
|
messages: [
|
|
558
558
|
{
|
|
@@ -578,7 +578,7 @@ describe.each([
|
|
|
578
578
|
}),
|
|
579
579
|
});
|
|
580
580
|
// Create two bundles with the same name but different case
|
|
581
|
-
await insertBundleNested(project
|
|
581
|
+
await insertBundleNested(project, createBundleNested({
|
|
582
582
|
id: "Helloworld",
|
|
583
583
|
messages: [
|
|
584
584
|
{
|
|
@@ -593,7 +593,7 @@ describe.each([
|
|
|
593
593
|
},
|
|
594
594
|
],
|
|
595
595
|
}));
|
|
596
|
-
await insertBundleNested(project
|
|
596
|
+
await insertBundleNested(project, createBundleNested({
|
|
597
597
|
id: "helloworld",
|
|
598
598
|
messages: [
|
|
599
599
|
{
|
|
@@ -883,7 +883,7 @@ const mockBundles = [
|
|
|
883
883
|
},
|
|
884
884
|
];
|
|
885
885
|
for (const bundle of mockBundles) {
|
|
886
|
-
await insertBundleNested(project
|
|
886
|
+
await insertBundleNested(project, bundle);
|
|
887
887
|
}
|
|
888
888
|
function createBundleNested(args) {
|
|
889
889
|
return {
|
|
@@ -237,7 +237,7 @@ test("includes eslint-disable comment", async () => {
|
|
|
237
237
|
fs: fs,
|
|
238
238
|
});
|
|
239
239
|
const messages = await fs.promises.readFile("/output/messages.js", "utf8");
|
|
240
|
-
expect(messages).toContain("
|
|
240
|
+
expect(messages).toContain("/* eslint-disable */");
|
|
241
241
|
await compile({
|
|
242
242
|
project: "/project.inlang",
|
|
243
243
|
outdir: "/output",
|
|
@@ -245,7 +245,7 @@ test("includes eslint-disable comment", async () => {
|
|
|
245
245
|
fs: fs,
|
|
246
246
|
});
|
|
247
247
|
const messagesWithoutComment = await fs.promises.readFile("/output/messages.js", "utf8");
|
|
248
|
-
expect(messagesWithoutComment).not.toContain("
|
|
248
|
+
expect(messagesWithoutComment).not.toContain("/* eslint-disable */");
|
|
249
249
|
});
|
|
250
250
|
test("default compiler options should include cookied, variable and baseLocale to ensure easy try out of paraglide js, working both in server and browser environemnts", () => {
|
|
251
251
|
// someone trying out paraglide js should be able to call `getLocale()` and `setLocale()`
|
|
@@ -298,16 +298,29 @@ test("emits warnings for modules that couldn't be imported via http", async () =
|
|
|
298
298
|
const mock = vi.fn();
|
|
299
299
|
consola.mockTypes(() => mock);
|
|
300
300
|
const fs = memfs().fs;
|
|
301
|
+
const fetchMock = vi.fn().mockRejectedValue(new TypeError("network error"));
|
|
302
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
303
|
+
const errorsSpy = vi.spyOn(project.errors, "get").mockResolvedValue([
|
|
304
|
+
{
|
|
305
|
+
message: "Failed to import module https://example.com/non-existent-paraglide-plugin.js",
|
|
306
|
+
},
|
|
307
|
+
]);
|
|
301
308
|
// save project to directory to test loading
|
|
302
309
|
await saveProjectToDirectory({
|
|
303
310
|
project,
|
|
304
311
|
path: "/project.inlang",
|
|
305
312
|
fs: fs.promises,
|
|
306
313
|
});
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
314
|
+
try {
|
|
315
|
+
await compile({
|
|
316
|
+
project: "/project.inlang",
|
|
317
|
+
outdir: "/output",
|
|
318
|
+
fs: fs,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
vi.unstubAllGlobals();
|
|
323
|
+
errorsSpy.mockRestore();
|
|
324
|
+
}
|
|
312
325
|
expect(mock).toHaveBeenCalled();
|
|
313
326
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-file.d.ts","sourceRoot":"","sources":["../../src/compiler/output-file.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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,CA+GxB"}
|
|
@@ -31,19 +31,41 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
31
31
|
}
|
|
32
32
|
// add the fallbacks (needs to be done after the messages to avoid referencing
|
|
33
33
|
// the message before they are defined)
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const needsFallbackSet = new Set(needsFallback);
|
|
35
|
+
const emittedFallbacks = new Set();
|
|
36
|
+
const emittingFallbacks = new Set();
|
|
37
|
+
/**
|
|
38
|
+
* Emits the fallback definition for a locale ensuring that dependent fallbacks
|
|
39
|
+
* are declared beforehand.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* emitFallback("fr-ca");
|
|
43
|
+
*/
|
|
44
|
+
const emitFallback = (locale) => {
|
|
45
|
+
if (emittedFallbacks.has(locale))
|
|
46
|
+
return;
|
|
47
|
+
if (emittingFallbacks.has(locale))
|
|
48
|
+
return;
|
|
49
|
+
emittingFallbacks.add(locale);
|
|
36
50
|
const safeLocale = toSafeModuleId(locale);
|
|
37
51
|
const fallbackLocale = fallbackMap[locale];
|
|
52
|
+
if (fallbackLocale &&
|
|
53
|
+
needsFallbackSet.has(fallbackLocale) &&
|
|
54
|
+
!compiledBundle.messages[fallbackLocale]) {
|
|
55
|
+
emitFallback(fallbackLocale);
|
|
56
|
+
}
|
|
38
57
|
if (fallbackLocale) {
|
|
39
58
|
const safeFallbackLocale = toSafeModuleId(fallbackLocale);
|
|
40
|
-
// take the fallback locale
|
|
41
59
|
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => string} */\nconst ${safeLocale}_${safeModuleId} = ${safeFallbackLocale}_${safeModuleId};`);
|
|
42
60
|
}
|
|
43
61
|
else {
|
|
44
|
-
// fallback to just the bundle id
|
|
45
62
|
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => string} */\nconst ${safeLocale}_${safeModuleId} = () => '${escapeForSingleQuoteString(bundleId)}'`);
|
|
46
63
|
}
|
|
64
|
+
emittingFallbacks.delete(locale);
|
|
65
|
+
emittedFallbacks.add(locale);
|
|
66
|
+
};
|
|
67
|
+
for (const locale of needsFallback) {
|
|
68
|
+
emitFallback(locale);
|
|
47
69
|
}
|
|
48
70
|
output[filename] = messages.join("\n\n") + "\n\n" + output[filename];
|
|
49
71
|
// add the imports
|
|
@@ -75,3 +75,45 @@ test("handles case senstivity by creating directories and files only in lowercas
|
|
|
75
75
|
expect(output).toHaveProperty("messages/happyelephant2.js");
|
|
76
76
|
expect(output).not.toHaveProperty("messages/HappyElephant.js");
|
|
77
77
|
});
|
|
78
|
+
// Regression test for https://github.com/opral/inlang-paraglide-js/issues/507
|
|
79
|
+
test("emits fallback definitions after their dependencies", () => {
|
|
80
|
+
const resources = [
|
|
81
|
+
{
|
|
82
|
+
bundle: {
|
|
83
|
+
code: "export const admin_tasks = (inputs) => inputs;",
|
|
84
|
+
node: {
|
|
85
|
+
id: "admin_tasks",
|
|
86
|
+
declarations: [],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
messages: {
|
|
90
|
+
en: {
|
|
91
|
+
code: '/** @type {(inputs: {}) => string} */ () => "admin"',
|
|
92
|
+
node: {},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
const settings = {
|
|
98
|
+
locales: ["fr-ca", "fr", "en"],
|
|
99
|
+
baseLocale: "en",
|
|
100
|
+
};
|
|
101
|
+
const fallbackMap = {
|
|
102
|
+
"fr-ca": "fr",
|
|
103
|
+
fr: "en",
|
|
104
|
+
en: undefined,
|
|
105
|
+
};
|
|
106
|
+
const output = generateOutput(resources, settings, fallbackMap);
|
|
107
|
+
const file = output["messages/admin_tasks.js"];
|
|
108
|
+
expect(file).toBeDefined();
|
|
109
|
+
if (!file) {
|
|
110
|
+
throw new Error("messages/admin_tasks.js should have been generated");
|
|
111
|
+
}
|
|
112
|
+
expect(file).toContain("const fr_admin_tasks = en_admin_tasks;");
|
|
113
|
+
expect(file).toContain("const fr_ca_admin_tasks = fr_admin_tasks;");
|
|
114
|
+
const frIndex = file.indexOf("const fr_admin_tasks");
|
|
115
|
+
const frCaIndex = file.indexOf("const fr_ca_admin_tasks");
|
|
116
|
+
expect(frIndex).toBeGreaterThan(-1);
|
|
117
|
+
expect(frCaIndex).toBeGreaterThan(-1);
|
|
118
|
+
expect(frIndex).toBeLessThan(frCaIndex);
|
|
119
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert-is-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/assert-is-locale.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assert-is-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/assert-is-locale.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,sCAJW,GAAG,GACD,MAAM,CAiBlB"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { isLocale } from "./is-locale.js";
|
|
2
1
|
import { locales } from "./variables.js";
|
|
3
2
|
/**
|
|
4
3
|
* Asserts that the input is a locale.
|
|
@@ -8,8 +7,13 @@ import { locales } from "./variables.js";
|
|
|
8
7
|
* @throws {Error} If the input is not a locale.
|
|
9
8
|
*/
|
|
10
9
|
export function assertIsLocale(input) {
|
|
11
|
-
if (
|
|
10
|
+
if (typeof input !== "string") {
|
|
11
|
+
throw new Error(`Invalid locale: ${input}. Expected a string.`);
|
|
12
|
+
}
|
|
13
|
+
const lowerInput = input.toLowerCase();
|
|
14
|
+
const matchedLocale = locales.find((item) => item.toLowerCase() === lowerInput);
|
|
15
|
+
if (!matchedLocale) {
|
|
12
16
|
throw new Error(`Invalid locale: ${input}. Expected one of: ${locales.join(", ")}`);
|
|
13
17
|
}
|
|
14
|
-
return
|
|
18
|
+
return matchedLocale;
|
|
15
19
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { test, expect } from "vitest";
|
|
2
|
-
import { createParaglide } from "../create-paraglide.js";
|
|
3
1
|
import { newProject } from "@inlang/sdk";
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
|
+
import { createParaglide } from "../create-paraglide.js";
|
|
4
4
|
test("throws if the locale is not available", async () => {
|
|
5
5
|
const runtime = await createParaglide({
|
|
6
6
|
blob: await newProject({
|
|
@@ -12,6 +12,21 @@ test("throws if the locale is not available", async () => {
|
|
|
12
12
|
});
|
|
13
13
|
expect(() => runtime.assertIsLocale("es")).toThrow();
|
|
14
14
|
});
|
|
15
|
+
test("throws for non-string inputs", async () => {
|
|
16
|
+
const runtime = await createParaglide({
|
|
17
|
+
blob: await newProject({
|
|
18
|
+
settings: {
|
|
19
|
+
baseLocale: "en",
|
|
20
|
+
locales: ["en", "de"],
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
expect(() => runtime.assertIsLocale(null)).toThrow();
|
|
25
|
+
expect(() => runtime.assertIsLocale(undefined)).toThrow();
|
|
26
|
+
expect(() => runtime.assertIsLocale(123)).toThrow();
|
|
27
|
+
expect(() => runtime.assertIsLocale({})).toThrow();
|
|
28
|
+
expect(() => runtime.assertIsLocale([])).toThrow();
|
|
29
|
+
});
|
|
15
30
|
test("passes if the locale is available", async () => {
|
|
16
31
|
const runtime = await createParaglide({
|
|
17
32
|
blob: await newProject({
|
|
@@ -37,3 +52,19 @@ test("the return value is a Locale", async () => {
|
|
|
37
52
|
// in the ambient type definition
|
|
38
53
|
locale;
|
|
39
54
|
});
|
|
55
|
+
test("is case-insensitive", async () => {
|
|
56
|
+
const runtime = await createParaglide({
|
|
57
|
+
blob: await newProject({
|
|
58
|
+
settings: {
|
|
59
|
+
baseLocale: "en",
|
|
60
|
+
locales: ["en", "pt-BR", "de-ch"],
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
});
|
|
64
|
+
expect(() => runtime.assertIsLocale("EN")).not.toThrow();
|
|
65
|
+
expect(() => runtime.assertIsLocale("pt-br")).not.toThrow();
|
|
66
|
+
expect(() => runtime.assertIsLocale("de-CH")).not.toThrow();
|
|
67
|
+
expect(runtime.assertIsLocale("EN")).toBe("en");
|
|
68
|
+
expect(runtime.assertIsLocale("pT-bR")).toBe("pt-BR");
|
|
69
|
+
expect(runtime.assertIsLocale("de-CH")).toBe("de-ch");
|
|
70
|
+
});
|
|
@@ -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,CA4IT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/is-locale.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,iCAHW,GAAG,GACD,MAAM,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"is-locale.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/is-locale.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,iCAHW,GAAG,GACD,MAAM,IAAI,MAAM,CAO5B"}
|