UNPKG

@lingui/cli

Version:

Lingui CLI to extract messages, compile catalogs, and manage translation workflows

139 lines (138 loc) 6.13 kB
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); } }); } }