@lingui/cli 5.7.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/api/catalog.d.ts +2 -2
- package/dist/api/catalog.js +45 -58
- 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/api/catalog.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LinguiConfigNormalized, OrderBy } from "@lingui/conf";
|
|
1
|
+
import { LinguiConfigNormalized, OrderBy, OrderByFn } from "@lingui/conf";
|
|
2
2
|
import { FormatterWrapper } from "./formats";
|
|
3
3
|
import { CompiledCatalogNamespace } from "./compile";
|
|
4
4
|
import { GetTranslationsOptions } from "./catalog/getTranslationsForCatalog";
|
|
@@ -69,4 +69,4 @@ export declare class Catalog {
|
|
|
69
69
|
export declare function cleanObsolete<T extends ExtractedCatalogType>(messages: T): T;
|
|
70
70
|
export declare function order<T extends ExtractedCatalogType>(by: OrderBy, catalog: T): T;
|
|
71
71
|
export declare function writeCompiled(path: string, locale: string, compiledCatalog: string, namespace?: CompiledCatalogNamespace): Promise<string>;
|
|
72
|
-
export declare
|
|
72
|
+
export declare const orderByMessage: OrderByFn;
|
package/dist/api/catalog.js
CHANGED
|
@@ -3,11 +3,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Catalog = void 0;
|
|
6
|
+
exports.orderByMessage = exports.Catalog = void 0;
|
|
7
7
|
exports.cleanObsolete = cleanObsolete;
|
|
8
8
|
exports.order = order;
|
|
9
9
|
exports.writeCompiled = writeCompiled;
|
|
10
|
-
exports.orderByMessage = orderByMessage;
|
|
11
10
|
const fs_1 = __importDefault(require("fs"));
|
|
12
11
|
const path_1 = __importDefault(require("path"));
|
|
13
12
|
const glob_1 = require("glob");
|
|
@@ -140,8 +139,7 @@ class Catalog {
|
|
|
140
139
|
return await this.format.read(filename, undefined);
|
|
141
140
|
}
|
|
142
141
|
get sourcePaths() {
|
|
143
|
-
const includeGlobs = this.include
|
|
144
|
-
.map((includePath) => {
|
|
142
|
+
const includeGlobs = this.include.map((includePath) => {
|
|
145
143
|
const isDir = (0, utils_1.isDirectory)(includePath);
|
|
146
144
|
/**
|
|
147
145
|
* glob library results from absolute patterns such as /foo/* are mounted onto the root setting using path.join.
|
|
@@ -150,8 +148,7 @@ class Catalog {
|
|
|
150
148
|
return isDir
|
|
151
149
|
? (0, normalize_path_1.default)(path_1.default.resolve(process.cwd(), includePath === "/" ? "" : includePath, "**/*.*"))
|
|
152
150
|
: includePath;
|
|
153
|
-
})
|
|
154
|
-
.map(utils_1.makePathRegexSafe);
|
|
151
|
+
});
|
|
155
152
|
return (0, glob_1.globSync)(includeGlobs, { ignore: this.exclude, mark: true });
|
|
156
153
|
}
|
|
157
154
|
get localeDir() {
|
|
@@ -173,28 +170,33 @@ function cleanObsolete(messages) {
|
|
|
173
170
|
return Object.fromEntries(Object.entries(messages).filter(([, message]) => !message.obsolete));
|
|
174
171
|
}
|
|
175
172
|
function order(by, catalog) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return Object.keys(messages)
|
|
188
|
-
.sort()
|
|
173
|
+
const orderByFn = typeof by === "function"
|
|
174
|
+
? by
|
|
175
|
+
: {
|
|
176
|
+
messageId: orderByMessageId,
|
|
177
|
+
message: exports.orderByMessage,
|
|
178
|
+
origin: orderByOrigin,
|
|
179
|
+
}[by];
|
|
180
|
+
return Object.keys(catalog)
|
|
181
|
+
.sort((a, b) => {
|
|
182
|
+
return orderByFn({ messageId: a, entry: catalog[a] }, { messageId: b, entry: catalog[b] });
|
|
183
|
+
})
|
|
189
184
|
.reduce((acc, key) => {
|
|
190
185
|
;
|
|
191
|
-
acc[key] =
|
|
186
|
+
acc[key] = catalog[key];
|
|
192
187
|
return acc;
|
|
193
188
|
}, {});
|
|
194
189
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Object keys are in the same order as they were created
|
|
192
|
+
* https://stackoverflow.com/a/31102605/1535540
|
|
193
|
+
*/
|
|
194
|
+
const orderByMessageId = (a, b) => {
|
|
195
|
+
return a.messageId.localeCompare(b.messageId);
|
|
196
|
+
};
|
|
197
|
+
const orderByOrigin = (a, b) => {
|
|
198
|
+
function getFirstOrigin(entry) {
|
|
199
|
+
const sortedOrigins = entry.origin.sort((a, b) => {
|
|
198
200
|
if (a[0] < b[0])
|
|
199
201
|
return -1;
|
|
200
202
|
if (a[0] > b[0])
|
|
@@ -203,26 +205,18 @@ function orderByOrigin(messages) {
|
|
|
203
205
|
});
|
|
204
206
|
return sortedOrigins[0];
|
|
205
207
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return 0;
|
|
219
|
-
})
|
|
220
|
-
.reduce((acc, key) => {
|
|
221
|
-
;
|
|
222
|
-
acc[key] = messages[key];
|
|
223
|
-
return acc;
|
|
224
|
-
}, {});
|
|
225
|
-
}
|
|
208
|
+
const [aFile, aLineNumber] = getFirstOrigin(a.entry);
|
|
209
|
+
const [bFile, bLineNumber] = getFirstOrigin(b.entry);
|
|
210
|
+
if (aFile < bFile)
|
|
211
|
+
return -1;
|
|
212
|
+
if (aFile > bFile)
|
|
213
|
+
return 1;
|
|
214
|
+
if (aLineNumber < bLineNumber)
|
|
215
|
+
return -1;
|
|
216
|
+
if (aLineNumber > bLineNumber)
|
|
217
|
+
return 1;
|
|
218
|
+
return 0;
|
|
219
|
+
};
|
|
226
220
|
async function writeCompiled(path, locale, compiledCatalog, namespace) {
|
|
227
221
|
let ext;
|
|
228
222
|
switch (namespace) {
|
|
@@ -240,21 +234,14 @@ async function writeCompiled(path, locale, compiledCatalog, namespace) {
|
|
|
240
234
|
await (0, utils_1.writeFile)(filename, compiledCatalog);
|
|
241
235
|
return filename;
|
|
242
236
|
}
|
|
243
|
-
|
|
237
|
+
const orderByMessage = (a, b) => {
|
|
244
238
|
// hardcoded en-US locale to have consistent sorting
|
|
245
239
|
// @see https://github.com/lingui/js-lingui/pull/1808
|
|
246
240
|
const collator = new Intl.Collator("en-US");
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
})
|
|
255
|
-
.reduce((acc, key) => {
|
|
256
|
-
;
|
|
257
|
-
acc[key] = messages[key];
|
|
258
|
-
return acc;
|
|
259
|
-
}, {});
|
|
260
|
-
}
|
|
241
|
+
const aMsg = a.entry.message || "";
|
|
242
|
+
const bMsg = b.entry.message || "";
|
|
243
|
+
const aCtxt = a.entry.context || "";
|
|
244
|
+
const bCtxt = b.entry.context || "";
|
|
245
|
+
return collator.compare(aMsg, bMsg) || collator.compare(aCtxt, bCtxt);
|
|
246
|
+
};
|
|
247
|
+
exports.orderByMessage = orderByMessage;
|
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
|
}
|