@lingui/cli 6.5.0 → 6.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/README.md +27 -6
- package/dist/api/catalog/getCatalogs.js +9 -1
- package/dist/api/catalog.d.ts +8 -1
- package/dist/api/catalog.js +45 -14
- package/dist/api/compile/compileLocale.js +5 -3
- package/dist/api/getPathsForExtractWatcher.js +3 -2
- package/dist/api/stats.js +3 -2
- package/dist/services/translationIO.js +2 -2
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -4,22 +4,43 @@
|
|
|
4
4
|
|
|
5
5
|
# @lingui/cli
|
|
6
6
|
|
|
7
|
-
>
|
|
7
|
+
> Lingui CLI: extracts i18n messages from source code into catalogs and compiles the translated catalogs for the runtime
|
|
8
8
|
|
|
9
|
-
`@lingui/cli` is part of [
|
|
9
|
+
`@lingui/cli` is part of [Lingui][documentation]. Lingui is a lightweight, open-source internationalization (i18n) library for JavaScript and TypeScript. It brings compile-time macros and a CLI for message extraction to React, React Native, Vue, SolidJS, Astro, Svelte, and Node.js.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The `lingui` command reads `lingui.config.js` (or `.ts`) from the project root: the source locale, the target locales and where the catalogs live. The [installation guide][installation] shows the minimal file and the [configuration reference][conf] lists every option.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install --save-dev @lingui/cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
- `lingui extract` finds the messages in the source, merges them into one catalog per locale, keeps existing translations and marks removed messages as obsolete.
|
|
22
|
+
- `lingui compile` compiles the translated catalogs into JavaScript modules for the runtime and reports messages with invalid ICU syntax.
|
|
23
|
+
- `lingui extract-template` writes a single `.pot` template with the source messages, for translation platforms that create the locale files themselves.
|
|
24
|
+
- `lingui extract-experimental` follows the imports from each entry point instead of a glob, so a multi-page app gets one catalog per page.
|
|
25
|
+
|
|
26
|
+
Extraction parses JavaScript and TypeScript out of the box; Vue single-file components need [`@lingui/extractor-vue`][extractor-vue]. Catalogs are PO files by default, with [other formats][catalog-formats] available as separate packages. To let the bundler compile catalogs instead of running `lingui compile`, use [`@lingui/vite-plugin`][vite-plugin], [`@lingui/loader`][loader] or [`@lingui/metro-transformer`][metro-transformer].
|
|
27
|
+
|
|
28
|
+
See the [CLI reference][reference] for all commands and options.
|
|
14
29
|
|
|
15
30
|
## License
|
|
16
31
|
|
|
17
|
-
This package is licensed under [MIT][license] license.
|
|
32
|
+
This package is licensed under the [MIT][license] license.
|
|
18
33
|
|
|
19
34
|
[license]: https://github.com/lingui/js-lingui/blob/main/LICENSE
|
|
20
|
-
[linguijs]: https://github.com/lingui/js-lingui
|
|
21
35
|
[documentation]: https://lingui.dev
|
|
36
|
+
[installation]: https://lingui.dev/installation
|
|
37
|
+
[conf]: https://lingui.dev/ref/conf
|
|
22
38
|
[reference]: https://lingui.dev/ref/cli
|
|
39
|
+
[catalog-formats]: https://lingui.dev/ref/catalog-formats
|
|
40
|
+
[extractor-vue]: https://lingui.dev/ref/extractor-vue
|
|
41
|
+
[vite-plugin]: https://lingui.dev/ref/vite-plugin
|
|
42
|
+
[loader]: https://lingui.dev/ref/loader
|
|
43
|
+
[metro-transformer]: https://lingui.dev/ref/metro-transformer
|
|
23
44
|
[package]: https://www.npmjs.com/package/@lingui/cli
|
|
24
45
|
[badge-downloads]: https://img.shields.io/npm/dw/@lingui/cli.svg
|
|
25
46
|
[badge-version]: https://img.shields.io/npm/v/@lingui/cli.svg
|
|
@@ -43,13 +43,18 @@ export async function getCatalogs(config) {
|
|
|
43
43
|
});
|
|
44
44
|
candidates.forEach((catalogDir) => {
|
|
45
45
|
const name = path.basename(catalogDir);
|
|
46
|
+
const globName = escapeCatalogNameForGlob(name);
|
|
46
47
|
catalogs.push(new Catalog({
|
|
47
48
|
name,
|
|
48
49
|
path: normalizeRelativePath(replacePlaceholders(catalog.path, { name })),
|
|
49
50
|
include: include.map((path) => replacePlaceholders(path, { name })),
|
|
50
51
|
exclude: exclude.map((path) => replacePlaceholders(path, { name })),
|
|
51
52
|
format,
|
|
52
|
-
}, config
|
|
53
|
+
}, config, {
|
|
54
|
+
path: replacePlaceholders(catalog.path, { name: globName }),
|
|
55
|
+
include: include.map((path) => replacePlaceholders(path, { name: globName })),
|
|
56
|
+
exclude: exclude.map((path) => replacePlaceholders(path, { name: globName })),
|
|
57
|
+
}));
|
|
53
58
|
});
|
|
54
59
|
});
|
|
55
60
|
if (config.experimental?.extractor &&
|
|
@@ -58,6 +63,9 @@ export async function getCatalogs(config) {
|
|
|
58
63
|
}
|
|
59
64
|
return catalogs;
|
|
60
65
|
}
|
|
66
|
+
function escapeCatalogNameForGlob(value) {
|
|
67
|
+
return value.replace(/[[\]()]/g, "[$&]");
|
|
68
|
+
}
|
|
61
69
|
/**
|
|
62
70
|
* Ensure that value is always array. If not, turn it into an array of one element.
|
|
63
71
|
*/
|
package/dist/api/catalog.d.ts
CHANGED
|
@@ -29,7 +29,13 @@ export type CatalogProps = {
|
|
|
29
29
|
templatePath?: string;
|
|
30
30
|
format: FormatterWrapper;
|
|
31
31
|
};
|
|
32
|
+
type CatalogGlobPatterns = {
|
|
33
|
+
path: string;
|
|
34
|
+
include: Array<string>;
|
|
35
|
+
exclude: Array<string>;
|
|
36
|
+
};
|
|
32
37
|
export declare class Catalog {
|
|
38
|
+
#private;
|
|
33
39
|
config: LinguiConfigNormalized;
|
|
34
40
|
name?: string;
|
|
35
41
|
path: string;
|
|
@@ -37,7 +43,7 @@ export declare class Catalog {
|
|
|
37
43
|
exclude: Array<string>;
|
|
38
44
|
format: FormatterWrapper;
|
|
39
45
|
templateFile: string;
|
|
40
|
-
constructor({ name, path, include, templatePath, format, exclude }: CatalogProps, config: LinguiConfigNormalized);
|
|
46
|
+
constructor({ name, path, include, templatePath, format, exclude }: CatalogProps, config: LinguiConfigNormalized, globPatterns?: CatalogGlobPatterns);
|
|
41
47
|
getFilename(locale: string): string;
|
|
42
48
|
make(options: MakeOptions): Promise<AllCatalogsType | false>;
|
|
43
49
|
makeTemplate(options: MakeTemplateOptions): Promise<CatalogType | false>;
|
|
@@ -71,3 +77,4 @@ export declare function cleanObsolete<T extends CatalogType>(messages: T): T;
|
|
|
71
77
|
export declare function order<T extends CatalogType>(by: OrderBy, catalog: T): T;
|
|
72
78
|
export declare function writeCompiled(path: string, locale: string, compiledCatalog: string, namespace?: CompiledCatalogNamespace): Promise<string>;
|
|
73
79
|
export declare const orderByMessage: OrderByFn;
|
|
80
|
+
export {};
|
package/dist/api/catalog.js
CHANGED
|
@@ -17,12 +17,19 @@ export class Catalog {
|
|
|
17
17
|
exclude;
|
|
18
18
|
format;
|
|
19
19
|
templateFile;
|
|
20
|
-
|
|
20
|
+
#includePatterns;
|
|
21
|
+
#excludePatterns;
|
|
22
|
+
constructor({ name, path, include, templatePath, format, exclude = [] }, config, globPatterns = { path, include, exclude }) {
|
|
21
23
|
this.config = config;
|
|
22
24
|
this.name = name;
|
|
23
25
|
this.path = normalizeRelativePath(path);
|
|
24
26
|
this.include = include.map(normalizeRelativePath);
|
|
25
27
|
this.exclude = [this.localeDir, ...exclude.map(normalizeRelativePath)];
|
|
28
|
+
this.#includePatterns = createCatalogPatterns(this.include, globPatterns.include.map(normalizeRelativePath));
|
|
29
|
+
this.#excludePatterns = createCatalogPatterns(this.exclude, [
|
|
30
|
+
getLocaleDir(normalizeRelativePath(globPatterns.path)),
|
|
31
|
+
...globPatterns.exclude.map(normalizeRelativePath),
|
|
32
|
+
]);
|
|
26
33
|
this.format = format;
|
|
27
34
|
this.templateFile =
|
|
28
35
|
templatePath ||
|
|
@@ -194,30 +201,54 @@ export class Catalog {
|
|
|
194
201
|
async readTemplate() {
|
|
195
202
|
return await this.format.read(this.templateFile, undefined);
|
|
196
203
|
}
|
|
204
|
+
/** @internal */
|
|
205
|
+
get sourcePatterns() {
|
|
206
|
+
return {
|
|
207
|
+
include: resolveCatalogPatterns(this.include, this.#includePatterns),
|
|
208
|
+
exclude: resolveCatalogPatterns(this.exclude, this.#excludePatterns),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
197
211
|
get sourcePaths() {
|
|
198
|
-
const
|
|
212
|
+
const sourcePatterns = this.sourcePatterns;
|
|
213
|
+
const includeGlobs = this.include.map((includePath, index) => {
|
|
214
|
+
const includeGlob = sourcePatterns.include[index];
|
|
199
215
|
const isDir = isDirectory(includePath);
|
|
200
216
|
/**
|
|
201
217
|
* glob library results from absolute patterns such as /foo/* are mounted onto the root setting using path.join.
|
|
202
218
|
* On windows, this will by default result in /foo/* matching C:\foo\bar.txt.
|
|
203
219
|
*/
|
|
204
220
|
return isDir
|
|
205
|
-
? normalize(path.resolve(process.cwd(), includePath === "/" ? "" :
|
|
206
|
-
:
|
|
221
|
+
? normalize(path.resolve(process.cwd(), includePath === "/" ? "" : includeGlob, "**/*.*"))
|
|
222
|
+
: includeGlob;
|
|
207
223
|
});
|
|
208
|
-
return globSync(includeGlobs, { exclude:
|
|
224
|
+
return globSync(includeGlobs, { exclude: sourcePatterns.exclude });
|
|
209
225
|
}
|
|
210
226
|
get localeDir() {
|
|
211
|
-
|
|
212
|
-
if (localePatternIndex === -1) {
|
|
213
|
-
throw Error(`Invalid catalog path: ${LOCALE} variable is missing`);
|
|
214
|
-
}
|
|
215
|
-
return this.path.substring(0, localePatternIndex);
|
|
227
|
+
return getLocaleDir(this.path);
|
|
216
228
|
}
|
|
217
229
|
get locales() {
|
|
218
230
|
return this.config.locales;
|
|
219
231
|
}
|
|
220
232
|
}
|
|
233
|
+
function createCatalogPatterns(values, globs) {
|
|
234
|
+
return values.map((value, index) => ({
|
|
235
|
+
value,
|
|
236
|
+
glob: globs[index] ?? value,
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
function resolveCatalogPatterns(values, patterns) {
|
|
240
|
+
return values.map((value, index) => {
|
|
241
|
+
const pattern = patterns[index];
|
|
242
|
+
return pattern?.value === value ? pattern.glob : value;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function getLocaleDir(catalogPath) {
|
|
246
|
+
const localePatternIndex = catalogPath.indexOf(LOCALE);
|
|
247
|
+
if (localePatternIndex === -1) {
|
|
248
|
+
throw Error(`Invalid catalog path: ${LOCALE} variable is missing`);
|
|
249
|
+
}
|
|
250
|
+
return catalogPath.substring(0, localePatternIndex);
|
|
251
|
+
}
|
|
221
252
|
function getTemplatePath(ext, path) {
|
|
222
253
|
return path.replace(LOCALE_SUFFIX_RE, "messages" + ext);
|
|
223
254
|
}
|
|
@@ -242,12 +273,15 @@ export function order(by, catalog) {
|
|
|
242
273
|
return acc;
|
|
243
274
|
}, {});
|
|
244
275
|
}
|
|
276
|
+
// hardcoded en-US locale to have consistent sorting
|
|
277
|
+
// @see https://github.com/lingui/js-lingui/pull/1808
|
|
278
|
+
const collator = new Intl.Collator("en-US");
|
|
245
279
|
/**
|
|
246
280
|
* Object keys are in the same order as they were created
|
|
247
281
|
* https://stackoverflow.com/a/31102605/1535540
|
|
248
282
|
*/
|
|
249
283
|
const orderByMessageId = (a, b) => {
|
|
250
|
-
return a.messageId
|
|
284
|
+
return collator.compare(a.messageId, b.messageId);
|
|
251
285
|
};
|
|
252
286
|
const orderByOrigin = (a, b) => {
|
|
253
287
|
if (!a.entry.origin || !b.entry.origin) {
|
|
@@ -292,9 +326,6 @@ export async function writeCompiled(path, locale, compiledCatalog, namespace) {
|
|
|
292
326
|
await writeFile(filename, compiledCatalog);
|
|
293
327
|
return filename;
|
|
294
328
|
}
|
|
295
|
-
// hardcoded en-US locale to have consistent sorting
|
|
296
|
-
// @see https://github.com/lingui/js-lingui/pull/1808
|
|
297
|
-
const collator = new Intl.Collator("en-US");
|
|
298
329
|
export const orderByMessage = (a, b) => {
|
|
299
330
|
const aMsg = a.entry.message || "";
|
|
300
331
|
const bMsg = b.entry.message || "";
|
|
@@ -14,8 +14,9 @@ export async function compileLocale(catalogs, locale, options, config, doMerge,
|
|
|
14
14
|
fallbackLocales: config.fallbackLocales,
|
|
15
15
|
sourceLocale: config.sourceLocale,
|
|
16
16
|
});
|
|
17
|
+
const pseudoLocaleConfig = config.pseudoLocale.find((item) => item.locale === locale);
|
|
17
18
|
if (!options.allowEmpty &&
|
|
18
|
-
|
|
19
|
+
!pseudoLocaleConfig &&
|
|
19
20
|
missingMessages.length > 0) {
|
|
20
21
|
logger.error(styleText("red", `Error: Failed to compile catalog for locale ${styleText("bold", locale)}!`));
|
|
21
22
|
if (options.verbose) {
|
|
@@ -53,12 +54,13 @@ async function compileAndWrite(locale, config, options, writePath, messages, log
|
|
|
53
54
|
const namespace = options.typescript
|
|
54
55
|
? "ts"
|
|
55
56
|
: options.namespace || config.compileNamespace;
|
|
57
|
+
const pseudoLocaleConfig = config.pseudoLocale.find((item) => item.locale === locale);
|
|
56
58
|
const { source: compiledCatalog, errors } = createCompiledCatalog(locale, messages, {
|
|
57
59
|
strict: false,
|
|
58
60
|
namespace,
|
|
59
61
|
outputPrefix: options.outputPrefix,
|
|
60
|
-
pseudoLocale:
|
|
61
|
-
pseudoLocaleOptions:
|
|
62
|
+
pseudoLocale: pseudoLocaleConfig?.locale,
|
|
63
|
+
pseudoLocaleOptions: pseudoLocaleConfig?.options,
|
|
62
64
|
compilerBabelOptions: config.compilerBabelOptions,
|
|
63
65
|
});
|
|
64
66
|
if (errors.length) {
|
|
@@ -7,8 +7,9 @@ export async function getPathsForExtractWatcher(config) {
|
|
|
7
7
|
const paths = [];
|
|
8
8
|
const ignored = [];
|
|
9
9
|
catalogs.forEach((catalog) => {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const sourcePatterns = catalog.sourcePatterns;
|
|
11
|
+
paths.push(...sourcePatterns.include);
|
|
12
|
+
ignored.push(...sourcePatterns.exclude);
|
|
12
13
|
});
|
|
13
14
|
return { paths, ignored };
|
|
14
15
|
}
|
package/dist/api/stats.js
CHANGED
|
@@ -25,8 +25,9 @@ export function printStats(config, catalogs) {
|
|
|
25
25
|
return a.localeCompare(b);
|
|
26
26
|
})
|
|
27
27
|
.forEach((locale) => {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// skip pseudo locale
|
|
29
|
+
if (config.pseudoLocale.some((item) => item.locale === locale))
|
|
30
|
+
return;
|
|
30
31
|
const catalog = catalogs[locale];
|
|
31
32
|
// catalog is null if no catalog exists on disk and the locale
|
|
32
33
|
// was not extracted due to a `--locale` filter
|
|
@@ -7,8 +7,8 @@ import { readFileSync } from "node:fs";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
const getTargetLocales = (config) => {
|
|
9
9
|
const sourceLocale = config.sourceLocale || "en";
|
|
10
|
-
const
|
|
11
|
-
return config.locales.filter((value) => value != sourceLocale && value
|
|
10
|
+
const pseudoLocales = new Set(config.pseudoLocale.map((item) => item.locale));
|
|
11
|
+
return config.locales.filter((value) => value != sourceLocale && !pseudoLocales.has(value));
|
|
12
12
|
};
|
|
13
13
|
// Main sync method, call "Init" or "Sync" depending on the project context
|
|
14
14
|
export default async function syncProcess(config, options, extractionResult) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingui/cli",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "Lingui CLI
|
|
3
|
+
"version": "6.7.0",
|
|
4
|
+
"description": "Lingui CLI: extracts i18n messages from source code into catalogs and compiles the translated catalogs for the runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"lingui",
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"@babel/generator": "^7.28.5",
|
|
55
55
|
"@babel/parser": "^7.22.0",
|
|
56
56
|
"@babel/types": "^7.21.2",
|
|
57
|
-
"@lingui/babel-plugin-extract-messages": "6.
|
|
58
|
-
"@lingui/babel-plugin-lingui-macro": "6.
|
|
59
|
-
"@lingui/conf": "6.
|
|
60
|
-
"@lingui/core": "6.
|
|
61
|
-
"@lingui/format-po": "6.
|
|
62
|
-
"@lingui/message-utils": "6.
|
|
57
|
+
"@lingui/babel-plugin-extract-messages": "6.7.0",
|
|
58
|
+
"@lingui/babel-plugin-lingui-macro": "6.7.0",
|
|
59
|
+
"@lingui/conf": "6.7.0",
|
|
60
|
+
"@lingui/core": "6.7.0",
|
|
61
|
+
"@lingui/format-po": "6.7.0",
|
|
62
|
+
"@lingui/message-utils": "6.7.0",
|
|
63
63
|
"chokidar": "5.0.0",
|
|
64
64
|
"cli-table3": "^0.6.5",
|
|
65
65
|
"commander": "^14.0.2",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"ms": "^2.1.3",
|
|
69
69
|
"normalize-path": "^3.0.0",
|
|
70
70
|
"ora": "^9.1.0",
|
|
71
|
-
"pseudolocale": "^
|
|
71
|
+
"pseudolocale": "^3.1.0",
|
|
72
72
|
"source-map": "^0.7.6",
|
|
73
73
|
"tinypool": "^2.1.0"
|
|
74
74
|
},
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"rolldown": "^1.1.1",
|
|
98
98
|
"vitest": "catalog:"
|
|
99
99
|
},
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "a2d4b6d42e052fed1d432050b20cc56cfdacfe98"
|
|
101
101
|
}
|