@lingui/cli
Version:
Lingui CLI to extract messages, compile catalogs, and manage translation workflows
139 lines (138 loc) • 6.13 kB
JavaScript
import { styleText } from "node:util";
import { watch } from "chokidar";
import { program } from "commander";
import { getConfig } from "@lingui/conf";
import { helpRun } from "./api/help.js";
import { getCatalogs } from "./api/index.js";
import { compileLocale } from "./api/compile/compileLocale.js";
import { createCompileWorkerPool } from "./api/workerPools.js";
import { resolveWorkersOptions, } from "./api/resolveWorkersOptions.js";
import ms from "ms";
import { getPathsForCompileWatcher } from "./api/getPathsForCompileWatcher.js";
export async function command(config, options) {
const startTime = Date.now();
// Check config.compile.merge if catalogs for current locale are to be merged into a single compiled file
const doMerge = !!config.catalogsMergePath;
console.log("Compiling message catalogs…");
let errored = false;
if (!options.workersOptions.poolSize) {
// single threaded
const catalogs = await getCatalogs(config);
for (const locale of config.locales) {
try {
await compileLocale(catalogs, locale, options, config, doMerge, console);
}
catch (err) {
if (err.name === "ProgramExit") {
errored = true;
}
else {
throw err;
}
}
}
}
else {
const resolvedConfigPath = config.resolvedConfigPath;
if (!resolvedConfigPath) {
throw new Error("Multithreading is only supported when lingui config loaded from file system, not passed by API");
}
options.verbose &&
console.log(`Use worker pool of size ${options.workersOptions.poolSize}`);
const pool = createCompileWorkerPool({
poolSize: options.workersOptions.poolSize,
});
try {
await Promise.all(config.locales.map(async (locale) => {
const { logs, error, exitProgram } = await pool.run(locale, options, doMerge, resolvedConfigPath);
if (logs.errors) {
console.error(logs.errors);
}
if (exitProgram) {
errored = true;
return;
}
if (error) {
throw error;
}
}));
}
finally {
await pool.destroy();
}
}
console.log(`Done in ${ms(Date.now() - startTime)}`);
return !errored;
}
if (import.meta.main) {
program
.description("Compile message catalogs to compiled bundle.")
.option("--config <path>", "Path to the config file")
.option("--strict", "Disable defaults for missing translations")
.option("--verbose", "Verbose output")
.option("--typescript", "Create Typescript definition for compiled bundle")
.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")
.option("--namespace <namespace>", "Specify namespace for compiled bundle. Ex: cjs(default) -> module.exports, es -> export, window.test -> window.test")
.option("--watch", "Enables Watch Mode")
.option("--debounce <delay>", "Debounces compilation for given amount of milliseconds")
.option("--output-prefix <prefix>", "Add a custom string to the beginning of compiled files (header/prefix). Useful for tools like linters or coverage directives. Defaults to '/*eslint-disable*/'")
.on("--help", function () {
console.log("\n Examples:\n");
console.log(" # Compile translations and use defaults or message IDs for missing translations");
console.log(` $ ${helpRun("compile")}`);
console.log("");
console.log(" # Compile translations but fail when there are missing");
console.log(" # translations (don't replace missing translations with");
console.log(" # default messages or message IDs)");
console.log(` $ ${helpRun("compile --strict")}`);
})
.parse(process.argv);
const options = program.opts();
const config = getConfig({ configPath: options.config });
let previousRun = Promise.resolve(true);
const compile = () => {
previousRun = previousRun.then(() => command(config, {
verbose: options.watch || options.verbose || false,
allowEmpty: !options.strict,
failOnCompileError: !!options.strict,
workersOptions: resolveWorkersOptions(options),
typescript: options.typescript || config.compileNamespace === "ts" || false,
namespace: options.namespace, // we want this to be undefined if user does not specify so default can be used
outputPrefix: options.outputPrefix,
}));
return previousRun;
};
let debounceTimer;
const dispatchCompile = () => {
// Skip debouncing if not enabled
if (!options.debounce)
compile();
// CLear the previous timer if there is any, and schedule the next
debounceTimer && clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => compile(), options.debounce);
};
// Check if Watch Mode is enabled
if (options.watch) {
console.info(styleText("bold", "Initializing Watch Mode..."));
(async function initWatch() {
const { paths } = await getPathsForCompileWatcher(config);
const watcher = watch(paths, {
persistent: true,
});
const onReady = () => {
console.info(styleText(["green", "bold"], "Watcher is ready!"));
watcher
.on("add", () => dispatchCompile())
.on("change", () => dispatchCompile());
};
watcher.on("ready", () => onReady());
})();
}
else {
compile().then((results) => {
if (!results) {
process.exit(1);
}
});
}
}