@inlang/paraglide-js 2.4.0 → 2.6.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/bundler-plugins/vite.d.ts +1 -1
- package/dist/bundler-plugins/vite.d.ts.map +1 -1
- package/dist/bundler-plugins/webpack.d.ts +1 -1
- package/dist/bundler-plugins/webpack.d.ts.map +1 -1
- 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-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/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 +99 -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 +36 -11
- 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 +42 -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/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
|
@@ -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,CA6HjC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
esModuleInterop: true,
|
|
44
|
+
lib: ["ESNext", "DOM"],
|
|
45
|
+
module: ts.ModuleKind.ESNext,
|
|
46
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
47
|
+
noEmitOnError: false,
|
|
48
|
+
outDir: virtualRoot,
|
|
49
|
+
rootDir: virtualRoot,
|
|
50
|
+
skipLibCheck: true,
|
|
51
|
+
target: ts.ScriptTarget.ESNext,
|
|
52
|
+
};
|
|
53
|
+
const defaultHost = ts.createCompilerHost(compilerOptions, true);
|
|
54
|
+
const declarations = {};
|
|
55
|
+
const host = {
|
|
56
|
+
...defaultHost,
|
|
57
|
+
fileExists: (fileName) => {
|
|
58
|
+
const normalized = normalizeFileName(fileName);
|
|
59
|
+
return files.has(normalized) || defaultHost.fileExists(fileName);
|
|
60
|
+
},
|
|
61
|
+
directoryExists: (directoryName) => {
|
|
62
|
+
const normalized = normalizeFileName(directoryName);
|
|
63
|
+
return (virtualDirectories.has(normalized) ||
|
|
64
|
+
defaultHost.directoryExists?.(directoryName) === true);
|
|
65
|
+
},
|
|
66
|
+
getDirectories: (directoryName) => {
|
|
67
|
+
const normalized = normalizeFileName(directoryName);
|
|
68
|
+
const children = Array.from(virtualDirectories).filter((dir) => path.dirname(dir) === normalized);
|
|
69
|
+
return [
|
|
70
|
+
...(defaultHost.getDirectories?.(directoryName) ?? []),
|
|
71
|
+
...children.map((dir) => path.basename(dir)),
|
|
72
|
+
];
|
|
73
|
+
},
|
|
74
|
+
readFile: (fileName) => {
|
|
75
|
+
const normalized = normalizeFileName(fileName);
|
|
76
|
+
return files.get(normalized) ?? defaultHost.readFile(fileName);
|
|
77
|
+
},
|
|
78
|
+
getSourceFile: (fileName, languageVersion, onError, shouldCreateNewFile) => {
|
|
79
|
+
const normalized = normalizeFileName(fileName);
|
|
80
|
+
const sourceText = files.get(normalized);
|
|
81
|
+
if (sourceText !== undefined) {
|
|
82
|
+
return ts.createSourceFile(fileName, sourceText, languageVersion, true);
|
|
83
|
+
}
|
|
84
|
+
return defaultHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewFile);
|
|
85
|
+
},
|
|
86
|
+
writeFile: (fileName, text) => {
|
|
87
|
+
const relativePath = path
|
|
88
|
+
.relative(virtualRoot, fileName)
|
|
89
|
+
.split(path.sep)
|
|
90
|
+
.join(path.posix.sep);
|
|
91
|
+
if (!relativePath.startsWith("..")) {
|
|
92
|
+
declarations[relativePath] = text;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const program = ts.createProgram(Array.from(files.keys()), compilerOptions, host);
|
|
97
|
+
program.emit(undefined, undefined, undefined, true);
|
|
98
|
+
return declarations;
|
|
99
|
+
}
|
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"}
|
|
@@ -31,24 +31,47 @@ 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
|
-
|
|
41
|
-
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => string} */\nconst ${safeLocale}_${safeModuleId} = ${safeFallbackLocale}_${safeModuleId};`);
|
|
59
|
+
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */\nconst ${safeLocale}_${safeModuleId} = ${safeFallbackLocale}_${safeModuleId};`);
|
|
42
60
|
}
|
|
43
61
|
else {
|
|
44
|
-
|
|
45
|
-
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => string} */\nconst ${safeLocale}_${safeModuleId} = () => '${escapeForSingleQuoteString(bundleId)}'`);
|
|
62
|
+
messages.push(`/** @type {(inputs: ${inputsType(inputs)}) => LocalizedString} */\nconst ${safeLocale}_${safeModuleId} = () => /** @type {LocalizedString} */ ('${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
|
-
// add the imports
|
|
71
|
+
// add the imports and type reference (LocalizedString is defined in runtime.js)
|
|
50
72
|
output[filename] =
|
|
51
|
-
`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` +
|
|
52
75
|
output[filename];
|
|
53
76
|
// Add the registry import to the message file
|
|
54
77
|
// if registry is used
|
|
@@ -58,8 +81,10 @@ export function generateOutput(compiledBundles, settings, fallbackMap) {
|
|
|
58
81
|
}
|
|
59
82
|
}
|
|
60
83
|
// all messages index file
|
|
61
|
-
output["messages/_index.js"] =
|
|
62
|
-
.
|
|
63
|
-
|
|
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");
|
|
64
89
|
return output;
|
|
65
90
|
}
|
|
@@ -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,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
|
}
|
|
@@ -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"}
|
|
@@ -13,5 +13,9 @@ import { locales } from "./variables.js";
|
|
|
13
13
|
* @returns {locale is Locale}
|
|
14
14
|
*/
|
|
15
15
|
export function isLocale(locale) {
|
|
16
|
-
|
|
16
|
+
if (typeof locale !== "string")
|
|
17
|
+
return false;
|
|
18
|
+
return !locale
|
|
19
|
+
? false
|
|
20
|
+
: locales.some((item) => item.toLowerCase() === locale.toLowerCase());
|
|
17
21
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-locale.test.d.ts","sourceRoot":"","sources":["../../../src/compiler/runtime/is-locale.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { newProject } from "@inlang/sdk";
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
|
+
import { createParaglide } from "../create-paraglide.js";
|
|
4
|
+
const runtime = await createParaglide({
|
|
5
|
+
blob: await newProject({
|
|
6
|
+
settings: {
|
|
7
|
+
baseLocale: "en",
|
|
8
|
+
locales: ["en", "pt-BR", "de-ch"],
|
|
9
|
+
},
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
test("returns true for exact matches", () => {
|
|
13
|
+
expect(runtime.isLocale("pt-BR")).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
test("is case-insensitive", () => {
|
|
16
|
+
expect(runtime.isLocale("EN")).toBe(true);
|
|
17
|
+
expect(runtime.isLocale("pt-br")).toBe(true);
|
|
18
|
+
expect(runtime.isLocale("de-CH")).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
test("returns false for non-existent locales", () => {
|
|
21
|
+
expect(runtime.isLocale("es")).toBe(false);
|
|
22
|
+
expect(runtime.isLocale("xx")).toBe(false);
|
|
23
|
+
expect(runtime.isLocale("")).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
test("returns false for non-string inputs", () => {
|
|
26
|
+
expect(runtime.isLocale(null)).toBe(false);
|
|
27
|
+
expect(runtime.isLocale(undefined)).toBe(false);
|
|
28
|
+
expect(runtime.isLocale(123)).toBe(false);
|
|
29
|
+
expect(runtime.isLocale({})).toBe(false);
|
|
30
|
+
expect(runtime.isLocale([])).toBe(false);
|
|
31
|
+
});
|
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.6.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"./urlpattern-polyfill": "./dist/urlpattern-polyfill/index.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@inlang/sdk": "2.4.9",
|
|
30
29
|
"commander": "11.1.0",
|
|
31
30
|
"consola": "3.4.0",
|
|
32
31
|
"json5": "2.2.3",
|
|
33
32
|
"unplugin": "^2.1.2",
|
|
34
33
|
"urlpattern-polyfill": "^10.0.0",
|
|
35
|
-
"@inlang/recommend-sherlock": "0.2.1"
|
|
34
|
+
"@inlang/recommend-sherlock": "0.2.1",
|
|
35
|
+
"@inlang/sdk": "2.4.9"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@rollup/plugin-virtual": "3.0.2",
|