@lingui/cli 4.0.0-next.4 → 4.0.0-next.6
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.
- package/dist/api/catalog/extractFromFiles.d.ts +5 -0
- package/dist/api/catalog/extractFromFiles.js +47 -0
- package/dist/api/catalog/getCatalogs.d.ts +14 -0
- package/dist/api/catalog/getCatalogs.js +129 -0
- package/dist/api/catalog/getTranslationsForCatalog.d.ts +14 -0
- package/dist/api/catalog/getTranslationsForCatalog.js +72 -0
- package/dist/api/catalog/mergeCatalog.d.ts +3 -0
- package/dist/api/catalog/mergeCatalog.js +55 -0
- package/dist/api/catalog.d.ts +66 -0
- package/dist/api/catalog.js +261 -0
- package/dist/api/compile.d.ts +19 -0
- package/dist/api/compile.js +88 -0
- package/dist/api/extractors/babel.d.ts +3 -0
- package/dist/api/extractors/babel.js +101 -0
- package/dist/api/extractors/index.d.ts +6 -0
- package/dist/api/extractors/index.js +38 -0
- package/dist/api/extractors/typescript.d.ts +3 -0
- package/dist/api/extractors/typescript.js +11 -0
- package/dist/api/formats/formatterWrapper.d.ts +10 -0
- package/dist/api/formats/formatterWrapper.js +43 -0
- package/dist/api/formats/index.d.ts +5 -0
- package/dist/api/formats/index.js +47 -0
- package/dist/api/help.d.ts +1 -0
- package/dist/api/help.js +40 -0
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +25 -0
- package/dist/api/pseudoLocalize.d.ts +1 -0
- package/dist/api/pseudoLocalize.js +56 -0
- package/dist/api/rethrownError.d.ts +4 -0
- package/dist/api/rethrownError.js +11 -0
- package/dist/api/stats.d.ts +6 -0
- package/dist/api/stats.js +41 -0
- package/dist/api/types.d.ts +5 -0
- package/dist/api/types.js +2 -0
- package/dist/api/utils.d.ts +22 -0
- package/dist/api/utils.js +119 -0
- package/dist/extract-experimental/buildExternalizeFilter.d.ts +13 -0
- package/dist/extract-experimental/buildExternalizeFilter.js +38 -0
- package/dist/extract-experimental/bundleSource.d.ts +2 -0
- package/dist/extract-experimental/bundleSource.js +78 -0
- package/dist/extract-experimental/constants.d.ts +2 -0
- package/dist/extract-experimental/constants.js +5 -0
- package/dist/extract-experimental/getEntryPoints.d.ts +1 -0
- package/dist/extract-experimental/getEntryPoints.js +12 -0
- package/dist/extract-experimental/getExperimentalCatalogs.d.ts +3 -0
- package/dist/extract-experimental/getExperimentalCatalogs.js +26 -0
- package/dist/extract-experimental/resolveCatalogPath.d.ts +2 -0
- package/dist/extract-experimental/resolveCatalogPath.js +23 -0
- package/dist/extract-experimental/resolveTemplatePath.d.ts +1 -0
- package/dist/extract-experimental/resolveTemplatePath.js +16 -0
- package/dist/extract-experimental/writeCatalogs.d.ts +21 -0
- package/dist/extract-experimental/writeCatalogs.js +41 -0
- package/dist/lingui-compile.d.ts +9 -0
- package/dist/lingui-compile.js +170 -0
- package/dist/lingui-extract-experimental.d.ts +10 -0
- package/dist/lingui-extract-experimental.js +104 -0
- package/dist/lingui-extract-template.d.ts +6 -0
- package/dist/lingui-extract-template.js +46 -0
- package/dist/lingui-extract.d.ts +11 -0
- package/dist/lingui-extract.js +156 -0
- package/dist/lingui.d.ts +2 -0
- package/dist/lingui.js +13 -0
- package/dist/services/translationIO.d.ts +3 -0
- package/dist/services/translationIO.js +264 -0
- package/package.json +29 -14
- package/build/LICENSE +0 -21
- package/build/api/catalog/extractFromFiles.js +0 -45
- package/build/api/catalog/getCatalogs.js +0 -138
- package/build/api/catalog/getTranslationsForCatalog.js +0 -77
- package/build/api/catalog/mergeCatalog.js +0 -44
- package/build/api/catalog.js +0 -245
- package/build/api/compile.js +0 -72
- package/build/api/extractors/babel.js +0 -92
- package/build/api/extractors/index.js +0 -36
- package/build/api/extractors/typescript.js +0 -14
- package/build/api/formats/formatterWrapper.js +0 -45
- package/build/api/formats/index.js +0 -57
- package/build/api/generateMessageId.js +0 -12
- package/build/api/help.js +0 -42
- package/build/api/index.js +0 -58
- package/build/api/pseudoLocalize.js +0 -60
- package/build/api/rethrownError.js +0 -14
- package/build/api/stats.js +0 -40
- package/build/api/types.js +0 -5
- package/build/api/utils.js +0 -117
- package/build/extract-experimental/buildExternalizeFilter.js +0 -39
- package/build/extract-experimental/bundleSource.js +0 -69
- package/build/extract-experimental/constants.js +0 -10
- package/build/extract-experimental/getEntryPoints.js +0 -14
- package/build/extract-experimental/getExperimentalCatalogs.js +0 -28
- package/build/extract-experimental/resolveCatalogPath.js +0 -23
- package/build/extract-experimental/resolveTemplatePath.js +0 -17
- package/build/extract-experimental/writeCatalogs.js +0 -59
- package/build/lingui-compile.js +0 -156
- package/build/lingui-extract-experimental.js +0 -103
- package/build/lingui-extract-template.js +0 -46
- package/build/lingui-extract.js +0 -143
- package/build/lingui.js +0 -6
- package/build/services/translationIO.js +0 -266
- package/build/tests.js +0 -133
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
var _a;
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const conf_1 = require("@lingui/conf");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const formats_1 = require("./api/formats");
|
|
12
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
13
|
+
const extractFromFiles_1 = require("./api/catalog/extractFromFiles");
|
|
14
|
+
const utils_1 = require("./api/utils");
|
|
15
|
+
const bundleSource_1 = require("./extract-experimental/bundleSource");
|
|
16
|
+
const writeCatalogs_1 = require("./extract-experimental/writeCatalogs");
|
|
17
|
+
const getEntryPoints_1 = require("./extract-experimental/getEntryPoints");
|
|
18
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
19
|
+
async function command(linguiConfig, options) {
|
|
20
|
+
var _a;
|
|
21
|
+
options.verbose && console.log("Extracting messages from source files…");
|
|
22
|
+
const config = (_a = linguiConfig.experimental) === null || _a === void 0 ? void 0 : _a.extractor;
|
|
23
|
+
if (!config) {
|
|
24
|
+
throw new Error("The configuration for experimental extractor is empty. Please read the docs.");
|
|
25
|
+
}
|
|
26
|
+
console.log(chalk_1.default.yellow([
|
|
27
|
+
"You have using an experimental feature",
|
|
28
|
+
"Experimental features are not covered by semver, and may cause unexpected or broken application behavior." +
|
|
29
|
+
" Use at your own risk.",
|
|
30
|
+
"",
|
|
31
|
+
].join("\n")));
|
|
32
|
+
const tempDir = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "js-lingui-extract-"));
|
|
33
|
+
await promises_1.default.rm(tempDir, { recursive: true, force: true });
|
|
34
|
+
const bundleResult = await (0, bundleSource_1.bundleSource)(config, (0, getEntryPoints_1.getEntryPoints)(config.entries), tempDir, linguiConfig.rootDir);
|
|
35
|
+
const stats = [];
|
|
36
|
+
let commandSuccess = true;
|
|
37
|
+
const format = await (0, formats_1.getFormat)(linguiConfig.format, linguiConfig.formatOptions, linguiConfig.sourceLocale);
|
|
38
|
+
for (const outFile of Object.keys(bundleResult.metafile.outputs)) {
|
|
39
|
+
const messages = await (0, extractFromFiles_1.extractFromFiles)([outFile], linguiConfig);
|
|
40
|
+
const { entryPoint } = bundleResult.metafile.outputs[outFile];
|
|
41
|
+
let output;
|
|
42
|
+
if (!messages) {
|
|
43
|
+
commandSuccess = false;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (options.template) {
|
|
47
|
+
output = (await (0, writeCatalogs_1.writeTemplate)({
|
|
48
|
+
linguiConfig,
|
|
49
|
+
clean: options.clean,
|
|
50
|
+
format,
|
|
51
|
+
messages,
|
|
52
|
+
entryPoint,
|
|
53
|
+
outputPattern: config.output,
|
|
54
|
+
})).statMessage;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
output = (await (0, writeCatalogs_1.writeCatalogs)({
|
|
58
|
+
locales: options.locales || linguiConfig.locales,
|
|
59
|
+
linguiConfig,
|
|
60
|
+
clean: options.clean,
|
|
61
|
+
format,
|
|
62
|
+
messages,
|
|
63
|
+
entryPoint,
|
|
64
|
+
overwrite: options.overwrite,
|
|
65
|
+
outputPattern: config.output,
|
|
66
|
+
})).statMessage;
|
|
67
|
+
}
|
|
68
|
+
stats.push({
|
|
69
|
+
entry: (0, utils_1.normalizeSlashes)(path_1.default.relative(linguiConfig.rootDir, entryPoint)),
|
|
70
|
+
content: output,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// cleanup temp directory
|
|
74
|
+
await promises_1.default.rm(tempDir, { recursive: true, force: true });
|
|
75
|
+
stats.forEach(({ entry, content }) => {
|
|
76
|
+
console.log([`Catalog statistics for ${entry}:`, content, ""].join("\n"));
|
|
77
|
+
});
|
|
78
|
+
return commandSuccess;
|
|
79
|
+
}
|
|
80
|
+
exports.default = command;
|
|
81
|
+
if (require.main === module) {
|
|
82
|
+
commander_1.program
|
|
83
|
+
.option("--config <path>", "Path to the config file")
|
|
84
|
+
.option("--template", "Extract to template")
|
|
85
|
+
.option("--overwrite", "Overwrite translations for source locale")
|
|
86
|
+
.option("--clean", "Remove obsolete translations")
|
|
87
|
+
.option("--locale <locale, [...]>", "Only extract the specified locales")
|
|
88
|
+
.option("--verbose", "Verbose output")
|
|
89
|
+
.parse(process.argv);
|
|
90
|
+
const options = commander_1.program.opts();
|
|
91
|
+
const config = (0, conf_1.getConfig)({
|
|
92
|
+
configPath: options.config,
|
|
93
|
+
});
|
|
94
|
+
const result = command(config, {
|
|
95
|
+
verbose: options.verbose || false,
|
|
96
|
+
template: options.template,
|
|
97
|
+
locales: (_a = options.locale) === null || _a === void 0 ? void 0 : _a.split(","),
|
|
98
|
+
overwrite: options.overwrite,
|
|
99
|
+
clean: options.clean,
|
|
100
|
+
}).then(() => {
|
|
101
|
+
if (!result)
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { LinguiConfigNormalized } from "@lingui/conf";
|
|
2
|
+
export type CliExtractTemplateOptions = {
|
|
3
|
+
verbose: boolean;
|
|
4
|
+
files?: string[];
|
|
5
|
+
};
|
|
6
|
+
export default function command(config: LinguiConfigNormalized, options: Partial<CliExtractTemplateOptions>): Promise<boolean>;
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const conf_1 = require("@lingui/conf");
|
|
9
|
+
const api_1 = require("./api");
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const utils_1 = require("./api/utils");
|
|
12
|
+
async function command(config, options) {
|
|
13
|
+
options.verbose && console.log("Extracting messages from source files…");
|
|
14
|
+
const catalogs = await (0, api_1.getCatalogs)(config);
|
|
15
|
+
const catalogStats = {};
|
|
16
|
+
let commandSuccess = true;
|
|
17
|
+
await Promise.all(catalogs.map(async (catalog) => {
|
|
18
|
+
const result = await catalog.makeTemplate(Object.assign(Object.assign({}, options), { orderBy: config.orderBy }));
|
|
19
|
+
if (result) {
|
|
20
|
+
catalogStats[(0, utils_1.normalizeSlashes)(path_1.default.relative(config.rootDir, catalog.templateFile))] = Object.keys(result).length;
|
|
21
|
+
}
|
|
22
|
+
commandSuccess && (commandSuccess = Boolean(result));
|
|
23
|
+
}));
|
|
24
|
+
Object.entries(catalogStats).forEach(([key, value]) => {
|
|
25
|
+
console.log(`Catalog statistics for ${chalk_1.default.bold(key)}: ${chalk_1.default.green(value)} messages`);
|
|
26
|
+
console.log();
|
|
27
|
+
});
|
|
28
|
+
return commandSuccess;
|
|
29
|
+
}
|
|
30
|
+
exports.default = command;
|
|
31
|
+
if (require.main === module) {
|
|
32
|
+
commander_1.program
|
|
33
|
+
.option("--config <path>", "Path to the config file")
|
|
34
|
+
.option("--verbose", "Verbose output")
|
|
35
|
+
.parse(process.argv);
|
|
36
|
+
const options = commander_1.program.opts();
|
|
37
|
+
const config = (0, conf_1.getConfig)({
|
|
38
|
+
configPath: options.config,
|
|
39
|
+
});
|
|
40
|
+
const result = command(config, {
|
|
41
|
+
verbose: options.verbose || false,
|
|
42
|
+
}).then(() => {
|
|
43
|
+
if (!result)
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { LinguiConfigNormalized } from "@lingui/conf";
|
|
2
|
+
export type CliExtractOptions = {
|
|
3
|
+
verbose: boolean;
|
|
4
|
+
files?: string[];
|
|
5
|
+
clean: boolean;
|
|
6
|
+
overwrite: boolean;
|
|
7
|
+
locale: string;
|
|
8
|
+
prevFormat: string | null;
|
|
9
|
+
watch?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export default function command(config: LinguiConfigNormalized, options: Partial<CliExtractOptions>): Promise<boolean>;
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
7
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const conf_1 = require("@lingui/conf");
|
|
11
|
+
const api_1 = require("./api");
|
|
12
|
+
const stats_1 = require("./api/stats");
|
|
13
|
+
const help_1 = require("./api/help");
|
|
14
|
+
const ora_1 = __importDefault(require("ora"));
|
|
15
|
+
const utils_1 = require("./api/utils");
|
|
16
|
+
async function command(config, options) {
|
|
17
|
+
options.verbose && console.log("Extracting messages from source files…");
|
|
18
|
+
const catalogs = await (0, api_1.getCatalogs)(config);
|
|
19
|
+
const catalogStats = {};
|
|
20
|
+
let commandSuccess = true;
|
|
21
|
+
const spinner = (0, ora_1.default)().start();
|
|
22
|
+
for (let catalog of catalogs) {
|
|
23
|
+
const result = await catalog.make(Object.assign(Object.assign({}, options), { orderBy: config.orderBy }));
|
|
24
|
+
catalogStats[(0, utils_1.normalizeSlashes)(path_1.default.relative(config.rootDir, catalog.path))] = result || {};
|
|
25
|
+
commandSuccess && (commandSuccess = Boolean(result));
|
|
26
|
+
}
|
|
27
|
+
if (commandSuccess) {
|
|
28
|
+
spinner.succeed();
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
spinner.fail();
|
|
32
|
+
}
|
|
33
|
+
Object.entries(catalogStats).forEach(([key, value]) => {
|
|
34
|
+
console.log(`Catalog statistics for ${key}: `);
|
|
35
|
+
console.log((0, stats_1.printStats)(config, value).toString());
|
|
36
|
+
console.log();
|
|
37
|
+
});
|
|
38
|
+
if (!options.watch) {
|
|
39
|
+
console.log(`(use "${chalk_1.default.yellow((0, help_1.helpRun)("extract"))}" to update catalogs with new messages)`);
|
|
40
|
+
console.log(`(use "${chalk_1.default.yellow((0, help_1.helpRun)("compile"))}" to compile catalogs for production)`);
|
|
41
|
+
}
|
|
42
|
+
// If service key is present in configuration, synchronize with cloud translation platform
|
|
43
|
+
if (typeof config.service === "object" &&
|
|
44
|
+
config.service.name &&
|
|
45
|
+
config.service.name.length) {
|
|
46
|
+
const moduleName = config.service.name.charAt(0).toLowerCase() + config.service.name.slice(1);
|
|
47
|
+
await import(`./services/${moduleName}`)
|
|
48
|
+
.then((module) => module.default(config, options))
|
|
49
|
+
.catch((err) => console.error(`Can't load service module ${moduleName}`, err));
|
|
50
|
+
}
|
|
51
|
+
return commandSuccess;
|
|
52
|
+
}
|
|
53
|
+
exports.default = command;
|
|
54
|
+
if (require.main === module) {
|
|
55
|
+
commander_1.program
|
|
56
|
+
.option("--config <path>", "Path to the config file")
|
|
57
|
+
.option("--locale <locale>", "Only extract the specified locale")
|
|
58
|
+
.option("--overwrite", "Overwrite translations for source locale")
|
|
59
|
+
.option("--clean", "Remove obsolete translations")
|
|
60
|
+
.option("--debounce <delay>", "Debounces extraction for given amount of milliseconds")
|
|
61
|
+
.option("--verbose", "Verbose output")
|
|
62
|
+
.option("--convert-from <format>", "Convert from previous format of message catalogs")
|
|
63
|
+
.option("--watch", "Enables Watch Mode")
|
|
64
|
+
.parse(process.argv);
|
|
65
|
+
const options = commander_1.program.opts();
|
|
66
|
+
const config = (0, conf_1.getConfig)({
|
|
67
|
+
configPath: options.config,
|
|
68
|
+
});
|
|
69
|
+
let hasErrors = false;
|
|
70
|
+
const prevFormat = options.convertFrom;
|
|
71
|
+
if (prevFormat && config.format === prevFormat) {
|
|
72
|
+
hasErrors = true;
|
|
73
|
+
console.error("Trying to migrate message catalog to the same format");
|
|
74
|
+
console.error(`Set ${chalk_1.default.bold("new")} format in LinguiJS configuration\n` +
|
|
75
|
+
` and ${chalk_1.default.bold("previous")} format using --convert-from option.`);
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(`Example: Convert from lingui format to minimal`);
|
|
78
|
+
console.log(chalk_1.default.yellow((0, help_1.helpRun)(`extract --convert-from lingui`)));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
if (options.locale && !config.locales.includes(options.locale)) {
|
|
82
|
+
hasErrors = true;
|
|
83
|
+
console.error(`Locale ${chalk_1.default.bold(options.locale)} does not exist.`);
|
|
84
|
+
console.error();
|
|
85
|
+
}
|
|
86
|
+
if (hasErrors)
|
|
87
|
+
process.exit(1);
|
|
88
|
+
const extract = (filePath) => {
|
|
89
|
+
return command(config, {
|
|
90
|
+
verbose: options.watch || options.verbose || false,
|
|
91
|
+
clean: options.watch ? false : options.clean || false,
|
|
92
|
+
overwrite: options.watch || options.overwrite || false,
|
|
93
|
+
locale: options.locale,
|
|
94
|
+
watch: options.watch || false,
|
|
95
|
+
files: (filePath === null || filePath === void 0 ? void 0 : filePath.length) ? filePath : undefined,
|
|
96
|
+
prevFormat,
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const changedPaths = new Set();
|
|
100
|
+
let debounceTimer;
|
|
101
|
+
let previousExtract = Promise.resolve(true);
|
|
102
|
+
const dispatchExtract = (filePath) => {
|
|
103
|
+
// Skip debouncing if not enabled but still chain them so no racing issue
|
|
104
|
+
// on deleting the tmp folder.
|
|
105
|
+
if (!options.debounce) {
|
|
106
|
+
previousExtract = previousExtract.then(() => extract(filePath));
|
|
107
|
+
return previousExtract;
|
|
108
|
+
}
|
|
109
|
+
filePath === null || filePath === void 0 ? void 0 : filePath.forEach((path) => changedPaths.add(path));
|
|
110
|
+
// CLear the previous timer if there is any, and schedule the next
|
|
111
|
+
debounceTimer && clearTimeout(debounceTimer);
|
|
112
|
+
debounceTimer = setTimeout(async () => {
|
|
113
|
+
const filePath = [...changedPaths];
|
|
114
|
+
changedPaths.clear();
|
|
115
|
+
await extract(filePath);
|
|
116
|
+
}, options.debounce);
|
|
117
|
+
};
|
|
118
|
+
// Check if Watch Mode is enabled
|
|
119
|
+
if (options.watch) {
|
|
120
|
+
console.info(chalk_1.default.bold("Initializing Watch Mode..."));
|
|
121
|
+
(async function initWatch() {
|
|
122
|
+
const catalogs = await (0, api_1.getCatalogs)(config);
|
|
123
|
+
let paths = [];
|
|
124
|
+
let ignored = [];
|
|
125
|
+
catalogs.forEach((catalog) => {
|
|
126
|
+
paths.push(...catalog.include);
|
|
127
|
+
ignored.push(...catalog.exclude);
|
|
128
|
+
});
|
|
129
|
+
const watcher = chokidar_1.default.watch(paths, {
|
|
130
|
+
ignored: ["/(^|[/\\])../", ...ignored],
|
|
131
|
+
persistent: true,
|
|
132
|
+
});
|
|
133
|
+
const onReady = () => {
|
|
134
|
+
console.info(chalk_1.default.green.bold("Watcher is ready!"));
|
|
135
|
+
watcher
|
|
136
|
+
.on("add", (path) => dispatchExtract([path]))
|
|
137
|
+
.on("change", (path) => dispatchExtract([path]));
|
|
138
|
+
};
|
|
139
|
+
watcher.on("ready", () => onReady());
|
|
140
|
+
})();
|
|
141
|
+
}
|
|
142
|
+
else if (commander_1.program.args) {
|
|
143
|
+
// this behaviour occurs when we extract files by his name
|
|
144
|
+
// for ex: lingui extract src/app, this will extract only files included in src/app
|
|
145
|
+
extract(commander_1.program.args).then((result) => {
|
|
146
|
+
if (!result)
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
extract().then((result) => {
|
|
152
|
+
if (!result)
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
package/dist/lingui.d.ts
ADDED
package/dist/lingui.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const packageJson = JSON.parse((0, node_fs_1.readFileSync)("../package.json", "utf8"));
|
|
7
|
+
commander_1.program
|
|
8
|
+
.version(packageJson.version)
|
|
9
|
+
.command("extract [files...]", "Extracts messages from source files")
|
|
10
|
+
.command("extract-experimental", "Extracts messages from source files traversing dependency tree")
|
|
11
|
+
.command("extract-template", "Extracts messages from source files to a .pot template")
|
|
12
|
+
.command("compile", "Compile message catalogs")
|
|
13
|
+
.parse(process.argv);
|
|
@@ -0,0 +1,264 @@
|
|
|
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
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const pofile_1 = __importDefault(require("pofile"));
|
|
9
|
+
const https_1 = __importDefault(require("https"));
|
|
10
|
+
const glob_1 = __importDefault(require("glob"));
|
|
11
|
+
const date_fns_1 = require("date-fns");
|
|
12
|
+
const getCreateHeaders = (language) => ({
|
|
13
|
+
"POT-Creation-Date": (0, date_fns_1.format)(new Date(), "yyyy-MM-dd HH:mmxxxx"),
|
|
14
|
+
"MIME-Version": "1.0",
|
|
15
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
16
|
+
"Content-Transfer-Encoding": "8bit",
|
|
17
|
+
"X-Generator": "@lingui/cli",
|
|
18
|
+
Language: language,
|
|
19
|
+
});
|
|
20
|
+
const getTargetLocales = (config) => {
|
|
21
|
+
const sourceLocale = config.sourceLocale || "en";
|
|
22
|
+
const pseudoLocale = config.pseudoLocale || "pseudo";
|
|
23
|
+
return config.locales.filter((value) => value != sourceLocale && value != pseudoLocale);
|
|
24
|
+
};
|
|
25
|
+
// Main sync method, call "Init" or "Sync" depending on the project context
|
|
26
|
+
function syncProcess(config, options) {
|
|
27
|
+
if (config.format != "po") {
|
|
28
|
+
console.error(`\n----------\nTranslation.io service is only compatible with the "po" format. Please update your Lingui configuration accordingly.\n----------`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const successCallback = (project) => {
|
|
32
|
+
console.log(`\n----------\nProject successfully synchronized. Please use this URL to translate: ${project.url}\n----------`);
|
|
33
|
+
};
|
|
34
|
+
const failCallback = (errors) => {
|
|
35
|
+
console.error(`\n----------\nSynchronization with Translation.io failed: ${errors.join(", ")}\n----------`);
|
|
36
|
+
};
|
|
37
|
+
init(config, options, successCallback, (errors) => {
|
|
38
|
+
if (errors.length &&
|
|
39
|
+
errors[0] === "This project has already been initialized.") {
|
|
40
|
+
sync(config, options, successCallback, failCallback);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
failCallback(errors);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
exports.default = syncProcess;
|
|
48
|
+
// Initialize project with source and existing translations (only first time!)
|
|
49
|
+
// Cf. https://translation.io/docs/create-library#initialization
|
|
50
|
+
function init(config, options, successCallback, failCallback) {
|
|
51
|
+
const sourceLocale = config.sourceLocale || "en";
|
|
52
|
+
const targetLocales = getTargetLocales(config);
|
|
53
|
+
const paths = poPathsPerLocale(config);
|
|
54
|
+
const segments = {};
|
|
55
|
+
targetLocales.forEach((targetLocale) => {
|
|
56
|
+
segments[targetLocale] = [];
|
|
57
|
+
});
|
|
58
|
+
// Create segments from source locale PO items
|
|
59
|
+
paths[sourceLocale].forEach((path) => {
|
|
60
|
+
const raw = fs_1.default.readFileSync(path).toString();
|
|
61
|
+
const po = pofile_1.default.parse(raw);
|
|
62
|
+
po.items
|
|
63
|
+
.filter((item) => !item["obsolete"])
|
|
64
|
+
.forEach((item) => {
|
|
65
|
+
targetLocales.forEach((targetLocale) => {
|
|
66
|
+
const newSegment = createSegmentFromPoItem(item);
|
|
67
|
+
segments[targetLocale].push(newSegment);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
// Add translations to segments from target locale PO items
|
|
72
|
+
targetLocales.forEach((targetLocale) => {
|
|
73
|
+
paths[targetLocale].forEach((path) => {
|
|
74
|
+
const raw = fs_1.default.readFileSync(path).toString();
|
|
75
|
+
const po = pofile_1.default.parse(raw);
|
|
76
|
+
po.items
|
|
77
|
+
.filter((item) => !item["obsolete"])
|
|
78
|
+
.forEach((item, index) => {
|
|
79
|
+
segments[targetLocale][index].target = item.msgstr[0];
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
const request = {
|
|
84
|
+
client: "lingui",
|
|
85
|
+
version: require("@lingui/core/package.json").version,
|
|
86
|
+
source_language: sourceLocale,
|
|
87
|
+
target_languages: targetLocales,
|
|
88
|
+
segments: segments,
|
|
89
|
+
};
|
|
90
|
+
postTio("init", request, config.service.apiKey, (response) => {
|
|
91
|
+
if (response.errors) {
|
|
92
|
+
failCallback(response.errors);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
saveSegmentsToTargetPos(config, paths, response.segments);
|
|
96
|
+
successCallback(response.project);
|
|
97
|
+
}
|
|
98
|
+
}, (error) => {
|
|
99
|
+
console.error(`\n----------\nSynchronization with Translation.io failed: ${error}\n----------`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// Send all source text from PO to Translation.io and create new PO based on received translations
|
|
103
|
+
// Cf. https://translation.io/docs/create-library#synchronization
|
|
104
|
+
function sync(config, options, successCallback, failCallback) {
|
|
105
|
+
const sourceLocale = config.sourceLocale || "en";
|
|
106
|
+
const targetLocales = getTargetLocales(config);
|
|
107
|
+
const paths = poPathsPerLocale(config);
|
|
108
|
+
const segments = [];
|
|
109
|
+
// Create segments with correct source
|
|
110
|
+
paths[sourceLocale].forEach((path) => {
|
|
111
|
+
const raw = fs_1.default.readFileSync(path).toString();
|
|
112
|
+
const po = pofile_1.default.parse(raw);
|
|
113
|
+
po.items
|
|
114
|
+
.filter((item) => !item["obsolete"])
|
|
115
|
+
.forEach((item) => {
|
|
116
|
+
const newSegment = createSegmentFromPoItem(item);
|
|
117
|
+
segments.push(newSegment);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
const request = {
|
|
121
|
+
client: "lingui",
|
|
122
|
+
version: require("@lingui/core/package.json").version,
|
|
123
|
+
source_language: sourceLocale,
|
|
124
|
+
target_languages: targetLocales,
|
|
125
|
+
segments: segments,
|
|
126
|
+
// Sync and then remove unused segments (not present in the local application) from Translation.io
|
|
127
|
+
purge: Boolean(options.clean),
|
|
128
|
+
};
|
|
129
|
+
postTio("sync", request, config.service.apiKey, (response) => {
|
|
130
|
+
if (response.errors) {
|
|
131
|
+
failCallback(response.errors);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
saveSegmentsToTargetPos(config, paths, response.segments);
|
|
135
|
+
successCallback(response.project);
|
|
136
|
+
}
|
|
137
|
+
}, (error) => {
|
|
138
|
+
console.error(`\n----------\nSynchronization with Translation.io failed: ${error}\n----------`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function createSegmentFromPoItem(item) {
|
|
142
|
+
const itemHasId = item.msgid != item.msgstr[0] && item.msgstr[0].length;
|
|
143
|
+
const segment = {
|
|
144
|
+
type: "source",
|
|
145
|
+
source: itemHasId ? item.msgstr[0] : item.msgid,
|
|
146
|
+
context: "",
|
|
147
|
+
references: [],
|
|
148
|
+
comment: "",
|
|
149
|
+
};
|
|
150
|
+
if (itemHasId) {
|
|
151
|
+
segment.context = item.msgid;
|
|
152
|
+
}
|
|
153
|
+
if (item.references.length) {
|
|
154
|
+
segment.references = item.references;
|
|
155
|
+
}
|
|
156
|
+
if (item.extractedComments.length) {
|
|
157
|
+
segment.comment = item.extractedComments.join(" | ");
|
|
158
|
+
}
|
|
159
|
+
return segment;
|
|
160
|
+
}
|
|
161
|
+
function createPoItemFromSegment(segment) {
|
|
162
|
+
const item = new pofile_1.default.Item();
|
|
163
|
+
item.msgid = segment.context ? segment.context : segment.source;
|
|
164
|
+
item.msgstr = [segment.target];
|
|
165
|
+
item.references =
|
|
166
|
+
segment.references && segment.references.length ? segment.references : [];
|
|
167
|
+
item.extractedComments = segment.comment ? segment.comment.split(" | ") : [];
|
|
168
|
+
return item;
|
|
169
|
+
}
|
|
170
|
+
function saveSegmentsToTargetPos(config, paths, segmentsPerLocale) {
|
|
171
|
+
Object.keys(segmentsPerLocale).forEach((targetLocale) => {
|
|
172
|
+
// Remove existing target POs and JS for this target locale
|
|
173
|
+
paths[targetLocale].forEach((path) => {
|
|
174
|
+
const jsPath = path.replace(/\.po?$/, "") + ".js";
|
|
175
|
+
const dirPath = (0, path_1.dirname)(path);
|
|
176
|
+
// Remove PO, JS and empty dir
|
|
177
|
+
if (fs_1.default.existsSync(path)) {
|
|
178
|
+
fs_1.default.unlinkSync(path);
|
|
179
|
+
}
|
|
180
|
+
if (fs_1.default.existsSync(jsPath)) {
|
|
181
|
+
fs_1.default.unlinkSync(jsPath);
|
|
182
|
+
}
|
|
183
|
+
if (fs_1.default.existsSync(dirPath) && fs_1.default.readdirSync(dirPath).length === 0) {
|
|
184
|
+
fs_1.default.rmdirSync(dirPath);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// Find target path (ignoring {name})
|
|
188
|
+
const localePath = "".concat(config.catalogs[0].path
|
|
189
|
+
.replace(/{locale}/g, targetLocale)
|
|
190
|
+
.replace(/{name}/g, ""), ".po");
|
|
191
|
+
const segments = segmentsPerLocale[targetLocale];
|
|
192
|
+
const po = new pofile_1.default();
|
|
193
|
+
po.headers = getCreateHeaders(targetLocale);
|
|
194
|
+
const items = [];
|
|
195
|
+
segments.forEach((segment) => {
|
|
196
|
+
const item = createPoItemFromSegment(segment);
|
|
197
|
+
items.push(item);
|
|
198
|
+
});
|
|
199
|
+
// Sort items by messageId
|
|
200
|
+
po.items = items.sort((a, b) => {
|
|
201
|
+
if (a.msgid < b.msgid) {
|
|
202
|
+
return -1;
|
|
203
|
+
}
|
|
204
|
+
if (a.msgid > b.msgid) {
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
return 0;
|
|
208
|
+
});
|
|
209
|
+
// Check that localePath directory exists and save PO file
|
|
210
|
+
fs_1.default.promises.mkdir((0, path_1.dirname)(localePath), { recursive: true }).then(() => {
|
|
211
|
+
po.save(localePath, (err) => {
|
|
212
|
+
if (err) {
|
|
213
|
+
console.error("Error while saving target PO files:");
|
|
214
|
+
console.error(err);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function poPathsPerLocale(config) {
|
|
222
|
+
const paths = {};
|
|
223
|
+
config.locales.forEach((locale) => {
|
|
224
|
+
paths[locale] = [];
|
|
225
|
+
config.catalogs.forEach((catalog) => {
|
|
226
|
+
const path = "".concat(catalog.path.replace(/{locale}/g, locale).replace(/{name}/g, "*"), ".po");
|
|
227
|
+
// If {name} is present (replaced by *), list all the existing POs
|
|
228
|
+
if (path.includes("*")) {
|
|
229
|
+
paths[locale] = paths[locale].concat(glob_1.default.sync(path));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
paths[locale].push(path);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
return paths;
|
|
237
|
+
}
|
|
238
|
+
function postTio(action, request, apiKey, successCallback, failCallback) {
|
|
239
|
+
const jsonRequest = JSON.stringify(request);
|
|
240
|
+
const options = {
|
|
241
|
+
hostname: "translation.io",
|
|
242
|
+
path: `/api/v1/segments/${action}.json?api_key=${apiKey}`,
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: {
|
|
245
|
+
"Content-Type": "application/json",
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
const req = https_1.default.request(options, (res) => {
|
|
249
|
+
res.setEncoding("utf8");
|
|
250
|
+
let body = "";
|
|
251
|
+
res.on("data", (chunk) => {
|
|
252
|
+
body = body.concat(chunk);
|
|
253
|
+
});
|
|
254
|
+
res.on("end", () => {
|
|
255
|
+
const response = JSON.parse(body);
|
|
256
|
+
successCallback(response);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
req.on("error", (e) => {
|
|
260
|
+
failCallback(e);
|
|
261
|
+
});
|
|
262
|
+
req.write(jsonRequest);
|
|
263
|
+
req.end();
|
|
264
|
+
}
|