@lingui/cli 5.8.0 → 5.9.0
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/mergeCatalog.js +2 -1
- package/dist/lingui-extract.js +4 -2
- package/dist/services/translationIO/segment-converters.d.ts +4 -0
- package/dist/services/translationIO/segment-converters.js +84 -0
- package/dist/services/translationIO/translationio-api.d.ts +48 -0
- package/dist/services/translationIO/translationio-api.js +42 -0
- package/dist/services/translationIO.d.ts +30 -1
- package/dist/services/translationIO.js +98 -250
- package/package.json +9 -8
|
@@ -32,7 +32,8 @@ function mergeCatalog(prevCatalog, nextCatalog, forSourceLocale, options) {
|
|
|
32
32
|
? nextCatalog[key].message || key
|
|
33
33
|
: prevCatalog[key].translation;
|
|
34
34
|
const _a = nextCatalog[key], { obsolete } = _a, rest = __rest(_a, ["obsolete"]);
|
|
35
|
-
|
|
35
|
+
const { extra } = prevCatalog[key];
|
|
36
|
+
return [key, Object.assign(Object.assign(Object.assign({}, extra), rest), { translation })];
|
|
36
37
|
}));
|
|
37
38
|
// Mark all remaining translations as obsolete
|
|
38
39
|
// Only if *options.files* is not provided
|
package/dist/lingui-extract.js
CHANGED
|
@@ -34,11 +34,13 @@ async function command(config, options) {
|
|
|
34
34
|
workerPool = (0, extractWorkerPool_1.createExtractWorkerPool)(options.workersOptions);
|
|
35
35
|
}
|
|
36
36
|
spinner.start();
|
|
37
|
+
let extractionResult;
|
|
37
38
|
try {
|
|
38
|
-
await Promise.all(catalogs.map(async (catalog) => {
|
|
39
|
+
extractionResult = await Promise.all(catalogs.map(async (catalog) => {
|
|
39
40
|
const result = await catalog.make(Object.assign(Object.assign({}, options), { orderBy: config.orderBy, workerPool }));
|
|
40
41
|
catalogStats[(0, normalize_path_1.default)(path_1.default.relative(config.rootDir, catalog.path))] = result || {};
|
|
41
42
|
commandSuccess && (commandSuccess = Boolean(result));
|
|
43
|
+
return { catalog, messagesByLocale: result };
|
|
42
44
|
}));
|
|
43
45
|
}
|
|
44
46
|
finally {
|
|
@@ -70,7 +72,7 @@ async function command(config, options) {
|
|
|
70
72
|
try {
|
|
71
73
|
const module = require(`./services/${moduleName}`);
|
|
72
74
|
await module
|
|
73
|
-
.default(config, options)
|
|
75
|
+
.default(config, options, extractionResult)
|
|
74
76
|
.then(console.log)
|
|
75
77
|
.catch(console.error);
|
|
76
78
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { MessageType } from "@lingui/conf";
|
|
2
|
+
import { TranslationIoSegment } from "./translationio-api";
|
|
3
|
+
export declare function createSegmentFromLinguiItem(key: string, item: MessageType): TranslationIoSegment;
|
|
4
|
+
export declare function createLinguiItemFromSegment(segment: TranslationIoSegment): readonly [string, MessageType];
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSegmentFromLinguiItem = createSegmentFromLinguiItem;
|
|
4
|
+
exports.createLinguiItemFromSegment = createLinguiItemFromSegment;
|
|
5
|
+
const generateMessageId_1 = require("@lingui/message-utils/generateMessageId");
|
|
6
|
+
const EXPLICIT_ID_FLAG = "js-lingui-explicit-id";
|
|
7
|
+
const EXPLICIT_ID_AND_CONTEXT_FLAG = "js-lingui-explicit-id-and-context";
|
|
8
|
+
function isGeneratedId(id, message) {
|
|
9
|
+
return id === (0, generateMessageId_1.generateMessageId)(message.message, message.context);
|
|
10
|
+
}
|
|
11
|
+
const joinOrigin = (origin) => origin.join(":");
|
|
12
|
+
const splitOrigin = (origin) => {
|
|
13
|
+
const [file, line] = origin.split(":");
|
|
14
|
+
return [file, line ? Number(line) : null];
|
|
15
|
+
};
|
|
16
|
+
function createSegmentFromLinguiItem(key, item) {
|
|
17
|
+
const itemHasExplicitId = !isGeneratedId(key, item);
|
|
18
|
+
const itemHasContext = !!item.context;
|
|
19
|
+
const segment = {
|
|
20
|
+
type: "source", // No way to edit text for source language (inside code), so not using "key" here
|
|
21
|
+
source: "",
|
|
22
|
+
context: "",
|
|
23
|
+
references: [],
|
|
24
|
+
comment: "",
|
|
25
|
+
};
|
|
26
|
+
// For segment.source & segment.context, we must remain compatible with projects created/synced before Lingui V4
|
|
27
|
+
if (itemHasExplicitId) {
|
|
28
|
+
segment.source = item.message || item.translation;
|
|
29
|
+
segment.context = key;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
segment.source = item.message || item.translation;
|
|
33
|
+
if (itemHasContext) {
|
|
34
|
+
segment.context = item.context;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (item.origin) {
|
|
38
|
+
segment.references = item.origin.map(joinOrigin);
|
|
39
|
+
}
|
|
40
|
+
// Since Lingui v4, when using explicit IDs, Lingui automatically adds 'js-lingui-explicit-id' to the extractedComments array
|
|
41
|
+
const comments = [];
|
|
42
|
+
if (itemHasExplicitId) {
|
|
43
|
+
if (itemHasContext) {
|
|
44
|
+
// segment.context is already used for the explicit ID, so we need to pass the context (for translators) in segment.comment
|
|
45
|
+
comments.push(item.context, EXPLICIT_ID_AND_CONTEXT_FLAG);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
comments.push(EXPLICIT_ID_FLAG);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
segment.comment = [...comments, ...(item.comments || [])].join(" | ");
|
|
52
|
+
return segment;
|
|
53
|
+
}
|
|
54
|
+
function createLinguiItemFromSegment(segment) {
|
|
55
|
+
var _a, _b, _c;
|
|
56
|
+
const segmentHasExplicitId = (_a = segment.comment) === null || _a === void 0 ? void 0 : _a.includes(EXPLICIT_ID_FLAG);
|
|
57
|
+
const segmentHasExplicitIdAndContext = (_b = segment.comment) === null || _b === void 0 ? void 0 : _b.includes(EXPLICIT_ID_AND_CONTEXT_FLAG);
|
|
58
|
+
const item = {
|
|
59
|
+
translation: segment.target,
|
|
60
|
+
origin: ((_c = segment.references) === null || _c === void 0 ? void 0 : _c.length)
|
|
61
|
+
? segment.references.map(splitOrigin)
|
|
62
|
+
: [],
|
|
63
|
+
message: segment.source,
|
|
64
|
+
comments: [],
|
|
65
|
+
};
|
|
66
|
+
let id = null;
|
|
67
|
+
if (segmentHasExplicitId || segmentHasExplicitIdAndContext) {
|
|
68
|
+
id = segment.context;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
id = (0, generateMessageId_1.generateMessageId)(segment.source, segment.context);
|
|
72
|
+
item.context = segment.context;
|
|
73
|
+
}
|
|
74
|
+
if (segment.comment) {
|
|
75
|
+
item.comments = segment.comment.split(" | ").filter(
|
|
76
|
+
// drop flags from comments
|
|
77
|
+
(comment) => comment !== EXPLICIT_ID_AND_CONTEXT_FLAG && comment !== EXPLICIT_ID_FLAG);
|
|
78
|
+
// We recompose a target PO Item that is consistent with the source PO Item
|
|
79
|
+
if (segmentHasExplicitIdAndContext) {
|
|
80
|
+
item.context = item.comments.shift();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return [id, item];
|
|
84
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type TranslationIoSyncRequest = {
|
|
2
|
+
client: "lingui";
|
|
3
|
+
version: string;
|
|
4
|
+
source_language: string;
|
|
5
|
+
target_languages: string[];
|
|
6
|
+
segments: TranslationIoSegment[];
|
|
7
|
+
purge?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type TranslationIoInitRequest = {
|
|
10
|
+
client: "lingui";
|
|
11
|
+
version: string;
|
|
12
|
+
source_language: string;
|
|
13
|
+
target_languages: string[];
|
|
14
|
+
segments: {
|
|
15
|
+
[locale: string]: TranslationIoSegment[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type TranslationIoSegment = {
|
|
19
|
+
type: string;
|
|
20
|
+
source: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
context?: string;
|
|
23
|
+
references?: string[];
|
|
24
|
+
comment?: string;
|
|
25
|
+
};
|
|
26
|
+
export type TranslationIoProject = {
|
|
27
|
+
name: string;
|
|
28
|
+
url: string;
|
|
29
|
+
};
|
|
30
|
+
export type TranslationIoResponse = {
|
|
31
|
+
errors?: string[];
|
|
32
|
+
project?: TranslationIoProject;
|
|
33
|
+
segments?: {
|
|
34
|
+
[locale: string]: TranslationIoSegment[];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type FetchResult<T> = {
|
|
38
|
+
data: T;
|
|
39
|
+
error: undefined;
|
|
40
|
+
} | {
|
|
41
|
+
error: {
|
|
42
|
+
response: Response;
|
|
43
|
+
message: string;
|
|
44
|
+
};
|
|
45
|
+
data: undefined;
|
|
46
|
+
};
|
|
47
|
+
export declare function tioSync(request: TranslationIoSyncRequest, apiKey: string): Promise<FetchResult<TranslationIoResponse>>;
|
|
48
|
+
export declare function tioInit(request: TranslationIoInitRequest, apiKey: string): Promise<FetchResult<TranslationIoResponse>>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tioSync = tioSync;
|
|
4
|
+
exports.tioInit = tioInit;
|
|
5
|
+
// todo: need to enable strictNullChecks to support this kind of type narrowing
|
|
6
|
+
// export type TranslationIoResponse =
|
|
7
|
+
// | TranslationIoErrorResponse
|
|
8
|
+
// | TranslationProjectResponse
|
|
9
|
+
//
|
|
10
|
+
// type TranslationIoErrorResponse = {
|
|
11
|
+
// errors: string[]
|
|
12
|
+
// }
|
|
13
|
+
// type TranslationProjectResponse = {
|
|
14
|
+
// errors: null
|
|
15
|
+
// project: TranslationIoProject
|
|
16
|
+
// segments: { [locale: string]: TranslationIoSegment[] }
|
|
17
|
+
// }
|
|
18
|
+
async function post(url, request) {
|
|
19
|
+
const response = await fetch(url, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify(request),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok && response.status !== 400) {
|
|
27
|
+
return {
|
|
28
|
+
error: {
|
|
29
|
+
response,
|
|
30
|
+
message: `Request failed with ${response.status} ${response.statusText} status. Body: ${await response.text()}`,
|
|
31
|
+
},
|
|
32
|
+
data: undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { data: await response.json(), error: undefined };
|
|
36
|
+
}
|
|
37
|
+
async function tioSync(request, apiKey) {
|
|
38
|
+
return post(`https://translation.io/api/v1/segments/sync.json?api_key=${apiKey}`, request);
|
|
39
|
+
}
|
|
40
|
+
async function tioInit(request, apiKey) {
|
|
41
|
+
return post(`https://translation.io/api/v1/segments/init.json?api_key=${apiKey}`, request);
|
|
42
|
+
}
|
|
@@ -1,3 +1,32 @@
|
|
|
1
1
|
import { LinguiConfigNormalized } from "@lingui/conf";
|
|
2
2
|
import { CliExtractOptions } from "../lingui-extract";
|
|
3
|
-
|
|
3
|
+
import { TranslationIoProject, TranslationIoSegment } from "./translationIO/translationio-api";
|
|
4
|
+
import { Catalog } from "../api/catalog";
|
|
5
|
+
import { AllCatalogsType } from "../api/types";
|
|
6
|
+
type ExtractionResult = {
|
|
7
|
+
catalog: Catalog;
|
|
8
|
+
messagesByLocale: AllCatalogsType;
|
|
9
|
+
}[];
|
|
10
|
+
export default function syncProcess(config: LinguiConfigNormalized, options: CliExtractOptions, extractionResult: ExtractionResult): Promise<string>;
|
|
11
|
+
export declare function init(config: LinguiConfigNormalized, extractionResult: ExtractionResult): Promise<{
|
|
12
|
+
readonly success: false;
|
|
13
|
+
readonly errors: string[];
|
|
14
|
+
readonly project?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
readonly success: true;
|
|
17
|
+
readonly project: TranslationIoProject;
|
|
18
|
+
readonly errors?: string[];
|
|
19
|
+
}>;
|
|
20
|
+
export declare function sync(config: LinguiConfigNormalized, options: CliExtractOptions, extractionResult: ExtractionResult): Promise<{
|
|
21
|
+
readonly success: false;
|
|
22
|
+
readonly errors: string[];
|
|
23
|
+
readonly project?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
readonly success: true;
|
|
26
|
+
readonly project: TranslationIoProject;
|
|
27
|
+
readonly errors?: string[];
|
|
28
|
+
}>;
|
|
29
|
+
export declare function writeSegmentsToCatalogs(config: LinguiConfigNormalized, sourceLocale: string, extractionResult: ExtractionResult, segmentsPerLocale: {
|
|
30
|
+
[locale: string]: TranslationIoSegment[];
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
export {};
|
|
@@ -4,130 +4,103 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.default = syncProcess;
|
|
7
|
+
exports.init = init;
|
|
8
|
+
exports.sync = sync;
|
|
9
|
+
exports.writeSegmentsToCatalogs = writeSegmentsToCatalogs;
|
|
7
10
|
const fs_1 = __importDefault(require("fs"));
|
|
8
11
|
const path_1 = require("path");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const date_fns_1 = require("date-fns");
|
|
13
|
-
const EXPLICIT_ID_FLAG = "js-lingui-explicit-id";
|
|
14
|
-
const EXPLICIT_ID_AND_CONTEXT_FLAG = "js-lingui-explicit-id-and-context";
|
|
15
|
-
const getCreateHeaders = (language) => ({
|
|
16
|
-
"POT-Creation-Date": (0, date_fns_1.format)(new Date(), "yyyy-MM-dd HH:mmxxxx"),
|
|
17
|
-
"MIME-Version": "1.0",
|
|
18
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
19
|
-
"Content-Transfer-Encoding": "8bit",
|
|
20
|
-
"X-Generator": "@lingui/cli",
|
|
21
|
-
Language: language,
|
|
22
|
-
});
|
|
12
|
+
const translationio_api_1 = require("./translationIO/translationio-api");
|
|
13
|
+
const catalog_1 = require("../api/catalog");
|
|
14
|
+
const segment_converters_1 = require("./translationIO/segment-converters");
|
|
23
15
|
const getTargetLocales = (config) => {
|
|
24
16
|
const sourceLocale = config.sourceLocale || "en";
|
|
25
17
|
const pseudoLocale = config.pseudoLocale || "pseudo";
|
|
26
18
|
return config.locales.filter((value) => value != sourceLocale && value != pseudoLocale);
|
|
27
19
|
};
|
|
28
|
-
const validCatalogFormat = (config) => {
|
|
29
|
-
if (typeof config.format === "string") {
|
|
30
|
-
return config.format === "po";
|
|
31
|
-
}
|
|
32
|
-
return config.format.catalogExtension === ".po";
|
|
33
|
-
};
|
|
34
20
|
// Main sync method, call "Init" or "Sync" depending on the project context
|
|
35
|
-
async function syncProcess(config, options) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
async function syncProcess(config, options, extractionResult) {
|
|
22
|
+
const reportSuccess = (project) => {
|
|
23
|
+
return `\n----------\nProject successfully synchronized. Please use this URL to translate: ${project.url}\n----------`;
|
|
24
|
+
};
|
|
25
|
+
const reportError = (errors) => {
|
|
26
|
+
throw `\n----------\nSynchronization with Translation.io failed: ${errors.join(", ")}\n----------`;
|
|
27
|
+
};
|
|
28
|
+
const { success, project, errors } = await init(config, extractionResult);
|
|
29
|
+
if (success) {
|
|
30
|
+
return reportSuccess(project);
|
|
39
31
|
}
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (errors.length &&
|
|
49
|
-
errors[0] === "This project has already been initialized.") {
|
|
50
|
-
sync(config, options, successCallback, failCallback);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
failCallback(errors);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
});
|
|
32
|
+
if (errors[0] === "This project has already been initialized.") {
|
|
33
|
+
const { success, project, errors } = await sync(config, options, extractionResult);
|
|
34
|
+
if (success) {
|
|
35
|
+
return reportSuccess(project);
|
|
36
|
+
}
|
|
37
|
+
return reportError(errors);
|
|
38
|
+
}
|
|
39
|
+
return reportError(errors);
|
|
57
40
|
}
|
|
58
41
|
// Initialize project with source and existing translations (only first time!)
|
|
59
42
|
// Cf. https://translation.io/docs/create-library#initialization
|
|
60
|
-
function init(config,
|
|
43
|
+
async function init(config, extractionResult) {
|
|
61
44
|
const sourceLocale = config.sourceLocale || "en";
|
|
62
45
|
const targetLocales = getTargetLocales(config);
|
|
63
|
-
const paths = poPathsPerLocale(config);
|
|
64
46
|
const segments = {};
|
|
65
47
|
targetLocales.forEach((targetLocale) => {
|
|
66
48
|
segments[targetLocale] = [];
|
|
67
49
|
});
|
|
68
50
|
// Create segments from source locale PO items
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.forEach((item) => {
|
|
51
|
+
for (const { messagesByLocale } of extractionResult) {
|
|
52
|
+
const messages = messagesByLocale[sourceLocale];
|
|
53
|
+
Object.entries(messages).forEach(([key, entry]) => {
|
|
54
|
+
if (entry.obsolete)
|
|
55
|
+
return;
|
|
75
56
|
targetLocales.forEach((targetLocale) => {
|
|
76
|
-
|
|
77
|
-
segments[targetLocale].push(newSegment);
|
|
57
|
+
segments[targetLocale].push((0, segment_converters_1.createSegmentFromLinguiItem)(key, entry));
|
|
78
58
|
});
|
|
79
59
|
});
|
|
80
|
-
}
|
|
60
|
+
}
|
|
81
61
|
// Add translations to segments from target locale PO items
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
88
|
-
.
|
|
89
|
-
segments[targetLocale][index].target = item.msgstr[0];
|
|
62
|
+
for (const { messagesByLocale } of extractionResult) {
|
|
63
|
+
for (const targetLocale of targetLocales) {
|
|
64
|
+
const messages = messagesByLocale[targetLocale];
|
|
65
|
+
Object.entries(messages)
|
|
66
|
+
.filter(([, entry]) => !entry.obsolete)
|
|
67
|
+
.forEach(([, entry], index) => {
|
|
68
|
+
segments[targetLocale][index].target = entry.translation;
|
|
90
69
|
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const { data, error } = await (0, translationio_api_1.tioInit)({
|
|
94
73
|
client: "lingui",
|
|
95
74
|
version: require("@lingui/core/package.json").version,
|
|
96
75
|
source_language: sourceLocale,
|
|
97
76
|
target_languages: targetLocales,
|
|
98
77
|
segments: segments,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}, (error) => {
|
|
109
|
-
console.error(`\n----------\nSynchronization with Translation.io failed: ${error}\n----------`);
|
|
110
|
-
});
|
|
78
|
+
}, config.service.apiKey);
|
|
79
|
+
if (error) {
|
|
80
|
+
return { success: false, errors: [error.message] };
|
|
81
|
+
}
|
|
82
|
+
if (data.errors) {
|
|
83
|
+
return { success: false, errors: data.errors };
|
|
84
|
+
}
|
|
85
|
+
await writeSegmentsToCatalogs(config, sourceLocale, extractionResult, data.segments);
|
|
86
|
+
return { success: true, project: data.project };
|
|
111
87
|
}
|
|
112
88
|
// Send all source text from PO to Translation.io and create new PO based on received translations
|
|
113
89
|
// Cf. https://translation.io/docs/create-library#synchronization
|
|
114
|
-
function sync(config, options,
|
|
90
|
+
async function sync(config, options, extractionResult) {
|
|
115
91
|
const sourceLocale = config.sourceLocale || "en";
|
|
116
92
|
const targetLocales = getTargetLocales(config);
|
|
117
|
-
const paths = poPathsPerLocale(config);
|
|
118
93
|
const segments = [];
|
|
119
94
|
// Create segments with correct source
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.
|
|
126
|
-
const newSegment = createSegmentFromPoItem(item);
|
|
127
|
-
segments.push(newSegment);
|
|
95
|
+
for (const { messagesByLocale } of extractionResult) {
|
|
96
|
+
const messages = messagesByLocale[sourceLocale];
|
|
97
|
+
Object.entries(messages).forEach(([key, entry]) => {
|
|
98
|
+
if (entry.obsolete)
|
|
99
|
+
return;
|
|
100
|
+
segments.push((0, segment_converters_1.createSegmentFromLinguiItem)(key, entry));
|
|
128
101
|
});
|
|
129
|
-
}
|
|
130
|
-
const
|
|
102
|
+
}
|
|
103
|
+
const { data, error } = await (0, translationio_api_1.tioSync)({
|
|
131
104
|
client: "lingui",
|
|
132
105
|
version: require("@lingui/core/package.json").version,
|
|
133
106
|
source_language: sourceLocale,
|
|
@@ -135,172 +108,47 @@ function sync(config, options, successCallback, failCallback) {
|
|
|
135
108
|
segments: segments,
|
|
136
109
|
// Sync and then remove unused segments (not present in the local application) from Translation.io
|
|
137
110
|
purge: Boolean(options.clean),
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
failCallback(response.errors);
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
saveSegmentsToTargetPos(config, paths, response.segments);
|
|
145
|
-
successCallback(response.project);
|
|
146
|
-
}
|
|
147
|
-
}, (error) => {
|
|
148
|
-
console.error(`\n----------\nSynchronization with Translation.io failed: ${error}\n----------`);
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
function createSegmentFromPoItem(item) {
|
|
152
|
-
const itemHasExplicitId = item.extractedComments.includes(EXPLICIT_ID_FLAG);
|
|
153
|
-
const itemHasContext = item.msgctxt != null;
|
|
154
|
-
const segment = {
|
|
155
|
-
type: "source", // No way to edit text for source language (inside code), so not using "key" here
|
|
156
|
-
source: "",
|
|
157
|
-
context: "",
|
|
158
|
-
references: [],
|
|
159
|
-
comment: "",
|
|
160
|
-
};
|
|
161
|
-
// For segment.source & segment.context, we must remain compatible with projects created/synced before Lingui V4
|
|
162
|
-
if (itemHasExplicitId) {
|
|
163
|
-
segment.source = item.msgstr[0];
|
|
164
|
-
segment.context = item.msgid;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
segment.source = item.msgid;
|
|
168
|
-
if (itemHasContext) {
|
|
169
|
-
segment.context = item.msgctxt;
|
|
170
|
-
}
|
|
111
|
+
}, config.service.apiKey);
|
|
112
|
+
if (error) {
|
|
113
|
+
return { success: false, errors: [error.message] };
|
|
171
114
|
}
|
|
172
|
-
if (
|
|
173
|
-
|
|
115
|
+
if (data.errors) {
|
|
116
|
+
return { success: false, errors: data.errors };
|
|
174
117
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
segment.comment = item.extractedComments.join(" | ");
|
|
178
|
-
if (itemHasExplicitId && itemHasContext) {
|
|
179
|
-
// segment.context is already used for the explicit ID, so we need to pass the context (for translators) in segment.comment
|
|
180
|
-
segment.comment = `${item.msgctxt} | ${segment.comment}`;
|
|
181
|
-
// Replace the flag to let us know how to recompose a target PO Item that is consistent with the source PO Item
|
|
182
|
-
segment.comment = segment.comment.replace(EXPLICIT_ID_FLAG, EXPLICIT_ID_AND_CONTEXT_FLAG);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return segment;
|
|
118
|
+
await writeSegmentsToCatalogs(config, sourceLocale, extractionResult, data.segments);
|
|
119
|
+
return { success: true, project: data.project };
|
|
186
120
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
item.msgctxt = item.extractedComments.shift();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return item;
|
|
211
|
-
}
|
|
212
|
-
function saveSegmentsToTargetPos(config, paths, segmentsPerLocale) {
|
|
213
|
-
Object.keys(segmentsPerLocale).forEach((targetLocale) => {
|
|
214
|
-
// Remove existing target POs and JS for this target locale
|
|
215
|
-
paths[targetLocale].forEach((path) => {
|
|
216
|
-
const jsPath = path.replace(/\.po?$/, "") + ".js";
|
|
217
|
-
const dirPath = (0, path_1.dirname)(path);
|
|
218
|
-
// Remove PO, JS and empty dir
|
|
219
|
-
if (fs_1.default.existsSync(path)) {
|
|
220
|
-
fs_1.default.unlinkSync(path);
|
|
221
|
-
}
|
|
222
|
-
if (fs_1.default.existsSync(jsPath)) {
|
|
223
|
-
fs_1.default.unlinkSync(jsPath);
|
|
224
|
-
}
|
|
225
|
-
if (fs_1.default.existsSync(dirPath) && fs_1.default.readdirSync(dirPath).length === 0) {
|
|
226
|
-
fs_1.default.rmdirSync(dirPath);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
// Find target path (ignoring {name})
|
|
230
|
-
const localePath = "".concat(config.catalogs[0].path
|
|
231
|
-
.replace(/{locale}/g, targetLocale)
|
|
232
|
-
.replace(/{name}/g, ""), ".po");
|
|
233
|
-
const segments = segmentsPerLocale[targetLocale];
|
|
234
|
-
const po = new pofile_1.default();
|
|
235
|
-
po.headers = getCreateHeaders(targetLocale);
|
|
236
|
-
const items = [];
|
|
237
|
-
segments.forEach((segment) => {
|
|
238
|
-
const item = createPoItemFromSegment(segment);
|
|
239
|
-
items.push(item);
|
|
240
|
-
});
|
|
241
|
-
// Sort items by messageId
|
|
242
|
-
po.items = items.sort((a, b) => {
|
|
243
|
-
if (a.msgid < b.msgid) {
|
|
244
|
-
return -1;
|
|
245
|
-
}
|
|
246
|
-
if (a.msgid > b.msgid) {
|
|
247
|
-
return 1;
|
|
248
|
-
}
|
|
249
|
-
return 0;
|
|
250
|
-
});
|
|
251
|
-
// Check that localePath directory exists and save PO file
|
|
252
|
-
fs_1.default.promises.mkdir((0, path_1.dirname)(localePath), { recursive: true }).then(() => {
|
|
253
|
-
po.save(localePath, (err) => {
|
|
254
|
-
if (err) {
|
|
255
|
-
console.error("Error while saving target PO files:");
|
|
256
|
-
console.error(err);
|
|
257
|
-
process.exit(1);
|
|
121
|
+
async function writeSegmentsToCatalogs(config, sourceLocale, extractionResult, segmentsPerLocale) {
|
|
122
|
+
// Create segments from source locale PO items
|
|
123
|
+
for (const { catalog, messagesByLocale } of extractionResult) {
|
|
124
|
+
const sourceMessages = messagesByLocale[sourceLocale];
|
|
125
|
+
for (const targetLocale of Object.keys(segmentsPerLocale)) {
|
|
126
|
+
// Remove existing target POs and JS for this target locale
|
|
127
|
+
{
|
|
128
|
+
const path = catalog.getFilename(targetLocale);
|
|
129
|
+
const jsPath = path.replace(new RegExp(`${catalog.format.getCatalogExtension()}$`), "") + ".js";
|
|
130
|
+
const dirPath = (0, path_1.dirname)(path);
|
|
131
|
+
// todo: check tests and all these logic, maybe it could be simplified to just drop the folder
|
|
132
|
+
// Remove PO, JS and empty dir
|
|
133
|
+
if (fs_1.default.existsSync(path)) {
|
|
134
|
+
await fs_1.default.promises.unlink(path);
|
|
135
|
+
}
|
|
136
|
+
if (fs_1.default.existsSync(jsPath)) {
|
|
137
|
+
await fs_1.default.promises.unlink(jsPath);
|
|
138
|
+
}
|
|
139
|
+
if (fs_1.default.existsSync(dirPath) && fs_1.default.readdirSync(dirPath).length === 0) {
|
|
140
|
+
await fs_1.default.promises.rmdir(dirPath);
|
|
258
141
|
}
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
function poPathsPerLocale(config) {
|
|
264
|
-
const paths = {};
|
|
265
|
-
config.locales.forEach((locale) => {
|
|
266
|
-
paths[locale] = [];
|
|
267
|
-
config.catalogs.forEach((catalog) => {
|
|
268
|
-
const path = "".concat(catalog.path.replace(/{locale}/g, locale).replace(/{name}/g, "*"), ".po");
|
|
269
|
-
// If {name} is present (replaced by *), list all the existing POs
|
|
270
|
-
if (path.includes("*")) {
|
|
271
|
-
paths[locale] = paths[locale].concat((0, glob_1.globSync)(path));
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
paths[locale].push(path);
|
|
275
142
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
"Content-Type": "application/json",
|
|
288
|
-
},
|
|
289
|
-
};
|
|
290
|
-
const req = https_1.default.request(options, (res) => {
|
|
291
|
-
res.setEncoding("utf8");
|
|
292
|
-
let body = "";
|
|
293
|
-
res.on("data", (chunk) => {
|
|
294
|
-
body = body.concat(chunk);
|
|
295
|
-
});
|
|
296
|
-
res.on("end", () => {
|
|
297
|
-
const response = JSON.parse(body);
|
|
298
|
-
successCallback(response);
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
req.on("error", (e) => {
|
|
302
|
-
failCallback(e);
|
|
303
|
-
});
|
|
304
|
-
req.write(jsonRequest);
|
|
305
|
-
req.end();
|
|
143
|
+
const translations = Object.fromEntries(segmentsPerLocale[targetLocale].map((segment) => (0, segment_converters_1.createLinguiItemFromSegment)(segment)));
|
|
144
|
+
const messages = Object.fromEntries(Object.entries(sourceMessages).map(([key, entry]) => {
|
|
145
|
+
var _a;
|
|
146
|
+
return [
|
|
147
|
+
key,
|
|
148
|
+
Object.assign(Object.assign({}, entry), { translation: (_a = translations[key]) === null || _a === void 0 ? void 0 : _a.translation }),
|
|
149
|
+
];
|
|
150
|
+
}));
|
|
151
|
+
await catalog.write(targetLocale, (0, catalog_1.order)(config.orderBy, messages));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
306
154
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingui/cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "CLI for working wit message catalogs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
"@babel/parser": "^7.22.0",
|
|
63
63
|
"@babel/runtime": "^7.21.0",
|
|
64
64
|
"@babel/types": "^7.21.2",
|
|
65
|
-
"@lingui/babel-plugin-extract-messages": "5.
|
|
66
|
-
"@lingui/babel-plugin-lingui-macro": "5.
|
|
67
|
-
"@lingui/conf": "5.
|
|
68
|
-
"@lingui/core": "5.
|
|
69
|
-
"@lingui/format-po": "5.
|
|
70
|
-
"@lingui/message-utils": "5.
|
|
65
|
+
"@lingui/babel-plugin-extract-messages": "5.9.0",
|
|
66
|
+
"@lingui/babel-plugin-lingui-macro": "5.9.0",
|
|
67
|
+
"@lingui/conf": "5.9.0",
|
|
68
|
+
"@lingui/core": "5.9.0",
|
|
69
|
+
"@lingui/format-po": "5.9.0",
|
|
70
|
+
"@lingui/message-utils": "5.9.0",
|
|
71
71
|
"chokidar": "3.5.1",
|
|
72
72
|
"cli-table": "^0.3.11",
|
|
73
73
|
"commander": "^10.0.0",
|
|
@@ -93,7 +93,8 @@
|
|
|
93
93
|
"@types/normalize-path": "^3.0.0",
|
|
94
94
|
"mock-fs": "^5.2.0",
|
|
95
95
|
"mockdate": "^3.0.5",
|
|
96
|
+
"msw": "^2.12.7",
|
|
96
97
|
"ts-node": "^10.9.2"
|
|
97
98
|
},
|
|
98
|
-
"gitHead": "
|
|
99
|
+
"gitHead": "491d4c17651c3f76116fe7f63f6bb8a554bef8da"
|
|
99
100
|
}
|