@lingui/cli 6.7.0 → 6.8.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.
Files changed (47) hide show
  1. package/dist/api/catalog/extractFromFiles.d.ts +3 -3
  2. package/dist/api/catalog/extractFromFiles.js +15 -7
  3. package/dist/api/catalog/getTranslationsForCatalog.d.ts +11 -2
  4. package/dist/api/catalog/getTranslationsForCatalog.js +61 -28
  5. package/dist/api/catalog/mergeCatalog.js +3 -1
  6. package/dist/api/catalog/translations.d.ts +16 -0
  7. package/dist/api/catalog/translations.js +35 -0
  8. package/dist/api/catalog.d.ts +0 -1
  9. package/dist/api/catalog.js +4 -58
  10. package/dist/api/check/index.d.ts +8 -0
  11. package/dist/api/check/index.js +39 -0
  12. package/dist/api/check/missing.d.ts +2 -0
  13. package/dist/api/check/missing.js +56 -0
  14. package/dist/api/check/sync.d.ts +2 -0
  15. package/dist/api/check/sync.js +96 -0
  16. package/dist/api/check/types.d.ts +62 -0
  17. package/dist/api/check/types.js +13 -0
  18. package/dist/api/compile/compileLocale.js +6 -7
  19. package/dist/api/extractors/index.d.ts +1 -4
  20. package/dist/api/extractors/index.js +3 -10
  21. package/dist/api/formats/formatterWrapper.d.ts +1 -0
  22. package/dist/api/formats/formatterWrapper.js +12 -5
  23. package/dist/api/index.d.ts +3 -1
  24. package/dist/api/index.js +1 -1
  25. package/dist/api/messages.d.ts +8 -2
  26. package/dist/api/messages.js +29 -2
  27. package/dist/api/resolveWorkersOptions.js +17 -3
  28. package/dist/api/runBounded.d.ts +1 -0
  29. package/dist/api/runBounded.js +13 -0
  30. package/dist/api/utils.d.ts +5 -0
  31. package/dist/api/utils.js +7 -0
  32. package/dist/api/workerPools.d.ts +2 -0
  33. package/dist/api/workerPools.js +2 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/lingui-check.d.ts +6 -0
  37. package/dist/lingui-check.js +137 -0
  38. package/dist/lingui-compile.js +6 -6
  39. package/dist/lingui-extract-experimental.js +1 -1
  40. package/dist/lingui-extract-template.js +1 -1
  41. package/dist/lingui-extract.js +1 -1
  42. package/dist/lingui.js +1 -0
  43. package/dist/workers/missingWorker.d.ts +4 -0
  44. package/dist/workers/missingWorker.js +23 -0
  45. package/dist/workers/missingWorkerWrapper.prod.d.ts +3 -0
  46. package/dist/workers/missingWorkerWrapper.prod.js +2 -0
  47. package/package.json +8 -8
@@ -0,0 +1,13 @@
1
+ export const checkSpecificOptions = [
2
+ "clean",
3
+ "overwrite",
4
+ "missingBehavior",
5
+ ];
6
+ export function finalizeCheckResult(name, findings, passMessage, failMessage) {
7
+ return {
8
+ name,
9
+ passed: findings.length === 0,
10
+ findings,
11
+ summary: findings.length === 0 ? passMessage : failMessage(findings.length),
12
+ };
13
+ }
@@ -5,8 +5,9 @@ import { ProgramExit } from "../ProgramExit.js";
5
5
  import { createCompiledCatalog } from "../compile.js";
6
6
  import normalizePath from "normalize-path";
7
7
  import nodepath from "path";
8
- import { createCompilationErrorMessage } from "../messages.js";
8
+ import { createCompilationErrorMessage, getMissingBehaviorDescription, } from "../messages.js";
9
9
  import { getTranslationsForCatalog } from "../catalog/getTranslationsForCatalog.js";
10
+ import { createMissingTranslationFinding } from "../catalog/translations.js";
10
11
  export async function compileLocale(catalogs, locale, options, config, doMerge, logger) {
11
12
  let mergedCatalogs = {};
12
13
  for (const catalog of catalogs) {
@@ -20,16 +21,14 @@ export async function compileLocale(catalogs, locale, options, config, doMerge,
20
21
  missingMessages.length > 0) {
21
22
  logger.error(styleText("red", `Error: Failed to compile catalog for locale ${styleText("bold", locale)}!`));
22
23
  if (options.verbose) {
23
- logger.error(styleText("red", "Missing translations:"));
24
+ logger.error(styleText("red", `Missing translations ${getMissingBehaviorDescription("resolved")}:`));
24
25
  missingMessages.forEach((missing) => {
25
- const source = missing.source || missing.source === missing.id
26
- ? `: (${missing.source})`
27
- : "";
28
- logger.error(`${missing.id}${source}`);
26
+ const finding = createMissingTranslationFinding(catalog, locale, missing);
27
+ logger.error(`${finding.catalogPath}: ${finding.message}`);
29
28
  });
30
29
  }
31
30
  else {
32
- logger.error(styleText("red", `Missing ${missingMessages.length} translation(s)`));
31
+ logger.error(styleText("red", `Missing ${missingMessages.length} translation(s) ${getMissingBehaviorDescription("resolved")}`));
33
32
  }
34
33
  logger.error("");
35
34
  throw new ProgramExit();
@@ -1,5 +1,2 @@
1
- import { Experimental__BatchExtractorType, ExtractedMessage, ExtractorType, LinguiConfigNormalized, PerFileExtractorType } from "@lingui/conf";
2
- export declare function isBatchExtractor(ext: ExtractorType): ext is Experimental__BatchExtractorType;
3
- export declare function isPerFileExtractor(ext: ExtractorType): ext is PerFileExtractorType;
4
- export declare const getConfiguredExtractors: (linguiConfig: LinguiConfigNormalized) => ExtractorType[];
1
+ import { ExtractedMessage, LinguiConfigNormalized } from "@lingui/conf";
5
2
  export default function extract(filename: string, onMessageExtracted: (msg: ExtractedMessage) => void, linguiConfig: LinguiConfigNormalized): Promise<boolean>;
@@ -9,17 +9,10 @@ function createDefaultExtractor(linguiConfig) {
9
9
  }
10
10
  return defaultExtractor;
11
11
  }
12
- export function isBatchExtractor(ext) {
13
- return "extractFromFiles" in ext && typeof ext.extractFromFiles === "function";
14
- }
15
- export function isPerFileExtractor(ext) {
16
- return "extract" in ext && typeof ext.extract === "function";
17
- }
18
- export const getConfiguredExtractors = (linguiConfig) => {
19
- return linguiConfig.extractors ?? [createDefaultExtractor(linguiConfig)];
20
- };
21
12
  export default async function extract(filename, onMessageExtracted, linguiConfig) {
22
- const extractorsToExtract = getConfiguredExtractors(linguiConfig).filter(isPerFileExtractor);
13
+ const extractorsToExtract = linguiConfig.extractors ?? [
14
+ createDefaultExtractor(linguiConfig),
15
+ ];
23
16
  for (const ext of extractorsToExtract) {
24
17
  if (!ext.match(filename))
25
18
  continue;
@@ -5,6 +5,7 @@ export declare class FormatterWrapper {
5
5
  constructor(f: CatalogFormatter, sourceLocale: string);
6
6
  getCatalogExtension(): string;
7
7
  getTemplateExtension(): string;
8
+ serialize(filename: string, catalog: CatalogType, locale?: string, existing?: string): Promise<string>;
8
9
  write(filename: string, catalog: CatalogType, locale?: string): Promise<void>;
9
10
  read(filename: string, locale: string | undefined): Promise<CatalogType | undefined>;
10
11
  }
@@ -1,4 +1,4 @@
1
- import { readFile, writeFileIfChanged } from "../utils.js";
1
+ import { readFile, writeFile } from "../utils.js";
2
2
  import { RethrownError } from "../rethrownError.js";
3
3
  export class FormatterWrapper {
4
4
  f;
@@ -13,14 +13,21 @@ export class FormatterWrapper {
13
13
  getTemplateExtension() {
14
14
  return this.f.templateExtension || this.f.catalogExtension;
15
15
  }
16
- async write(filename, catalog, locale) {
17
- const content = await this.f.serialize(catalog, {
16
+ async serialize(filename, catalog, locale, existing) {
17
+ const resolvedExisting = arguments.length >= 4 ? existing : await readFile(filename);
18
+ return await this.f.serialize(catalog, {
18
19
  locale,
19
20
  sourceLocale: this.sourceLocale,
20
- existing: await readFile(filename),
21
+ existing: resolvedExisting,
21
22
  filename,
22
23
  });
23
- await writeFileIfChanged(filename, content);
24
+ }
25
+ async write(filename, catalog, locale) {
26
+ const existing = await readFile(filename);
27
+ const content = await this.serialize(filename, catalog, locale, existing);
28
+ if (content !== existing) {
29
+ await writeFile(filename, content);
30
+ }
24
31
  }
25
32
  async read(filename, locale) {
26
33
  const content = await readFile(filename);
@@ -3,5 +3,7 @@ export { getCatalogForFile, getCatalogs } from "./catalog/getCatalogs.js";
3
3
  export { createCompiledCatalog } from "./compile.js";
4
4
  export { default as extractor, extractFromFileWithBabel, } from "./extractors/babel.js";
5
5
  export { getCatalogDependentFiles } from "./catalog/getCatalogDependentFiles.js";
6
- export { createMissingErrorMessage, createCompilationErrorMessage, } from "./messages.js";
6
+ export { createMissingErrorMessage, createCompilationErrorMessage, isFailOnMissingEnabled, getFailOnMissingBehavior, formatFailOnMissingOption, } from "./messages.js";
7
+ export type { FailOnMissingOption } from "./messages.js";
8
+ export type { MissingBehavior } from "./catalog/getTranslationsForCatalog.js";
7
9
  export * from "./types.js";
package/dist/api/index.js CHANGED
@@ -3,5 +3,5 @@ export { getCatalogForFile, getCatalogs } from "./catalog/getCatalogs.js";
3
3
  export { createCompiledCatalog } from "./compile.js";
4
4
  export { default as extractor, extractFromFileWithBabel, } from "./extractors/babel.js";
5
5
  export { getCatalogDependentFiles } from "./catalog/getCatalogDependentFiles.js";
6
- export { createMissingErrorMessage, createCompilationErrorMessage, } from "./messages.js";
6
+ export { createMissingErrorMessage, createCompilationErrorMessage, isFailOnMissingEnabled, getFailOnMissingBehavior, formatFailOnMissingOption, } from "./messages.js";
7
7
  export * from "./types.js";
@@ -1,4 +1,10 @@
1
- import { TranslationMissingEvent } from "./catalog/getTranslationsForCatalog.js";
2
- import { MessageCompilationError } from "./compile.js";
1
+ import { type MissingBehavior, type TranslationMissingEvent } from "./catalog/getTranslationsForCatalog.js";
2
+ import type { MessageCompilationError } from "./compile.js";
3
+ export declare function getMissingBehaviorDescription(missingBehavior: MissingBehavior): "before applying fallbackLocales" | "after applying fallbackLocales";
4
+ export type FailOnMissingOption = boolean | MissingBehavior;
5
+ export declare function isFailOnMissingEnabled(option: FailOnMissingOption | undefined): option is true | "resolved" | "catalog";
6
+ export declare function getFailOnMissingBehavior(option: FailOnMissingOption | undefined): MissingBehavior;
7
+ export declare function formatFailOnMissingOption(option: FailOnMissingOption | undefined): "true" | "\"resolved\"" | "\"catalog\"" | "false";
3
8
  export declare function createMissingErrorMessage(locale: string, missingMessages: TranslationMissingEvent[], configurationMsg: string): string;
9
+ export declare function createMissingErrorMessage(locale: string, missingMessages: TranslationMissingEvent[], missingBehavior?: MissingBehavior): string;
4
10
  export declare function createCompilationErrorMessage(locale: string, errors: MessageCompilationError[]): string;
@@ -1,8 +1,35 @@
1
+ import { isMissingBehavior, } from "./catalog/getTranslationsForCatalog.js";
1
2
  import { styleText } from "node:util";
2
- export function createMissingErrorMessage(locale, missingMessages, configurationMsg) {
3
+ export function getMissingBehaviorDescription(missingBehavior) {
4
+ return missingBehavior === "catalog"
5
+ ? "before applying fallbackLocales"
6
+ : "after applying fallbackLocales";
7
+ }
8
+ export function isFailOnMissingEnabled(option) {
9
+ return option === true || option === "resolved" || option === "catalog";
10
+ }
11
+ export function getFailOnMissingBehavior(option) {
12
+ return option === "catalog" ? "catalog" : "resolved";
13
+ }
14
+ export function formatFailOnMissingOption(option) {
15
+ if (option === true)
16
+ return "true";
17
+ if (option === "resolved")
18
+ return '"resolved"';
19
+ if (option === "catalog")
20
+ return '"catalog"';
21
+ return "false";
22
+ }
23
+ export function createMissingErrorMessage(locale, missingMessages, missingBehaviorOrConfigurationMsg = "resolved") {
24
+ const missingBehavior = isMissingBehavior(missingBehaviorOrConfigurationMsg ?? "")
25
+ ? missingBehaviorOrConfigurationMsg
26
+ : undefined;
27
+ const missingBehaviorDescription = missingBehavior
28
+ ? ` ${getMissingBehaviorDescription(missingBehavior)}`
29
+ : "";
3
30
  let message = `Failed to compile catalog for locale ${styleText("bold", locale)}!
4
31
 
5
- Missing ${missingMessages.length} translation(s):
32
+ Missing ${missingMessages.length} translation(s)${missingBehaviorDescription}:
6
33
  \n`;
7
34
  missingMessages.forEach((missing) => {
8
35
  const source = missing.source || missing.source === missing.id
@@ -1,15 +1,29 @@
1
1
  import * as os from "node:os";
2
+ function parseWorkers(workers) {
3
+ if (workers === undefined) {
4
+ return undefined;
5
+ }
6
+ const parsedWorkers = Number(workers);
7
+ if (!Number.isFinite(parsedWorkers) || !Number.isInteger(parsedWorkers)) {
8
+ throw new Error("The `--workers` option must be an integer.");
9
+ }
10
+ return parsedWorkers;
11
+ }
2
12
  export function resolveWorkersOptions(opts) {
3
13
  const cores = os.availableParallelism();
4
- if (Number(opts.workers) <= 1 || cores === 1) {
14
+ const workers = parseWorkers(opts.workers);
15
+ if (workers !== undefined && workers <= 1) {
16
+ return { poolSize: 0 };
17
+ }
18
+ if (cores === 1) {
5
19
  return { poolSize: 0 };
6
20
  }
7
- if (!opts.workers) {
21
+ if (workers === undefined) {
8
22
  if (cores <= 2) {
9
23
  return { poolSize: cores }; // on tiny machines, use all
10
24
  }
11
25
  // on big machines cap to 8, to avoid trashing
12
26
  return { poolSize: Math.min(cores - 1, 8) };
13
27
  }
14
- return { poolSize: Number(opts.workers) };
28
+ return { poolSize: workers };
15
29
  }
@@ -0,0 +1 @@
1
+ export declare function runBounded<T, R>(items: readonly T[], concurrency: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
@@ -0,0 +1,13 @@
1
+ export async function runBounded(items, concurrency, worker) {
2
+ const results = new Array(items.length);
3
+ const workerCount = Math.min(Math.max(concurrency, 1), items.length);
4
+ let nextIndex = 0;
5
+ const pool = Array.from({ length: workerCount }, async () => {
6
+ while (nextIndex < items.length) {
7
+ const currentIndex = nextIndex++;
8
+ results[currentIndex] = await worker(items[currentIndex], currentIndex);
9
+ }
10
+ });
11
+ await Promise.all(pool);
12
+ return results;
13
+ }
@@ -14,6 +14,11 @@ export declare function makeInstall(packageName: string, dev?: boolean): string;
14
14
  * Preserve absolute paths: /absolute/path => /absolute/path
15
15
  */
16
16
  export declare function normalizeRelativePath(sourcePath: string): string;
17
+ /**
18
+ * Normalize a path relative to the project rootDir, for display purposes
19
+ * (e.g. finding.catalogPath).
20
+ */
21
+ export declare function toRootRelativePath(rootDir: string, filePath: string): string;
17
22
  /**
18
23
  * Escape special regex characters used in file-based routing systems
19
24
  */
package/dist/api/utils.js CHANGED
@@ -87,6 +87,13 @@ export function normalizeRelativePath(sourcePath) {
87
87
  return (normalizePath(path.relative(process.cwd(), sourcePath), false) +
88
88
  (isDir ? "/" : ""));
89
89
  }
90
+ /**
91
+ * Normalize a path relative to the project rootDir, for display purposes
92
+ * (e.g. finding.catalogPath).
93
+ */
94
+ export function toRootRelativePath(rootDir, filePath) {
95
+ return normalizePath(path.relative(rootDir, filePath));
96
+ }
90
97
  /**
91
98
  * Escape special regex characters used in file-based routing systems
92
99
  */
@@ -1,3 +1,5 @@
1
1
  import { WorkerPool } from "./typedPool.js";
2
2
  import type { ExtractWorkerFunction } from "../workers/extractWorker.js";
3
+ import type { MissingWorkerFunction } from "../workers/missingWorker.js";
3
4
  export type ExtractWorkerPool = WorkerPool<ExtractWorkerFunction>;
5
+ export type MissingWorkerPool = WorkerPool<MissingWorkerFunction>;
@@ -5,3 +5,5 @@ export const createExtractWorkerPool = (opts) => createWorkerPool("../workers/ex
5
5
  export const createExtractExperimentalWorkerPool = (opts) => createWorkerPool("../extract-experimental/workers/extractWorkerWrapper", import.meta.url, opts.poolSize);
6
6
  /** @internal */
7
7
  export const createCompileWorkerPool = (opts) => createWorkerPool("../workers/compileWorkerWrapper", import.meta.url, opts.poolSize);
8
+ /** @internal */
9
+ export const createMissingWorkerPool = (opts) => createWorkerPool("../workers/missingWorkerWrapper", import.meta.url, opts.poolSize);
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { defineConfig } from "@lingui/conf";
1
+ export { defineConfig, type LinguiConfig } from "@lingui/conf";
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
+ // Re-export both the `defineConfig` function and the types it depends on
1
2
  export { defineConfig } from "@lingui/conf";
@@ -0,0 +1,6 @@
1
+ import { Command } from "commander";
2
+ import { LinguiConfigNormalized } from "@lingui/conf";
3
+ import { CheckName, CheckRunOptions, CheckResult } from "./api/check/types.js";
4
+ export declare function renderCheckResult(result: CheckResult, verbose: boolean): string[];
5
+ export declare function runCheck(config: LinguiConfigNormalized, check: CheckName, options: CheckRunOptions): Promise<CheckResult>;
6
+ export declare function createProgram(): Command;
@@ -0,0 +1,137 @@
1
+ import { styleText } from "node:util";
2
+ import { Command } from "commander";
3
+ import ms from "ms";
4
+ import { getConfig } from "@lingui/conf";
5
+ import { helpRun } from "./api/help.js";
6
+ import { getCatalogs } from "./api/index.js";
7
+ import { getCheck, getRegisteredChecks, validateSupportedOptions, } from "./api/check/index.js";
8
+ import { resolveWorkersOptions } from "./api/resolveWorkersOptions.js";
9
+ import { isMissingBehavior } from "./api/catalog/getTranslationsForCatalog.js";
10
+ export function renderCheckResult(result, verbose) {
11
+ const status = result.passed
12
+ ? styleText("green", "PASS")
13
+ : styleText("red", "FAIL");
14
+ const lines = [`${status} ${result.name}: ${result.summary}`];
15
+ if (verbose) {
16
+ lines.push(...result.findings.map((finding) => `${finding.catalogPath}: ${finding.message}`));
17
+ }
18
+ return lines;
19
+ }
20
+ function validateLocales(config, locales) {
21
+ if (!locales?.length) {
22
+ return config.locales;
23
+ }
24
+ const missingLocale = locales.find((locale) => !config.locales.includes(locale));
25
+ if (missingLocale) {
26
+ throw new Error(`Locale ${styleText("bold", missingLocale)} does not exist.`);
27
+ }
28
+ return locales;
29
+ }
30
+ export async function runCheck(config, check, options) {
31
+ const checkDefinition = getCheck(check);
32
+ validateSupportedOptions(checkDefinition, options);
33
+ const locales = validateLocales(config, options.locale);
34
+ const catalogs = await getCatalogs(config);
35
+ return await checkDefinition.run({
36
+ config,
37
+ catalogs,
38
+ locales,
39
+ workersOptions: options.workersOptions,
40
+ clean: options.clean ?? false,
41
+ overwrite: options.overwrite ?? false,
42
+ missingBehavior: options.missingBehavior ?? "resolved",
43
+ });
44
+ }
45
+ function parseLocales(value) {
46
+ return value
47
+ .split(",")
48
+ .map((locale) => locale.trim())
49
+ .filter(Boolean);
50
+ }
51
+ function addCommonOptions(command) {
52
+ return command
53
+ .option("--config <path>", "Path to the config file")
54
+ .option("--locale <locale, [...]>", "Only check the specified locales", parseLocales)
55
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8; on 1-2 core machines, all cores). Pass `--workers 1` to disable worker threads and run everything in a single process")
56
+ .option("--verbose", "Verbose output");
57
+ }
58
+ function parseMissingBehavior(value) {
59
+ if (isMissingBehavior(value)) {
60
+ return value;
61
+ }
62
+ throw new Error("Option `--mode` must be either `resolved` or `catalog`.");
63
+ }
64
+ function renderHelpExamples(examples) {
65
+ if (!examples.length) {
66
+ return;
67
+ }
68
+ console.log("\n Examples:\n");
69
+ examples.forEach((example, index) => {
70
+ console.log(` # ${example.description}`);
71
+ console.log(` $ ${helpRun(example.command)}`);
72
+ if (index < examples.length - 1) {
73
+ console.log("");
74
+ }
75
+ });
76
+ }
77
+ async function runCliCommand(check, options) {
78
+ const startTime = Date.now();
79
+ const config = getConfig({ configPath: options.config });
80
+ const verbose = options.verbose ?? false;
81
+ console.log("Checking message catalogs…");
82
+ const result = await runCheck(config, check, {
83
+ locale: options.locale,
84
+ workersOptions: resolveWorkersOptions(options),
85
+ clean: options.clean,
86
+ overwrite: options.overwrite,
87
+ missingBehavior: options.mode,
88
+ });
89
+ const output = result.passed ? console.log : console.error;
90
+ renderCheckResult(result, verbose).forEach((line) => {
91
+ output(line);
92
+ });
93
+ console.log(`Done in ${ms(Date.now() - startTime)}`);
94
+ return result.passed;
95
+ }
96
+ function registerCheckCommand(checkProgram, check) {
97
+ const command = addCommonOptions(checkProgram.command(check.name).description(check.description));
98
+ check.cli.options.forEach((option) => {
99
+ if (option.name === "mode") {
100
+ command.option(`--${option.name} <mode>`, option.description, parseMissingBehavior);
101
+ return;
102
+ }
103
+ command.option(`--${option.name}`, option.description);
104
+ });
105
+ command
106
+ .on("--help", function () {
107
+ renderHelpExamples(check.cli.examples);
108
+ })
109
+ .action(async (options) => {
110
+ if (!(await runCliCommand(check.name, options))) {
111
+ process.exitCode = 1;
112
+ }
113
+ });
114
+ }
115
+ export function createProgram() {
116
+ const checkProgram = new Command()
117
+ .name("lingui check")
118
+ .description("Check message catalogs.")
119
+ .action(() => {
120
+ checkProgram.help({ error: true });
121
+ })
122
+ .on("--help", function () {
123
+ renderHelpExamples(getRegisteredChecks().flatMap((check) => check.cli.examples));
124
+ });
125
+ getRegisteredChecks().forEach((check) => {
126
+ registerCheckCommand(checkProgram, check);
127
+ });
128
+ return checkProgram;
129
+ }
130
+ if (import.meta.main) {
131
+ createProgram()
132
+ .parseAsync(process.argv)
133
+ .catch((error) => {
134
+ console.error(error instanceof Error ? error.message : String(error));
135
+ process.exit(1);
136
+ });
137
+ }
@@ -9,6 +9,7 @@ import { createCompileWorkerPool } from "./api/workerPools.js";
9
9
  import { resolveWorkersOptions, } from "./api/resolveWorkersOptions.js";
10
10
  import ms from "ms";
11
11
  import { getPathsForCompileWatcher } from "./api/getPathsForCompileWatcher.js";
12
+ import { ProgramExit } from "./api/ProgramExit.js";
12
13
  export async function command(config, options) {
13
14
  const startTime = Date.now();
14
15
  // Check config.compile.merge if catalogs for current locale are to be merged into a single compiled file
@@ -23,7 +24,7 @@ export async function command(config, options) {
23
24
  await compileLocale(catalogs, locale, options, config, doMerge, console);
24
25
  }
25
26
  catch (err) {
26
- if (err.name === "ProgramExit") {
27
+ if (err instanceof ProgramExit) {
27
28
  errored = true;
28
29
  }
29
30
  else {
@@ -68,10 +69,10 @@ if (import.meta.main) {
68
69
  program
69
70
  .description("Compile message catalogs to compiled bundle.")
70
71
  .option("--config <path>", "Path to the config file")
71
- .option("--strict", "Disable defaults for missing translations")
72
+ .option("--strict", "Fail if translations are missing after applying fallbackLocales, or if compilation errors occur")
72
73
  .option("--verbose", "Verbose output")
73
74
  .option("--typescript", "Create Typescript definition for compiled bundle")
74
- .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")
75
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8; on 1-2 core machines, all cores). Pass `--workers 1` to disable worker threads and run everything in a single process")
75
76
  .option("--namespace <namespace>", "Specify namespace for compiled bundle. Ex: cjs(default) -> module.exports, es -> export, window.test -> window.test")
76
77
  .option("--watch", "Enables Watch Mode")
77
78
  .option("--debounce <delay>", "Debounces compilation for given amount of milliseconds")
@@ -81,9 +82,8 @@ if (import.meta.main) {
81
82
  console.log(" # Compile translations and use defaults or message IDs for missing translations");
82
83
  console.log(` $ ${helpRun("compile")}`);
83
84
  console.log("");
84
- console.log(" # Compile translations but fail when there are missing");
85
- console.log(" # translations (don't replace missing translations with");
86
- console.log(" # default messages or message IDs)");
85
+ console.log(" # Compile translations but fail when resolved output");
86
+ console.log(" # still has missing translations or compilation errors");
87
87
  console.log(` $ ${helpRun("compile --strict")}`);
88
88
  })
89
89
  .parse(process.argv);
@@ -183,7 +183,7 @@ if (import.meta.main) {
183
183
  .option("--clean", "Remove obsolete translations")
184
184
  .option("--locale <locale, [...]>", "Only extract the specified locales")
185
185
  .option("--verbose", "Verbose output")
186
- .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")
186
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8; on 1-2 core machines, all cores). Pass `--workers 1` to disable worker threads and run everything in a single process")
187
187
  .parse(process.argv);
188
188
  const options = program.opts();
189
189
  const config = getConfig({
@@ -45,7 +45,7 @@ if (import.meta.main) {
45
45
  program
46
46
  .option("--config <path>", "Path to the config file")
47
47
  .option("--verbose", "Verbose output")
48
- .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")
48
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8; on 1-2 core machines, all cores). Pass `--workers 1` to disable worker threads and run everything in a single process")
49
49
  .parse(process.argv);
50
50
  const options = program.opts();
51
51
  const config = getConfig({
@@ -97,7 +97,7 @@ if (import.meta.main) {
97
97
  .map((s) => s.trim())
98
98
  .filter(Boolean);
99
99
  })
100
- .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")
100
+ .option("--workers <n>", "Number of worker threads to use (default: CPU count - 1, capped at 8; on 1-2 core machines, all cores). Pass `--workers 1` to disable worker threads and run everything in a single process")
101
101
  .option("--overwrite", "Overwrite translations for source locale")
102
102
  .option("--clean", "Remove obsolete translations")
103
103
  .option("--debounce <delay>", "Debounces extraction for given amount of milliseconds")
package/dist/lingui.js CHANGED
@@ -8,5 +8,6 @@ program
8
8
  .command("extract [files...]", "Extracts messages from source files")
9
9
  .command("extract-experimental", "Extracts messages from source files traversing dependency tree")
10
10
  .command("extract-template", "Extracts messages from source files to a .pot template")
11
+ .command("check", "Checks message catalogs")
11
12
  .command("compile", "Compile message catalogs")
12
13
  .parse(process.argv);
@@ -0,0 +1,4 @@
1
+ import { MissingTranslationFinding } from "../api/catalog/translations.js";
2
+ import type { MissingBehavior } from "../api/catalog/getTranslationsForCatalog.js";
3
+ export type MissingWorkerFunction = typeof missingWorker;
4
+ export declare const missingWorker: (catalogPath: string, locale: string, missingBehavior: MissingBehavior, linguiConfigPath: string) => Promise<MissingTranslationFinding[]>;
@@ -0,0 +1,23 @@
1
+ import { getConfig } from "@lingui/conf";
2
+ import { getCatalogs } from "../api/catalog/getCatalogs.js";
3
+ import { getMissingTranslationFindings, } from "../api/catalog/translations.js";
4
+ let linguiConfig;
5
+ let catalogs;
6
+ export const missingWorker = async (catalogPath, locale, missingBehavior, linguiConfigPath) => {
7
+ if (!linguiConfig) {
8
+ // initialize config once per worker, speed up workers follow execution
9
+ linguiConfig = getConfig({
10
+ configPath: linguiConfigPath,
11
+ skipValidation: true,
12
+ });
13
+ }
14
+ if (!catalogs) {
15
+ // catalogs depend only on the config, so initialize them once per worker
16
+ catalogs = await getCatalogs(linguiConfig);
17
+ }
18
+ const catalog = catalogs.find((catalog) => catalog.path === catalogPath);
19
+ if (!catalog) {
20
+ throw new Error(`Unable to find catalog at path ${catalogPath}.`);
21
+ }
22
+ return getMissingTranslationFindings(catalog, locale, missingBehavior);
23
+ };
@@ -0,0 +1,3 @@
1
+ import { missingWorker } from "./missingWorker.js";
2
+ declare const _default: (args: Parameters<typeof missingWorker>) => Promise<import("../api/catalog/translations.js").MissingTranslationFinding[]>;
3
+ export default _default;
@@ -0,0 +1,2 @@
1
+ import { missingWorker } from "./missingWorker.js";
2
+ export default (args) => missingWorker(...args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingui/cli",
3
- "version": "6.7.0",
3
+ "version": "6.8.0",
4
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": [
@@ -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.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",
57
+ "@lingui/babel-plugin-extract-messages": "6.8.0",
58
+ "@lingui/babel-plugin-lingui-macro": "6.8.0",
59
+ "@lingui/conf": "6.8.0",
60
+ "@lingui/core": "6.8.0",
61
+ "@lingui/format-po": "6.8.0",
62
+ "@lingui/message-utils": "6.8.0",
63
63
  "chokidar": "5.0.0",
64
64
  "cli-table3": "^0.6.5",
65
65
  "commander": "^14.0.2",
@@ -97,5 +97,5 @@
97
97
  "rolldown": "^1.1.1",
98
98
  "vitest": "catalog:"
99
99
  },
100
- "gitHead": "a2d4b6d42e052fed1d432050b20cc56cfdacfe98"
100
+ "gitHead": "8c0f1ba378076cc49b320cccd2e06c60afe94963"
101
101
  }