Skip to content

Commit

Permalink
feat: Add debounce option to compile and extract CLI (#1101)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroenvisser101 committed Aug 30, 2021
1 parent 04efd85 commit a13334c
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 48 deletions.
14 changes: 12 additions & 2 deletions docs/ref/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Commands
``extract``
-----------

.. lingui-cli:: extract [files...] [--clean] [--overwrite] [--format <format>] [--locale <locale>] [--convert-from <format>] [--verbose] [--watch]
.. lingui-cli:: extract [files...] [--clean] [--overwrite] [--format <format>] [--locale <locale>] [--convert-from <format>] [--verbose] [--watch [--debounce <delay>]]

This command extracts messages from source files and creates a message catalog for
each language using the following steps:
Expand Down Expand Up @@ -109,6 +109,11 @@ Watches only for changes in files in paths defined in config file or in the comm

Remember to use this only in development as this command do not cleans obsolete translations.

.. lingui-cli-option:: --debounce <delay>

Debounce, when used with ``--debounce <delay>``, delays extraction for ``<delay>`` milliseconds,
bundling multiple file changes together.

``extract-template``
--------------------

Expand All @@ -123,7 +128,7 @@ Prints additional information.
``compile``
-----------

.. lingui-cli:: compile [--strict] [--format <format>] [--verbose] [--namespace <namespace>] [--watch]
.. lingui-cli:: compile [--strict] [--format <format>] [--verbose] [--namespace <namespace>] [--watch [--debounce <delay>]]

This command compiles message catalogs in :conf:`localeDir` and outputs
minified Javascript files. Each message is replaced with a function
Expand Down Expand Up @@ -158,3 +163,8 @@ Generates a {compiledFile}.d.ts and the compiled file is generated using the ext
Watch mode.

Watches only for changes in locale files in your defined locale catalogs. For ex. ``locales\en\messages.po``

.. lingui-cli-option:: --debounce <delay>

Debounce, when used with ``--debounce <delay>``, delays compilation for ``<delay>`` milliseconds,
to avoid compiling multiple times for subsequent file changes.
82 changes: 51 additions & 31 deletions packages/cli/src/lingui-compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getConfig, LinguiConfig } from "@lingui/conf"
import { getCatalogs } from "./api/catalog"
import { createCompiledCatalog } from "./api/compile"
import { helpRun } from "./api/help"
import { getFormat } from "./api";
import { getFormat } from "./api"

const noMessages: (catalogs: Object[]) => boolean = R.pipe(
R.map(R.isEmpty),
Expand Down Expand Up @@ -50,19 +50,13 @@ function command(config: LinguiConfig, options) {
}

catalogs.forEach((catalog) => {
const messages = catalog.getTranslations(
locale,
{
fallbackLocales: config.fallbackLocales,
sourceLocale: config.sourceLocale,
}
)
const messages = catalog.getTranslations(locale, {
fallbackLocales: config.fallbackLocales,
sourceLocale: config.sourceLocale,
})

if (!options.allowEmpty) {
const missingMsgIds = R.pipe(
R.pickBy(R.isNil),
R.keys,
)(messages)
const missingMsgIds = R.pipe(R.pickBy(R.isNil), R.keys)(messages)

if (missingMsgIds.length > 0) {
console.error(
Expand All @@ -77,7 +71,9 @@ function command(config: LinguiConfig, options) {
console.error(chalk.red("Missing translations:"))
missingMsgIds.forEach((msgId) => console.log(msgId))
} else {
console.error(chalk.red(`Missing ${missingMsgIds.length} translation(s)`))
console.error(
chalk.red(`Missing ${missingMsgIds.length} translation(s)`)
)
}
console.error()
process.exit(1)
Expand All @@ -87,7 +83,9 @@ function command(config: LinguiConfig, options) {
if (doMerge) {
mergedCatalogs = { ...mergedCatalogs, ...messages }
} else {
const namespace = options.typescript ? "ts" : options.namespace || config.compileNamespace
const namespace = options.typescript
? "ts"
: options.namespace || config.compileNamespace
const compiledCatalog = createCompiledCatalog(locale, messages, {
strict: false,
namespace,
Expand Down Expand Up @@ -135,6 +133,10 @@ if (require.main === module) {
"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"
)
.on("--help", function () {
console.log("\n Examples:\n")
console.log(
Expand All @@ -159,42 +161,60 @@ if (require.main === module) {
config.format = program.format
}

const compile = () => command(config, {
verbose: program.watch || program.verbose || false,
allowEmpty: !program.strict,
typescript: program.typescript || config.compileNamespace === "ts" || false,
namespace: program.namespace, // we want this to be undefined if user does not specify so default can be used
})
const compile = () =>
command(config, {
verbose: program.watch || program.verbose || false,
allowEmpty: !program.strict,
typescript:
program.typescript || config.compileNamespace === "ts" || false,
namespace: program.namespace, // we want this to be undefined if user does not specify so default can be used
})

let debounceTimer: NodeJS.Timer
const dispatchCompile = () => {
// Skip debouncing if not enabled
if (!program.debounce) return compile()

// CLear the previous timer if there is any, and schedule the next
debounceTimer && clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => compile(), program.debounce)
}

// Check if Watch Mode is enabled
if (program.watch) {
const NAME = "{name}"
const LOCALE = "{locale}"

console.info(chalk.bold("Initializing Watch Mode..."))
console.info(chalk.bold("Initializing Watch Mode..."))

const catalogs = getCatalogs(config);
let paths = [];
const catalogExtension = getFormat(config.format).catalogExtension;
const catalogs = getCatalogs(config)
let paths = []
const catalogExtension = getFormat(config.format).catalogExtension

config.locales.forEach((locale) => {
catalogs.forEach((catalog) => {
paths.push(`${catalog.path.replace(LOCALE, locale).replace(NAME, "*")}${catalogExtension}`)
})
paths.push(
`${catalog.path
.replace(LOCALE, locale)
.replace(NAME, "*")}${catalogExtension}`
)
})
})

const watcher = chokidar.watch(paths, {
persistent: true,
});
})

const onReady = () => {
console.info(chalk.green.bold("Watcher is ready!"))
watcher.on('add', () => compile()).on('change', () => compile());
};
watcher
.on("add", () => dispatchCompile())
.on("change", () => dispatchCompile())
}

watcher.on('ready', () => onReady());
watcher.on("ready", () => onReady())
} else {
const results = compile();
const results = compile()

if (!results) {
process.exit(1)
Expand Down
52 changes: 37 additions & 15 deletions packages/cli/src/lingui-extract.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import chalk from "chalk"
import chokidar from "chokidar"
import chokidar, { watch } from "chokidar"
import program from "commander"

import { getConfig, LinguiConfig } from "@lingui/conf"
Expand Down Expand Up @@ -37,7 +37,7 @@ export default function command(
options.verbose && console.error("Extracting messages from source files…")

const catalogs = getCatalogs(config)
const catalogStats: { [path: string]: AllCatalogsType } = {}
const catalogStats: { [path: string]: AllCatalogsType } = {}
catalogs.forEach((catalog) => {
catalog.make({
...options,
Expand Down Expand Up @@ -65,7 +65,7 @@ export default function command(
`(use "${chalk.yellow(
helpRun("compile")
)}" to compile catalogs for production)`
)
)
}

return true
Expand All @@ -77,6 +77,10 @@ if (require.main === module) {
.option("--locale <locale>", "Only extract the specified locale")
.option("--overwrite", "Overwrite translations for source locale")
.option("--clean", "Remove obsolete translations")
.option(
"--debounce <delay>",
"Debounces extraction for given amount of milliseconds"
)
.option("--verbose", "Verbose output")
.option(
"--convert-from <format>",
Expand Down Expand Up @@ -148,39 +152,57 @@ if (require.main === module) {
})
}

const changedPaths = new Set<string>()
let debounceTimer: NodeJS.Timer
const dispatchExtract = (filePath?: string[]) => {
// Skip debouncing if not enabled
if (!program.debounce) return extract(filePath)

filePath?.forEach((path) => changedPaths.add(path))

// CLear the previous timer if there is any, and schedule the next
debounceTimer && clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
const filePath = [...changedPaths]
changedPaths.clear()

extract(filePath)
}, program.debounce)
}

// Check if Watch Mode is enabled
if (program.watch) {
console.info(chalk.bold("Initializing Watch Mode..."))

const catalogs = getCatalogs(config)
let paths = [];
let ignored = [];
let paths = []
let ignored = []

catalogs.forEach((catalog) => {
paths.push(...catalog.include);
ignored.push(...catalog.exclude);
paths.push(...catalog.include)
ignored.push(...catalog.exclude)
})

const watcher = chokidar.watch(paths, {
ignored: ['/(^|[\/\\])\../', ...ignored],
ignored: ["/(^|[/\\])../", ...ignored],
persistent: true,
});
})

const onReady = () => {
console.info(chalk.green.bold("Watcher is ready!"))
watcher
.on('add', (path) => extract([path]))
.on('change', (path) => extract([path]));
};
.on("add", (path) => dispatchExtract([path]))
.on("change", (path) => dispatchExtract([path]))
}

watcher.on('ready', () => onReady());
watcher.on("ready", () => onReady())
} else if (program.args) {
// this behaviour occurs when we extract files by his name
// for ex: lingui extract src/app, this will extract only files included in src/app
const result = extract(program.args);
const result = extract(program.args)
if (!result) process.exit(1)
} else {
const result = extract();
const result = extract()
if (!result) process.exit(1)
}
}

1 comment on commit a13334c

@vercel
Copy link

@vercel vercel bot commented on a13334c Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.