@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.
Files changed (100) hide show
  1. package/dist/api/catalog/extractFromFiles.d.ts +5 -0
  2. package/dist/api/catalog/extractFromFiles.js +47 -0
  3. package/dist/api/catalog/getCatalogs.d.ts +14 -0
  4. package/dist/api/catalog/getCatalogs.js +129 -0
  5. package/dist/api/catalog/getTranslationsForCatalog.d.ts +14 -0
  6. package/dist/api/catalog/getTranslationsForCatalog.js +72 -0
  7. package/dist/api/catalog/mergeCatalog.d.ts +3 -0
  8. package/dist/api/catalog/mergeCatalog.js +55 -0
  9. package/dist/api/catalog.d.ts +66 -0
  10. package/dist/api/catalog.js +261 -0
  11. package/dist/api/compile.d.ts +19 -0
  12. package/dist/api/compile.js +88 -0
  13. package/dist/api/extractors/babel.d.ts +3 -0
  14. package/dist/api/extractors/babel.js +101 -0
  15. package/dist/api/extractors/index.d.ts +6 -0
  16. package/dist/api/extractors/index.js +38 -0
  17. package/dist/api/extractors/typescript.d.ts +3 -0
  18. package/dist/api/extractors/typescript.js +11 -0
  19. package/dist/api/formats/formatterWrapper.d.ts +10 -0
  20. package/dist/api/formats/formatterWrapper.js +43 -0
  21. package/dist/api/formats/index.d.ts +5 -0
  22. package/dist/api/formats/index.js +47 -0
  23. package/dist/api/help.d.ts +1 -0
  24. package/dist/api/help.js +40 -0
  25. package/dist/api/index.d.ts +4 -0
  26. package/dist/api/index.js +25 -0
  27. package/dist/api/pseudoLocalize.d.ts +1 -0
  28. package/dist/api/pseudoLocalize.js +56 -0
  29. package/dist/api/rethrownError.d.ts +4 -0
  30. package/dist/api/rethrownError.js +11 -0
  31. package/dist/api/stats.d.ts +6 -0
  32. package/dist/api/stats.js +41 -0
  33. package/dist/api/types.d.ts +5 -0
  34. package/dist/api/types.js +2 -0
  35. package/dist/api/utils.d.ts +22 -0
  36. package/dist/api/utils.js +119 -0
  37. package/dist/extract-experimental/buildExternalizeFilter.d.ts +13 -0
  38. package/dist/extract-experimental/buildExternalizeFilter.js +38 -0
  39. package/dist/extract-experimental/bundleSource.d.ts +2 -0
  40. package/dist/extract-experimental/bundleSource.js +78 -0
  41. package/dist/extract-experimental/constants.d.ts +2 -0
  42. package/dist/extract-experimental/constants.js +5 -0
  43. package/dist/extract-experimental/getEntryPoints.d.ts +1 -0
  44. package/dist/extract-experimental/getEntryPoints.js +12 -0
  45. package/dist/extract-experimental/getExperimentalCatalogs.d.ts +3 -0
  46. package/dist/extract-experimental/getExperimentalCatalogs.js +26 -0
  47. package/dist/extract-experimental/resolveCatalogPath.d.ts +2 -0
  48. package/dist/extract-experimental/resolveCatalogPath.js +23 -0
  49. package/dist/extract-experimental/resolveTemplatePath.d.ts +1 -0
  50. package/dist/extract-experimental/resolveTemplatePath.js +16 -0
  51. package/dist/extract-experimental/writeCatalogs.d.ts +21 -0
  52. package/dist/extract-experimental/writeCatalogs.js +41 -0
  53. package/dist/lingui-compile.d.ts +9 -0
  54. package/dist/lingui-compile.js +170 -0
  55. package/dist/lingui-extract-experimental.d.ts +10 -0
  56. package/dist/lingui-extract-experimental.js +104 -0
  57. package/dist/lingui-extract-template.d.ts +6 -0
  58. package/dist/lingui-extract-template.js +46 -0
  59. package/dist/lingui-extract.d.ts +11 -0
  60. package/dist/lingui-extract.js +156 -0
  61. package/dist/lingui.d.ts +2 -0
  62. package/dist/lingui.js +13 -0
  63. package/dist/services/translationIO.d.ts +3 -0
  64. package/dist/services/translationIO.js +264 -0
  65. package/package.json +29 -14
  66. package/build/LICENSE +0 -21
  67. package/build/api/catalog/extractFromFiles.js +0 -45
  68. package/build/api/catalog/getCatalogs.js +0 -138
  69. package/build/api/catalog/getTranslationsForCatalog.js +0 -77
  70. package/build/api/catalog/mergeCatalog.js +0 -44
  71. package/build/api/catalog.js +0 -245
  72. package/build/api/compile.js +0 -72
  73. package/build/api/extractors/babel.js +0 -92
  74. package/build/api/extractors/index.js +0 -36
  75. package/build/api/extractors/typescript.js +0 -14
  76. package/build/api/formats/formatterWrapper.js +0 -45
  77. package/build/api/formats/index.js +0 -57
  78. package/build/api/generateMessageId.js +0 -12
  79. package/build/api/help.js +0 -42
  80. package/build/api/index.js +0 -58
  81. package/build/api/pseudoLocalize.js +0 -60
  82. package/build/api/rethrownError.js +0 -14
  83. package/build/api/stats.js +0 -40
  84. package/build/api/types.js +0 -5
  85. package/build/api/utils.js +0 -117
  86. package/build/extract-experimental/buildExternalizeFilter.js +0 -39
  87. package/build/extract-experimental/bundleSource.js +0 -69
  88. package/build/extract-experimental/constants.js +0 -10
  89. package/build/extract-experimental/getEntryPoints.js +0 -14
  90. package/build/extract-experimental/getExperimentalCatalogs.js +0 -28
  91. package/build/extract-experimental/resolveCatalogPath.js +0 -23
  92. package/build/extract-experimental/resolveTemplatePath.js +0 -17
  93. package/build/extract-experimental/writeCatalogs.js +0 -59
  94. package/build/lingui-compile.js +0 -156
  95. package/build/lingui-extract-experimental.js +0 -103
  96. package/build/lingui-extract-template.js +0 -46
  97. package/build/lingui-extract.js +0 -143
  98. package/build/lingui.js +0 -6
  99. package/build/services/translationIO.js +0 -266
  100. 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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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,3 @@
1
+ import { LinguiConfigNormalized } from "@lingui/conf";
2
+ import { CliExtractOptions } from "../lingui-extract";
3
+ export default function syncProcess(config: LinguiConfigNormalized, options: CliExtractOptions): void;
@@ -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
+ }