@lingui/cli 5.4.1 → 5.5.1

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.
Files changed (42) hide show
  1. package/dist/api/ProgramExit.d.ts +3 -0
  2. package/dist/api/ProgramExit.js +10 -0
  3. package/dist/api/catalog/extractFromFiles.d.ts +4 -1
  4. package/dist/api/catalog/extractFromFiles.js +56 -29
  5. package/dist/api/catalog/getCatalogs.d.ts +1 -1
  6. package/dist/api/catalog/getCatalogs.js +6 -9
  7. package/dist/api/catalog/getFallbackListForLocale.js +3 -2
  8. package/dist/api/catalog/getTranslationsForCatalog.d.ts +4 -2
  9. package/dist/api/catalog/getTranslationsForCatalog.js +18 -6
  10. package/dist/api/catalog.d.ts +16 -9
  11. package/dist/api/catalog.js +32 -30
  12. package/dist/api/compile/compileLocale.d.ts +5 -0
  13. package/dist/api/compile/compileLocale.js +84 -0
  14. package/dist/api/extractWorkerPool.d.ts +1 -0
  15. package/dist/api/extractWorkerPool.js +8 -0
  16. package/dist/api/extractors/babel.js +1 -0
  17. package/dist/api/extractors/index.d.ts +2 -6
  18. package/dist/api/extractors/index.js +2 -2
  19. package/dist/api/logger.d.ts +3 -0
  20. package/dist/api/logger.js +2 -0
  21. package/dist/api/resolveWorkersOptions.d.ts +6 -0
  22. package/dist/api/resolveWorkersOptions.js +21 -0
  23. package/dist/api/stats.js +9 -1
  24. package/dist/api/workerLogger.d.ts +9 -0
  25. package/dist/api/workerLogger.js +19 -0
  26. package/dist/extract-experimental/extractFromBundleAndWrite.d.ts +19 -0
  27. package/dist/extract-experimental/extractFromBundleAndWrite.js +63 -0
  28. package/dist/extract-experimental/workers/extractWorker.d.ts +6 -0
  29. package/dist/extract-experimental/workers/extractWorker.js +32 -0
  30. package/dist/lingui-compile.d.ts +2 -0
  31. package/dist/lingui-compile.js +48 -84
  32. package/dist/lingui-extract-experimental.d.ts +4 -2
  33. package/dist/lingui-extract-experimental.js +48 -46
  34. package/dist/lingui-extract-template.d.ts +2 -0
  35. package/dist/lingui-extract-template.js +23 -6
  36. package/dist/lingui-extract.d.ts +2 -0
  37. package/dist/lingui-extract.js +32 -8
  38. package/dist/workers/compileWorker.d.ts +11 -0
  39. package/dist/workers/compileWorker.js +39 -0
  40. package/dist/workers/extractWorker.d.ts +7 -0
  41. package/dist/workers/extractWorker.js +24 -0
  42. package/package.json +14 -10
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkerLogger = void 0;
4
+ class WorkerLogger {
5
+ constructor() {
6
+ this.errors = [];
7
+ }
8
+ error(msg) {
9
+ this.errors.push(msg);
10
+ }
11
+ flush() {
12
+ const errors = this.errors.join("\n");
13
+ this.errors = [];
14
+ return {
15
+ errors,
16
+ };
17
+ }
18
+ }
19
+ exports.WorkerLogger = WorkerLogger;
@@ -0,0 +1,19 @@
1
+ import { LinguiConfigNormalized } from "@lingui/conf";
2
+ import { FormatterWrapper } from "../api/formats";
3
+ export declare function extractFromBundleAndWrite(params: {
4
+ entryPoint: string;
5
+ bundleFile: string;
6
+ linguiConfig: LinguiConfigNormalized;
7
+ outputPattern: string;
8
+ format: FormatterWrapper;
9
+ template: boolean;
10
+ locales: string[];
11
+ clean: boolean;
12
+ overwrite: boolean;
13
+ }): Promise<{
14
+ success: boolean;
15
+ stat?: undefined;
16
+ } | {
17
+ success: boolean;
18
+ stat: string;
19
+ }>;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.extractFromBundleAndWrite = extractFromBundleAndWrite;
7
+ const extractFromFiles_1 = require("../api/catalog/extractFromFiles");
8
+ const writeCatalogs_1 = require("./writeCatalogs");
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const babel_1 = require("../api/extractors/babel");
11
+ async function extractFromBundle(filename, linguiConfig) {
12
+ const messages = {};
13
+ let success;
14
+ try {
15
+ const file = await promises_1.default.readFile(filename);
16
+ const parserOptions = linguiConfig.extractorParserOptions;
17
+ await (0, babel_1.extractFromFileWithBabel)(filename, file.toString(), (msg) => {
18
+ (0, extractFromFiles_1.mergeExtractedMessage)(msg, messages, linguiConfig);
19
+ }, {
20
+ linguiConfig,
21
+ }, {
22
+ plugins: (0, babel_1.getBabelParserOptions)(filename, parserOptions),
23
+ }, true);
24
+ success = true;
25
+ }
26
+ catch (e) {
27
+ console.error(`Cannot process file ${filename} ${e.message}`);
28
+ console.error(e.stack);
29
+ success = false;
30
+ }
31
+ return { success, messages };
32
+ }
33
+ async function extractFromBundleAndWrite(params) {
34
+ const { linguiConfig, entryPoint, format, outputPattern, locales, overwrite, clean, template, } = params;
35
+ const { messages, success } = await extractFromBundle(params.bundleFile, params.linguiConfig);
36
+ if (!success) {
37
+ return { success: false };
38
+ }
39
+ let stat;
40
+ if (template) {
41
+ stat = (await (0, writeCatalogs_1.writeTemplate)({
42
+ linguiConfig,
43
+ clean,
44
+ format,
45
+ messages,
46
+ entryPoint,
47
+ outputPattern,
48
+ })).statMessage;
49
+ }
50
+ else {
51
+ stat = (await (0, writeCatalogs_1.writeCatalogs)({
52
+ locales,
53
+ linguiConfig,
54
+ clean,
55
+ format,
56
+ messages,
57
+ entryPoint,
58
+ overwrite,
59
+ outputPattern,
60
+ })).statMessage;
61
+ }
62
+ return { success: true, stat };
63
+ }
@@ -0,0 +1,6 @@
1
+ export type ExtractWorkerFunction = typeof extractWorker;
2
+ declare const extractWorker: (linguiConfigPath: string, entryPoint: string, bundleFile: string, outputPattern: string, template: boolean, locales: string[], clean: boolean, overwrite: boolean) => Promise<{
3
+ success: boolean;
4
+ stat?: string;
5
+ }>;
6
+ export {};
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const worker_1 = require("threads/worker");
4
+ const conf_1 = require("@lingui/conf");
5
+ const extractFromBundleAndWrite_1 = require("../extractFromBundleAndWrite");
6
+ const formats_1 = require("../../api/formats");
7
+ let linguiConfig;
8
+ let format;
9
+ const extractWorker = async (linguiConfigPath, entryPoint, bundleFile, outputPattern, template, locales, clean, overwrite) => {
10
+ if (!linguiConfig) {
11
+ // initialize config once per worker, speed up workers follow execution
12
+ linguiConfig = (0, conf_1.getConfig)({
13
+ configPath: linguiConfigPath,
14
+ skipValidation: true,
15
+ });
16
+ }
17
+ if (!format) {
18
+ format = await (0, formats_1.getFormat)(linguiConfig.format, linguiConfig.formatOptions, linguiConfig.sourceLocale);
19
+ }
20
+ return await (0, extractFromBundleAndWrite_1.extractFromBundleAndWrite)({
21
+ entryPoint,
22
+ bundleFile,
23
+ outputPattern,
24
+ format,
25
+ linguiConfig,
26
+ locales,
27
+ overwrite,
28
+ clean,
29
+ template,
30
+ });
31
+ };
32
+ (0, worker_1.expose)(extractWorker);
@@ -1,4 +1,5 @@
1
1
  import { LinguiConfigNormalized } from "@lingui/conf";
2
+ import { WorkersOptions } from "./api/resolveWorkersOptions";
2
3
  export type CliCompileOptions = {
3
4
  verbose?: boolean;
4
5
  allowEmpty?: boolean;
@@ -6,5 +7,6 @@ export type CliCompileOptions = {
6
7
  typescript?: boolean;
7
8
  watch?: boolean;
8
9
  namespace?: string;
10
+ workersOptions: WorkersOptions;
9
11
  };
10
12
  export declare function command(config: LinguiConfigNormalized, options: CliCompileOptions): Promise<boolean>;
@@ -8,111 +8,75 @@ const picocolors_1 = __importDefault(require("picocolors"));
8
8
  const chokidar_1 = __importDefault(require("chokidar"));
9
9
  const commander_1 = require("commander");
10
10
  const conf_1 = require("@lingui/conf");
11
- const compile_1 = require("./api/compile");
12
11
  const help_1 = require("./api/help");
13
12
  const api_1 = require("./api");
14
- const getCatalogs_1 = require("./api/catalog/getCatalogs");
15
- const normalize_path_1 = __importDefault(require("normalize-path"));
16
- const path_1 = __importDefault(require("path"));
17
- class ProgramExit extends Error {
18
- }
13
+ const compileLocale_1 = require("./api/compile/compileLocale");
14
+ const threads_1 = require("threads");
15
+ const resolveWorkersOptions_1 = require("./api/resolveWorkersOptions");
16
+ const ms_1 = __importDefault(require("ms"));
19
17
  async function command(config, options) {
20
- const catalogs = await (0, api_1.getCatalogs)(config);
18
+ const startTime = Date.now();
21
19
  // Check config.compile.merge if catalogs for current locale are to be merged into a single compiled file
22
20
  const doMerge = !!config.catalogsMergePath;
23
21
  console.log("Compiling message catalogs…");
24
22
  let errored = false;
25
- await Promise.all(config.locales.map(async (locale) => {
26
- try {
27
- await compileLocale(locale, catalogs, options, config, doMerge);
28
- }
29
- catch (err) {
30
- if (err instanceof ProgramExit) {
31
- errored = true;
32
- }
33
- else {
34
- throw err;
35
- }
36
- }
37
- }));
38
- return !errored;
39
- }
40
- async function compileLocale(locale, catalogs, options, config, doMerge) {
41
- let mergedCatalogs = {};
42
- await Promise.all(catalogs.map(async (catalog) => {
43
- const { messages, missing: missingMessages } = await catalog.getTranslations(locale, {
44
- fallbackLocales: config.fallbackLocales,
45
- sourceLocale: config.sourceLocale,
46
- });
47
- if (!options.allowEmpty &&
48
- locale !== config.pseudoLocale &&
49
- missingMessages.length > 0) {
50
- console.error(picocolors_1.default.red(`Error: Failed to compile catalog for locale ${picocolors_1.default.bold(locale)}!`));
51
- if (options.verbose) {
52
- console.error(picocolors_1.default.red("Missing translations:"));
53
- missingMessages.forEach((missing) => {
54
- const source = missing.source || missing.source === missing.id
55
- ? `: (${missing.source})`
56
- : "";
57
- console.error(`${missing.id}${source}`);
58
- });
23
+ if (!options.workersOptions.poolSize) {
24
+ // single threaded
25
+ const catalogs = await (0, api_1.getCatalogs)(config);
26
+ for (const locale of config.locales) {
27
+ try {
28
+ await (0, compileLocale_1.compileLocale)(catalogs, locale, options, config, doMerge, console);
59
29
  }
60
- else {
61
- console.error(picocolors_1.default.red(`Missing ${missingMessages.length} translation(s)`));
30
+ catch (err) {
31
+ if (err.name === "ProgramExit") {
32
+ errored = true;
33
+ }
34
+ else {
35
+ throw err;
36
+ }
62
37
  }
63
- console.error();
64
- throw new ProgramExit();
65
38
  }
66
- if (doMerge) {
67
- mergedCatalogs = Object.assign(Object.assign({}, mergedCatalogs), messages);
39
+ }
40
+ else {
41
+ if (!config.resolvedConfigPath) {
42
+ throw new Error("Multithreading is only supported when lingui config loaded from file system, not passed by API");
68
43
  }
69
- else {
70
- if (!(await compileAndWrite(locale, config, options, catalog, messages))) {
71
- throw new ProgramExit();
44
+ options.verbose &&
45
+ console.log(`Use worker pool of size ${options.workersOptions.poolSize}`);
46
+ const pool = (0, threads_1.Pool)(() => (0, threads_1.spawn)(new threads_1.Worker("./workers/compileWorker")), { size: options.workersOptions.poolSize });
47
+ try {
48
+ for (const locale of config.locales) {
49
+ pool.queue(async (worker) => {
50
+ const { logs, error, exitProgram } = await worker.compileLocale(locale, options, doMerge, config.resolvedConfigPath);
51
+ if (logs.errors) {
52
+ console.error(logs.errors);
53
+ }
54
+ if (exitProgram) {
55
+ errored = true;
56
+ return;
57
+ }
58
+ if (error) {
59
+ throw error;
60
+ }
61
+ });
72
62
  }
63
+ await pool.completed(true);
73
64
  }
74
- }));
75
- if (doMerge) {
76
- const result = await compileAndWrite(locale, config, options, await (0, getCatalogs_1.getCatalogForMerge)(config), mergedCatalogs);
77
- if (!result) {
78
- throw new ProgramExit();
65
+ finally {
66
+ await pool.terminate();
79
67
  }
80
68
  }
81
- }
82
- async function compileAndWrite(locale, config, options, catalogToWrite, messages) {
83
- const namespace = options.typescript
84
- ? "ts"
85
- : options.namespace || config.compileNamespace;
86
- const { source: compiledCatalog, errors } = (0, compile_1.createCompiledCatalog)(locale, messages, {
87
- strict: false,
88
- namespace,
89
- pseudoLocale: config.pseudoLocale,
90
- compilerBabelOptions: config.compilerBabelOptions,
91
- });
92
- if (errors.length) {
93
- let message = (0, api_1.createCompilationErrorMessage)(locale, errors);
94
- if (options.failOnCompileError) {
95
- message += `These errors fail command execution because \`--strict\` option passed`;
96
- console.error(picocolors_1.default.red(message));
97
- return false;
98
- }
99
- else {
100
- message += `You can fail command execution on these errors by passing \`--strict\` option`;
101
- console.error(picocolors_1.default.red(message));
102
- }
103
- }
104
- let compiledPath = await catalogToWrite.writeCompiled(locale, compiledCatalog, namespace);
105
- compiledPath = (0, normalize_path_1.default)(path_1.default.relative(config.rootDir, compiledPath));
106
- options.verbose && console.error(picocolors_1.default.green(`${locale} ⇒ ${compiledPath}`));
107
- return true;
69
+ console.log(`Done in ${(0, ms_1.default)(Date.now() - startTime)}`);
70
+ return !errored;
108
71
  }
109
72
  if (require.main === module) {
110
73
  commander_1.program
111
- .description("Add compile message catalogs and add language data (plurals) to compiled bundle.")
74
+ .description("Compile message catalogs to compiled bundle.")
112
75
  .option("--config <path>", "Path to the config file")
113
76
  .option("--strict", "Disable defaults for missing translations")
114
77
  .option("--verbose", "Verbose output")
115
78
  .option("--typescript", "Create Typescript definition for compiled bundle")
79
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8). Pass `--workers 1` to disable worker threads and run everything in a single process")
116
80
  .option("--namespace <namespace>", "Specify namespace for compiled bundle. Ex: cjs(default) -> module.exports, es -> export, window.test -> window.test")
117
81
  .option("--watch", "Enables Watch Mode")
118
82
  .option("--debounce <delay>", "Debounces compilation for given amount of milliseconds")
@@ -135,6 +99,7 @@ if (require.main === module) {
135
99
  verbose: options.watch || options.verbose || false,
136
100
  allowEmpty: !options.strict,
137
101
  failOnCompileError: !!options.strict,
102
+ workersOptions: (0, resolveWorkersOptions_1.resolveWorkersOptions)(options),
138
103
  typescript: options.typescript || config.compileNamespace === "ts" || false,
139
104
  namespace: options.namespace, // we want this to be undefined if user does not specify so default can be used
140
105
  }));
@@ -180,7 +145,6 @@ if (require.main === module) {
180
145
  if (!results) {
181
146
  process.exit(1);
182
147
  }
183
- console.log("Done!");
184
148
  });
185
149
  }
186
150
  }
@@ -1,10 +1,12 @@
1
1
  import { LinguiConfigNormalized } from "@lingui/conf";
2
+ import { WorkersOptions } from "./api/resolveWorkersOptions";
2
3
  export type CliExtractTemplateOptions = {
3
- verbose: boolean;
4
+ verbose?: boolean;
4
5
  files?: string[];
5
6
  template?: boolean;
6
7
  locales?: string[];
7
8
  overwrite?: boolean;
8
9
  clean?: boolean;
10
+ workersOptions: WorkersOptions;
9
11
  };
10
- export default function command(linguiConfig: LinguiConfigNormalized, options: Partial<CliExtractTemplateOptions>): Promise<boolean>;
12
+ export default function command(linguiConfig: LinguiConfigNormalized, options: CliExtractTemplateOptions): Promise<boolean>;
@@ -10,13 +10,13 @@ const conf_1 = require("@lingui/conf");
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const formats_1 = require("./api/formats");
12
12
  const promises_1 = __importDefault(require("fs/promises"));
13
- const extractFromFiles_1 = require("./api/catalog/extractFromFiles");
14
13
  const normalize_path_1 = __importDefault(require("normalize-path"));
15
14
  const bundleSource_1 = require("./extract-experimental/bundleSource");
16
- const writeCatalogs_1 = require("./extract-experimental/writeCatalogs");
17
15
  const getEntryPoints_1 = require("./extract-experimental/getEntryPoints");
18
16
  const picocolors_1 = __importDefault(require("picocolors"));
19
- const babel_1 = require("./api/extractors/babel");
17
+ const threads_1 = require("threads");
18
+ const resolveWorkersOptions_1 = require("./api/resolveWorkersOptions");
19
+ const extractFromBundleAndWrite_1 = require("./extract-experimental/extractFromBundleAndWrite");
20
20
  async function command(linguiConfig, options) {
21
21
  var _a;
22
22
  options.verbose && console.log("Extracting messages from source files…");
@@ -43,58 +43,58 @@ async function command(linguiConfig, options) {
43
43
  const bundleResult = await (0, bundleSource_1.bundleSource)(linguiConfig, (0, getEntryPoints_1.getEntryPoints)(config.entries), tempDir, linguiConfig.rootDir);
44
44
  const stats = [];
45
45
  let commandSuccess = true;
46
- const format = await (0, formats_1.getFormat)(linguiConfig.format, linguiConfig.formatOptions, linguiConfig.sourceLocale);
47
- linguiConfig.extractors = [
48
- {
49
- match(_filename) {
50
- return true;
51
- },
52
- async extract(filename, code, onMessageExtracted, ctx) {
53
- const parserOptions = ctx.linguiConfig.extractorParserOptions;
54
- return (0, babel_1.extractFromFileWithBabel)(filename, code, onMessageExtracted, ctx, {
55
- plugins: (0, babel_1.getBabelParserOptions)(filename, parserOptions),
56
- }, true);
57
- },
58
- },
59
- ];
60
- for (const outFile of Object.keys(bundleResult.metafile.outputs)) {
61
- const messages = await (0, extractFromFiles_1.extractFromFiles)([outFile], linguiConfig);
62
- const { entryPoint } = bundleResult.metafile.outputs[outFile];
63
- let output;
64
- if (!messages) {
65
- commandSuccess = false;
66
- continue;
46
+ if (options.workersOptions.poolSize) {
47
+ if (!linguiConfig.resolvedConfigPath) {
48
+ throw new Error("Multithreading is only supported when lingui config loaded from file system, not passed by API");
67
49
  }
68
- if (options.template) {
69
- output = (await (0, writeCatalogs_1.writeTemplate)({
70
- linguiConfig,
71
- clean: options.clean,
72
- format,
73
- messages,
50
+ options.verbose &&
51
+ console.log(`Use worker pool of size ${options.workersOptions.poolSize}`);
52
+ const pool = (0, threads_1.Pool)(() => (0, threads_1.spawn)(new threads_1.Worker("./extract-experimental/workers/extractWorker")), { size: options.workersOptions.poolSize });
53
+ try {
54
+ for (const outFile of Object.keys(bundleResult.metafile.outputs)) {
55
+ const { entryPoint } = bundleResult.metafile.outputs[outFile];
56
+ pool.queue(async (extractFromBundleAndWrite) => {
57
+ const { success, stat } = await extractFromBundleAndWrite(linguiConfig.resolvedConfigPath, entryPoint, outFile, config.output, options.template, options.locales || linguiConfig.locales, options.clean, options.overwrite);
58
+ commandSuccess && (commandSuccess = success);
59
+ stats.push({
60
+ entry: (0, normalize_path_1.default)(path_1.default.relative(linguiConfig.rootDir, entryPoint)),
61
+ content: stat,
62
+ });
63
+ });
64
+ }
65
+ await pool.completed();
66
+ }
67
+ finally {
68
+ await pool.terminate();
69
+ }
70
+ }
71
+ else {
72
+ const format = await (0, formats_1.getFormat)(linguiConfig.format, linguiConfig.formatOptions, linguiConfig.sourceLocale);
73
+ for (const outFile of Object.keys(bundleResult.metafile.outputs)) {
74
+ const { entryPoint } = bundleResult.metafile.outputs[outFile];
75
+ const { success, stat } = await (0, extractFromBundleAndWrite_1.extractFromBundleAndWrite)({
74
76
  entryPoint,
77
+ bundleFile: outFile,
75
78
  outputPattern: config.output,
76
- })).statMessage;
77
- }
78
- else {
79
- output = (await (0, writeCatalogs_1.writeCatalogs)({
80
- locales: options.locales || linguiConfig.locales,
81
- linguiConfig,
82
- clean: options.clean,
83
79
  format,
84
- messages,
85
- entryPoint,
80
+ linguiConfig,
81
+ locales: options.locales || linguiConfig.locales,
86
82
  overwrite: options.overwrite,
87
- outputPattern: config.output,
88
- })).statMessage;
83
+ clean: options.clean,
84
+ template: options.template,
85
+ });
86
+ commandSuccess && (commandSuccess = success);
87
+ stats.push({
88
+ entry: (0, normalize_path_1.default)(path_1.default.relative(linguiConfig.rootDir, entryPoint)),
89
+ content: stat,
90
+ });
89
91
  }
90
- stats.push({
91
- entry: (0, normalize_path_1.default)(path_1.default.relative(linguiConfig.rootDir, entryPoint)),
92
- content: output,
93
- });
94
92
  }
95
93
  // cleanup temp directory
96
94
  await promises_1.default.rm(tempDir, { recursive: true, force: true });
97
- stats.forEach(({ entry, content }) => {
95
+ stats
96
+ .sort((a, b) => a.entry.localeCompare(b.entry))
97
+ .forEach(({ entry, content }) => {
98
98
  console.log([`Catalog statistics for ${entry}:`, content, ""].join("\n"));
99
99
  });
100
100
  return commandSuccess;
@@ -107,6 +107,7 @@ if (require.main === module) {
107
107
  .option("--clean", "Remove obsolete translations")
108
108
  .option("--locale <locale, [...]>", "Only extract the specified locales")
109
109
  .option("--verbose", "Verbose output")
110
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8). Pass `--workers 1` to disable worker threads and run everything in a single process")
110
111
  .parse(process.argv);
111
112
  const options = commander_1.program.opts();
112
113
  const config = (0, conf_1.getConfig)({
@@ -118,6 +119,7 @@ if (require.main === module) {
118
119
  locales: (_a = options.locale) === null || _a === void 0 ? void 0 : _a.split(","),
119
120
  overwrite: options.overwrite,
120
121
  clean: options.clean,
122
+ workersOptions: (0, resolveWorkersOptions_1.resolveWorkersOptions)(options),
121
123
  }).then(() => {
122
124
  if (!result)
123
125
  process.exit(1);
@@ -1,6 +1,8 @@
1
1
  import { LinguiConfigNormalized } from "@lingui/conf";
2
+ import { WorkersOptions } from "./api/resolveWorkersOptions";
2
3
  export type CliExtractTemplateOptions = {
3
4
  verbose: boolean;
4
5
  files?: string[];
6
+ workersOptions: WorkersOptions;
5
7
  };
6
8
  export default function command(config: LinguiConfigNormalized, options: Partial<CliExtractTemplateOptions>): Promise<boolean>;
@@ -10,18 +10,33 @@ const conf_1 = require("@lingui/conf");
10
10
  const api_1 = require("./api");
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const normalize_path_1 = __importDefault(require("normalize-path"));
13
+ const extractWorkerPool_1 = require("./api/extractWorkerPool");
14
+ const resolveWorkersOptions_1 = require("./api/resolveWorkersOptions");
13
15
  async function command(config, options) {
14
16
  options.verbose && console.log("Extracting messages from source files…");
15
17
  const catalogs = await (0, api_1.getCatalogs)(config);
16
18
  const catalogStats = {};
17
19
  let commandSuccess = true;
18
- await Promise.all(catalogs.map(async (catalog) => {
19
- const result = await catalog.makeTemplate(Object.assign(Object.assign({}, options), { orderBy: config.orderBy }));
20
- if (result) {
21
- catalogStats[(0, normalize_path_1.default)(path_1.default.relative(config.rootDir, catalog.templateFile))] = Object.keys(result).length;
20
+ let workerPool;
21
+ if (options.workersOptions.poolSize) {
22
+ options.verbose &&
23
+ console.log(`Use worker pool of size ${options.workersOptions.poolSize}`);
24
+ workerPool = (0, extractWorkerPool_1.createExtractWorkerPool)(options.workersOptions);
25
+ }
26
+ try {
27
+ await Promise.all(catalogs.map(async (catalog) => {
28
+ const result = await catalog.makeTemplate(Object.assign(Object.assign({}, options), { orderBy: config.orderBy, workerPool }));
29
+ if (result) {
30
+ catalogStats[(0, normalize_path_1.default)(path_1.default.relative(config.rootDir, catalog.templateFile))] = Object.keys(result).length;
31
+ }
32
+ commandSuccess && (commandSuccess = Boolean(result));
33
+ }));
34
+ }
35
+ finally {
36
+ if (workerPool) {
37
+ await workerPool.terminate();
22
38
  }
23
- commandSuccess && (commandSuccess = Boolean(result));
24
- }));
39
+ }
25
40
  Object.entries(catalogStats).forEach(([key, value]) => {
26
41
  console.log(`Catalog statistics for ${picocolors_1.default.bold(key)}: ${picocolors_1.default.green(value)} messages`);
27
42
  console.log();
@@ -32,6 +47,7 @@ if (require.main === module) {
32
47
  commander_1.program
33
48
  .option("--config <path>", "Path to the config file")
34
49
  .option("--verbose", "Verbose output")
50
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8). Pass `--workers 1` to disable worker threads and run everything in a single process")
35
51
  .parse(process.argv);
36
52
  const options = commander_1.program.opts();
37
53
  const config = (0, conf_1.getConfig)({
@@ -39,6 +55,7 @@ if (require.main === module) {
39
55
  });
40
56
  const result = command(config, {
41
57
  verbose: options.verbose || false,
58
+ workersOptions: (0, resolveWorkersOptions_1.resolveWorkersOptions)(options),
42
59
  }).then(() => {
43
60
  if (!result)
44
61
  process.exit(1);
@@ -1,4 +1,5 @@
1
1
  import { LinguiConfigNormalized } from "@lingui/conf";
2
+ import { WorkersOptions } from "./api/resolveWorkersOptions";
2
3
  export type CliExtractOptions = {
3
4
  verbose: boolean;
4
5
  files?: string[];
@@ -7,5 +8,6 @@ export type CliExtractOptions = {
7
8
  locale: string[];
8
9
  prevFormat: string | null;
9
10
  watch?: boolean;
11
+ workersOptions: WorkersOptions;
10
12
  };
11
13
  export default function command(config: LinguiConfigNormalized, options: Partial<CliExtractOptions>): Promise<boolean>;
@@ -14,22 +14,44 @@ const stats_1 = require("./api/stats");
14
14
  const help_1 = require("./api/help");
15
15
  const ora_1 = __importDefault(require("ora"));
16
16
  const normalize_path_1 = __importDefault(require("normalize-path"));
17
+ const resolveWorkersOptions_1 = require("./api/resolveWorkersOptions");
18
+ const extractWorkerPool_1 = require("./api/extractWorkerPool");
19
+ const ms_1 = __importDefault(require("ms"));
17
20
  async function command(config, options) {
21
+ const startTime = Date.now();
18
22
  options.verbose && console.log("Extracting messages from source files…");
19
23
  const catalogs = await (0, api_1.getCatalogs)(config);
20
24
  const catalogStats = {};
21
25
  let commandSuccess = true;
22
- const spinner = (0, ora_1.default)().start();
23
- await Promise.all(catalogs.map(async (catalog) => {
24
- const result = await catalog.make(Object.assign(Object.assign({}, options), { orderBy: config.orderBy }));
25
- catalogStats[(0, normalize_path_1.default)(path_1.default.relative(config.rootDir, catalog.path))] = result || {};
26
- commandSuccess && (commandSuccess = Boolean(result));
27
- }));
26
+ let workerPool;
27
+ // important to initialize ora before worker pool, otherwise it cause
28
+ // MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 unpipe listeners added to [WriteStream]. MaxListeners is 10. Use emitter.setMaxListeners() to increase limit
29
+ // when workers >= 10
30
+ const spinner = (0, ora_1.default)();
31
+ if (options.workersOptions.poolSize) {
32
+ options.verbose &&
33
+ console.log(`Use worker pool of size ${options.workersOptions.poolSize}`);
34
+ workerPool = (0, extractWorkerPool_1.createExtractWorkerPool)(options.workersOptions);
35
+ }
36
+ spinner.start();
37
+ try {
38
+ await Promise.all(catalogs.map(async (catalog) => {
39
+ const result = await catalog.make(Object.assign(Object.assign({}, options), { orderBy: config.orderBy, workerPool }));
40
+ catalogStats[(0, normalize_path_1.default)(path_1.default.relative(config.rootDir, catalog.path))] = result || {};
41
+ commandSuccess && (commandSuccess = Boolean(result));
42
+ }));
43
+ }
44
+ finally {
45
+ if (workerPool) {
46
+ await workerPool.terminate();
47
+ }
48
+ }
49
+ const doneMsg = `Done in ${(0, ms_1.default)(Date.now() - startTime)}`;
28
50
  if (commandSuccess) {
29
- spinner.succeed();
51
+ spinner.succeed(doneMsg);
30
52
  }
31
53
  else {
32
- spinner.fail();
54
+ spinner.fail(doneMsg);
33
55
  }
34
56
  Object.entries(catalogStats).forEach(([key, value]) => {
35
57
  console.log(`Catalog statistics for ${key}: `);
@@ -67,6 +89,7 @@ if (require.main === module) {
67
89
  .map((s) => s.trim())
68
90
  .filter(Boolean);
69
91
  })
92
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8). Pass `--workers 1` to disable worker threads and run everything in a single process")
70
93
  .option("--overwrite", "Overwrite translations for source locale")
71
94
  .option("--clean", "Remove obsolete translations")
72
95
  .option("--debounce <delay>", "Debounces extraction for given amount of milliseconds")
@@ -109,6 +132,7 @@ if (require.main === module) {
109
132
  watch: options.watch || false,
110
133
  files: (filePath === null || filePath === void 0 ? void 0 : filePath.length) ? filePath : undefined,
111
134
  prevFormat,
135
+ workersOptions: (0, resolveWorkersOptions_1.resolveWorkersOptions)(options),
112
136
  });
113
137
  };
114
138
  const changedPaths = new Set();
@@ -0,0 +1,11 @@
1
+ import { CliCompileOptions } from "../lingui-compile";
2
+ import { SerializedLogs } from "../api/workerLogger";
3
+ declare const compileWorker: {
4
+ compileLocale: (locale: string, options: CliCompileOptions, doMerge: boolean, linguiConfigPath: string) => Promise<{
5
+ logs?: SerializedLogs;
6
+ error?: unknown;
7
+ exitProgram?: boolean;
8
+ }>;
9
+ };
10
+ export type CompileWorker = typeof compileWorker;
11
+ export {};