@lingui/cli 3.14.0 → 3.16.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/CHANGELOG.md +482 -0
- package/build/LICENSE +21 -0
- package/build/api/catalog.js +582 -0
- package/build/api/compile.js +89 -0
- package/{api → build/api}/detect.js +23 -9
- package/build/api/extract.js +78 -0
- package/build/api/extractors/babel.js +51 -0
- package/build/api/extractors/index.js +51 -0
- package/build/api/extractors/typescript.js +71 -0
- package/build/api/formats/csv.js +65 -0
- package/{api → build/api}/formats/index.js +8 -5
- package/build/api/formats/lingui.js +67 -0
- package/build/api/formats/minimal.js +63 -0
- package/build/api/formats/po-gettext.js +296 -0
- package/build/api/formats/po.js +122 -0
- package/{api → build/api}/help.js +6 -18
- package/{api → build/api}/index.js +7 -7
- package/build/api/locales.js +45 -0
- package/{api → build/api}/pseudoLocalize.js +13 -13
- package/build/api/stats.js +46 -0
- package/{api → build/api}/utils.js +21 -40
- package/build/lingui-add-locale.js +11 -0
- package/build/lingui-compile.js +192 -0
- package/build/lingui-extract-template.js +64 -0
- package/build/lingui-extract.js +181 -0
- package/{lingui.js → build/lingui.js} +2 -2
- package/{services → build/services}/translationIO.js +78 -94
- package/build/tests.js +78 -0
- package/package.json +19 -13
- package/api/catalog.js +0 -775
- package/api/compile.js +0 -169
- package/api/extract.js +0 -192
- package/api/extractors/babel.js +0 -61
- package/api/extractors/index.js +0 -130
- package/api/extractors/typescript.js +0 -77
- package/api/formats/csv.js +0 -71
- package/api/formats/lingui.js +0 -64
- package/api/formats/minimal.js +0 -50
- package/api/formats/po-gettext.js +0 -331
- package/api/formats/po.js +0 -130
- package/api/locales.js +0 -43
- package/api/stats.js +0 -51
- package/lingui-add-locale.js +0 -11
- package/lingui-compile.js +0 -199
- package/lingui-extract-template.js +0 -71
- package/lingui-extract.js +0 -286
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getCatalogs = getCatalogs;
|
|
7
|
+
exports.getCatalogForFile = getCatalogForFile;
|
|
8
|
+
exports.getCatalogForMerge = getCatalogForMerge;
|
|
9
|
+
exports.normalizeRelativePath = normalizeRelativePath;
|
|
10
|
+
exports.order = order;
|
|
11
|
+
exports.orderByMessageId = orderByMessageId;
|
|
12
|
+
exports.orderByOrigin = orderByOrigin;
|
|
13
|
+
exports.cleanObsolete = exports.Catalog = void 0;
|
|
14
|
+
|
|
15
|
+
var _os = _interopRequireDefault(require("os"));
|
|
16
|
+
|
|
17
|
+
var _fsExtra = _interopRequireDefault(require("fs-extra"));
|
|
18
|
+
|
|
19
|
+
var _path = _interopRequireDefault(require("path"));
|
|
20
|
+
|
|
21
|
+
var R = _interopRequireWildcard(require("ramda"));
|
|
22
|
+
|
|
23
|
+
var _chalk = _interopRequireDefault(require("chalk"));
|
|
24
|
+
|
|
25
|
+
var _glob = _interopRequireDefault(require("glob"));
|
|
26
|
+
|
|
27
|
+
var _micromatch = _interopRequireDefault(require("micromatch"));
|
|
28
|
+
|
|
29
|
+
var _normalizePath = _interopRequireDefault(require("normalize-path"));
|
|
30
|
+
|
|
31
|
+
var _formats = _interopRequireDefault(require("./formats"));
|
|
32
|
+
|
|
33
|
+
var _extractors = _interopRequireDefault(require("./extractors"));
|
|
34
|
+
|
|
35
|
+
var _utils = require("./utils");
|
|
36
|
+
|
|
37
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
38
|
+
|
|
39
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
40
|
+
|
|
41
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
42
|
+
|
|
43
|
+
const NAME = "{name}";
|
|
44
|
+
const NAME_REPLACE_RE = /{name}/g;
|
|
45
|
+
const LOCALE = "{locale}";
|
|
46
|
+
const LOCALE_REPLACE_RE = /{locale}/g;
|
|
47
|
+
const LOCALE_SUFFIX_RE = /\{locale\}.*$/;
|
|
48
|
+
const PATHSEP = "/"; // force posix everywhere
|
|
49
|
+
|
|
50
|
+
class Catalog {
|
|
51
|
+
constructor({
|
|
52
|
+
name,
|
|
53
|
+
path,
|
|
54
|
+
include,
|
|
55
|
+
exclude = []
|
|
56
|
+
}, config) {
|
|
57
|
+
this.name = name;
|
|
58
|
+
this.path = normalizeRelativePath(path);
|
|
59
|
+
this.include = include.map(normalizeRelativePath);
|
|
60
|
+
this.exclude = [this.localeDir, ...exclude.map(normalizeRelativePath)];
|
|
61
|
+
this.config = config;
|
|
62
|
+
this.format = (0, _formats.default)(config.format);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async make(options) {
|
|
66
|
+
const nextCatalog = await this.collect(options);
|
|
67
|
+
if (!nextCatalog) return false;
|
|
68
|
+
const prevCatalogs = this.readAll();
|
|
69
|
+
const catalogs = this.merge(prevCatalogs, nextCatalog, {
|
|
70
|
+
overwrite: options.overwrite,
|
|
71
|
+
files: options.files
|
|
72
|
+
}); // Map over all locales and post-process each catalog
|
|
73
|
+
|
|
74
|
+
const cleanAndSort = R.map(R.pipe( // Clean obsolete messages
|
|
75
|
+
options.clean ? cleanObsolete : R.identity, // Sort messages
|
|
76
|
+
order(options.orderBy)));
|
|
77
|
+
const sortedCatalogs = cleanAndSort(catalogs);
|
|
78
|
+
|
|
79
|
+
if (options.locale) {
|
|
80
|
+
this.write(options.locale, sortedCatalogs[options.locale]);
|
|
81
|
+
} else {
|
|
82
|
+
this.writeAll(sortedCatalogs);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async makeTemplate(options) {
|
|
89
|
+
const catalog = await this.collect(options);
|
|
90
|
+
if (!catalog) return false;
|
|
91
|
+
const sort = order(options.orderBy);
|
|
92
|
+
this.writeTemplate(sort(catalog));
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Collect messages from source paths. Return a raw message catalog as JSON.
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async collect(options) {
|
|
101
|
+
const tmpDir = _path.default.join(_os.default.tmpdir(), `lingui-${process.pid}`);
|
|
102
|
+
|
|
103
|
+
if (_fsExtra.default.existsSync(tmpDir)) {
|
|
104
|
+
(0, _utils.removeDirectory)(tmpDir, true);
|
|
105
|
+
} else {
|
|
106
|
+
_fsExtra.default.mkdirSync(tmpDir);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
let paths = this.sourcePaths;
|
|
111
|
+
|
|
112
|
+
if (options.files) {
|
|
113
|
+
options.files = options.files.map(p => (0, _normalizePath.default)(p, false));
|
|
114
|
+
const regex = new RegExp(options.files.join("|"), "i");
|
|
115
|
+
paths = paths.filter(path => regex.test(path));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let catalogSuccess = true;
|
|
119
|
+
|
|
120
|
+
for (let filename of paths) {
|
|
121
|
+
const fileSuccess = await (0, _extractors.default)(filename, tmpDir, {
|
|
122
|
+
verbose: options.verbose,
|
|
123
|
+
configPath: options.configPath,
|
|
124
|
+
babelOptions: this.config.extractBabelOptions,
|
|
125
|
+
extractors: options.extractors,
|
|
126
|
+
projectType: options.projectType
|
|
127
|
+
});
|
|
128
|
+
catalogSuccess && (catalogSuccess = fileSuccess);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!catalogSuccess) return undefined;
|
|
132
|
+
return function traverse(directory) {
|
|
133
|
+
return _fsExtra.default.readdirSync(directory).map(filename => {
|
|
134
|
+
const filepath = _path.default.join(directory, filename);
|
|
135
|
+
|
|
136
|
+
if (_fsExtra.default.lstatSync(filepath).isDirectory()) {
|
|
137
|
+
return traverse(filepath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!filename.endsWith(".json")) return;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(_fsExtra.default.readFileSync(filepath).toString());
|
|
144
|
+
} catch (e) {}
|
|
145
|
+
}).filter(Boolean).reduce((catalog, messages) => R.mergeWithKey(mergeOriginsAndExtractedComments, catalog, messages), {});
|
|
146
|
+
}(tmpDir);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
throw e;
|
|
149
|
+
} finally {
|
|
150
|
+
(0, _utils.removeDirectory)(tmpDir);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
merge(prevCatalogs, nextCatalog, options) {
|
|
155
|
+
const nextKeys = R.keys(nextCatalog).map(String);
|
|
156
|
+
return R.mapObjIndexed((prevCatalog, locale) => {
|
|
157
|
+
const prevKeys = R.keys(prevCatalog).map(String);
|
|
158
|
+
const newKeys = R.difference(nextKeys, prevKeys);
|
|
159
|
+
const mergeKeys = R.intersection(nextKeys, prevKeys);
|
|
160
|
+
const obsoleteKeys = R.difference(prevKeys, nextKeys); // Initialize new catalog with new keys
|
|
161
|
+
|
|
162
|
+
const newMessages = R.mapObjIndexed((message, key) => ({
|
|
163
|
+
translation: this.config.sourceLocale === locale ? message.message || key : "",
|
|
164
|
+
...message
|
|
165
|
+
}), R.pick(newKeys, nextCatalog)); // Merge translations from previous catalog
|
|
166
|
+
|
|
167
|
+
const mergedMessages = mergeKeys.map(key => {
|
|
168
|
+
const updateFromDefaults = this.config.sourceLocale === locale && (prevCatalog[key].translation === prevCatalog[key].message || options.overwrite);
|
|
169
|
+
const translation = updateFromDefaults ? nextCatalog[key].message || key : prevCatalog[key].translation;
|
|
170
|
+
return {
|
|
171
|
+
[key]: {
|
|
172
|
+
translation,
|
|
173
|
+
...R.omit(["obsolete, translation"], nextCatalog[key])
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}); // Mark all remaining translations as obsolete
|
|
177
|
+
// Only if *options.files* is not provided
|
|
178
|
+
|
|
179
|
+
const obsoleteMessages = obsoleteKeys.map(key => ({
|
|
180
|
+
[key]: { ...prevCatalog[key],
|
|
181
|
+
obsolete: options.files ? false : true
|
|
182
|
+
}
|
|
183
|
+
}));
|
|
184
|
+
return R.mergeAll([newMessages, ...mergedMessages, ...obsoleteMessages]);
|
|
185
|
+
}, prevCatalogs);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getTranslations(locale, options) {
|
|
189
|
+
const catalogs = this.readAll();
|
|
190
|
+
const template = this.readTemplate() || {};
|
|
191
|
+
return R.mapObjIndexed((_value, key) => this.getTranslation(catalogs, locale, key, options), { ...template,
|
|
192
|
+
...catalogs[locale]
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getTranslation(catalogs, locale, key, {
|
|
197
|
+
fallbackLocales,
|
|
198
|
+
sourceLocale
|
|
199
|
+
}) {
|
|
200
|
+
const catalog = catalogs[locale] || {};
|
|
201
|
+
|
|
202
|
+
if (!catalog.hasOwnProperty(key)) {
|
|
203
|
+
console.error(`Message with key ${key} is missing in locale ${locale}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const getTranslation = _locale => {
|
|
207
|
+
const configLocales = this.config.locales.join('", "');
|
|
208
|
+
const localeCatalog = catalogs[_locale] || {};
|
|
209
|
+
|
|
210
|
+
if (!localeCatalog) {
|
|
211
|
+
console.warn(`
|
|
212
|
+
Catalog "${_locale}" isn't present in locales config parameter
|
|
213
|
+
Add "${_locale}" to your lingui.config.js:
|
|
214
|
+
{
|
|
215
|
+
locales: ["${configLocales}", "${_locale}"]
|
|
216
|
+
}
|
|
217
|
+
`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!localeCatalog.hasOwnProperty(key)) {
|
|
222
|
+
console.error(`Message with key ${key} is missing in locale ${_locale}`);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (catalogs[_locale]) {
|
|
227
|
+
return catalogs[_locale][key].translation;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return null;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const getMultipleFallbacks = _locale => {
|
|
234
|
+
const fL = fallbackLocales && fallbackLocales[_locale]; // some probably the fallback will be undefined, so just search by locale
|
|
235
|
+
|
|
236
|
+
if (!fL) return null;
|
|
237
|
+
|
|
238
|
+
if (Array.isArray(fL)) {
|
|
239
|
+
for (const fallbackLocale of fL) {
|
|
240
|
+
if (catalogs[fallbackLocale]) {
|
|
241
|
+
return getTranslation(fallbackLocale);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
return getTranslation(fL);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return (// Get translation in target locale
|
|
250
|
+
getTranslation(locale) || // We search in fallbackLocales as dependent of each locale
|
|
251
|
+
getMultipleFallbacks(locale) || // Get translation in fallbackLocales.default (if any)
|
|
252
|
+
fallbackLocales?.default && getTranslation(fallbackLocales.default) || // Get message default
|
|
253
|
+
catalog[key]?.defaults || // If sourceLocale is either target locale of fallback one, use key
|
|
254
|
+
sourceLocale && sourceLocale === locale && key || sourceLocale && fallbackLocales?.default && sourceLocale === fallbackLocales.default && key || // Otherwise no translation is available
|
|
255
|
+
undefined
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
write(locale, messages) {
|
|
260
|
+
const filename = this.path.replace(LOCALE_REPLACE_RE, locale) + this.format.catalogExtension;
|
|
261
|
+
const created = !_fsExtra.default.existsSync(filename);
|
|
262
|
+
|
|
263
|
+
const basedir = _path.default.dirname(filename);
|
|
264
|
+
|
|
265
|
+
if (!_fsExtra.default.existsSync(basedir)) {
|
|
266
|
+
_fsExtra.default.mkdirpSync(basedir);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const options = { ...this.config.formatOptions,
|
|
270
|
+
locale
|
|
271
|
+
};
|
|
272
|
+
this.format.write(filename, messages, options);
|
|
273
|
+
return [created, filename];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
writeAll(catalogs) {
|
|
277
|
+
this.locales.forEach(locale => this.write(locale, catalogs[locale]));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
writeTemplate(messages) {
|
|
281
|
+
const filename = this.templateFile;
|
|
282
|
+
|
|
283
|
+
const basedir = _path.default.dirname(filename);
|
|
284
|
+
|
|
285
|
+
if (!_fsExtra.default.existsSync(basedir)) {
|
|
286
|
+
_fsExtra.default.mkdirpSync(basedir);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const options = { ...this.config.formatOptions,
|
|
290
|
+
locale: undefined
|
|
291
|
+
};
|
|
292
|
+
this.format.write(filename, messages, options);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
writeCompiled(locale, compiledCatalog, namespace) {
|
|
296
|
+
let ext;
|
|
297
|
+
|
|
298
|
+
if (namespace === "es") {
|
|
299
|
+
ext = "mjs";
|
|
300
|
+
} else if (namespace === "ts") {
|
|
301
|
+
ext = "ts";
|
|
302
|
+
} else {
|
|
303
|
+
ext = "js";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const filename = `${this.path.replace(LOCALE_REPLACE_RE, locale)}.${ext}`;
|
|
307
|
+
|
|
308
|
+
const basedir = _path.default.dirname(filename);
|
|
309
|
+
|
|
310
|
+
if (!_fsExtra.default.existsSync(basedir)) {
|
|
311
|
+
_fsExtra.default.mkdirpSync(basedir);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_fsExtra.default.writeFileSync(filename, compiledCatalog);
|
|
315
|
+
|
|
316
|
+
return filename;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
read(locale) {
|
|
320
|
+
const filename = this.path.replace(LOCALE_REPLACE_RE, locale) + this.format.catalogExtension;
|
|
321
|
+
if (!_fsExtra.default.existsSync(filename)) return null;
|
|
322
|
+
return this.format.read(filename);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
readAll() {
|
|
326
|
+
return R.mergeAll(this.locales.map(locale => ({
|
|
327
|
+
[locale]: this.read(locale)
|
|
328
|
+
})));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
readTemplate() {
|
|
332
|
+
const filename = this.templateFile;
|
|
333
|
+
if (!_fsExtra.default.existsSync(filename)) return null;
|
|
334
|
+
return this.format.read(filename);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
get sourcePaths() {
|
|
338
|
+
const includeGlobs = this.include.map(includePath => {
|
|
339
|
+
const isDir = _fsExtra.default.existsSync(includePath) && _fsExtra.default.lstatSync(includePath).isDirectory();
|
|
340
|
+
/**
|
|
341
|
+
* glob library results from absolute patterns such as /foo/* are mounted onto the root setting using path.join.
|
|
342
|
+
* On windows, this will by default result in /foo/* matching C:\foo\bar.txt.
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
return isDir ? (0, _normalizePath.default)(_path.default.resolve(process.cwd(), includePath === "/" ? "" : includePath, "**/*.*")) : includePath;
|
|
347
|
+
});
|
|
348
|
+
const patterns = includeGlobs.length > 1 ? `{${includeGlobs.join(",")}}` : includeGlobs[0];
|
|
349
|
+
return _glob.default.sync(patterns, {
|
|
350
|
+
ignore: this.exclude,
|
|
351
|
+
mark: true
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
get templateFile() {
|
|
356
|
+
return this.path.replace(LOCALE_SUFFIX_RE, "messages.pot");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
get localeDir() {
|
|
360
|
+
const localePatternIndex = this.path.indexOf(LOCALE);
|
|
361
|
+
|
|
362
|
+
if (localePatternIndex === -1) {
|
|
363
|
+
throw Error(`Invalid catalog path: ${LOCALE} variable is missing`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return this.path.substr(0, localePatternIndex);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
get locales() {
|
|
370
|
+
return this.config.locales;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Parse `config.catalogs` and return a list of configured Catalog instances.
|
|
376
|
+
*/
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
exports.Catalog = Catalog;
|
|
380
|
+
|
|
381
|
+
function getCatalogs(config) {
|
|
382
|
+
const catalogsConfig = config.catalogs;
|
|
383
|
+
const catalogs = [];
|
|
384
|
+
catalogsConfig.forEach(catalog => {
|
|
385
|
+
// Validate that `catalogPath` doesn't end with trailing slash
|
|
386
|
+
if (catalog.path.endsWith(PATHSEP)) {
|
|
387
|
+
const extension = (0, _formats.default)(config.format).catalogExtension;
|
|
388
|
+
const correctPath = catalog.path.slice(0, -1);
|
|
389
|
+
const examplePath = correctPath.replace(LOCALE_REPLACE_RE, // Show example using one of configured locales (if any)
|
|
390
|
+
(config.locales || [])[0] || "en") + extension;
|
|
391
|
+
throw new Error( // prettier-ignore
|
|
392
|
+
`Remove trailing slash from "${catalog.path}". Catalog path isn't a directory,` + ` but translation file without extension. For example, catalog path "${correctPath}"` + ` results in translation file "${examplePath}".`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const include = ensureArray(catalog.include).map(normalizeRelativePath);
|
|
396
|
+
const exclude = ensureArray(catalog.exclude).map(normalizeRelativePath); // catalog.path without {name} pattern -> always refers to a single catalog
|
|
397
|
+
|
|
398
|
+
if (!catalog.path.includes(NAME)) {
|
|
399
|
+
// Validate that sourcePaths doesn't use {name} pattern either
|
|
400
|
+
const invalidSource = include.find(path => path.includes(NAME));
|
|
401
|
+
|
|
402
|
+
if (invalidSource !== undefined) {
|
|
403
|
+
throw new Error(`Catalog with path "${catalog.path}" doesn't have a {name} pattern` + ` in it, but one of source directories uses it: "${invalidSource}".` + ` Either add {name} pattern to "${catalog.path}" or remove it` + ` from all source directories.`);
|
|
404
|
+
} // catalog name is the last directory of catalog.path.
|
|
405
|
+
// If the last part is {locale}, then catalog doesn't have an explicit name
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
const name = function () {
|
|
409
|
+
const _name = catalog.path.split(PATHSEP).slice(-1)[0];
|
|
410
|
+
return _name !== LOCALE ? _name : null;
|
|
411
|
+
}();
|
|
412
|
+
|
|
413
|
+
catalogs.push(new Catalog({
|
|
414
|
+
name,
|
|
415
|
+
path: normalizeRelativePath(catalog.path),
|
|
416
|
+
include,
|
|
417
|
+
exclude
|
|
418
|
+
}, config));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const patterns = include.map(path => path.replace(NAME_REPLACE_RE, "*"));
|
|
423
|
+
|
|
424
|
+
const candidates = _glob.default.sync(patterns.length > 1 ? `{${patterns.join(",")}}` : patterns[0], {
|
|
425
|
+
ignore: exclude,
|
|
426
|
+
mark: true
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
candidates.forEach(catalogDir => {
|
|
430
|
+
const name = _path.default.basename(catalogDir);
|
|
431
|
+
|
|
432
|
+
catalogs.push(new Catalog({
|
|
433
|
+
name,
|
|
434
|
+
path: normalizeRelativePath(catalog.path.replace(NAME_REPLACE_RE, name)),
|
|
435
|
+
include: include.map(path => path.replace(NAME_REPLACE_RE, name)),
|
|
436
|
+
exclude: exclude.map(path => path.replace(NAME_REPLACE_RE, name))
|
|
437
|
+
}, config));
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
return catalogs;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function getCatalogForFile(file, catalogs) {
|
|
444
|
+
for (const catalog of catalogs) {
|
|
445
|
+
const catalogFile = `${catalog.path}${catalog.format.catalogExtension}`;
|
|
446
|
+
const catalogGlob = catalogFile.replace(LOCALE_REPLACE_RE, "*");
|
|
447
|
+
|
|
448
|
+
const match = _micromatch.default.capture(normalizeRelativePath(_path.default.relative(catalog.config.rootDir, catalogGlob)), normalizeRelativePath(file));
|
|
449
|
+
|
|
450
|
+
if (match) {
|
|
451
|
+
return {
|
|
452
|
+
locale: match[0],
|
|
453
|
+
catalog
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Create catalog for merged messages.
|
|
462
|
+
*/
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
function getCatalogForMerge(config) {
|
|
466
|
+
const catalogConfig = config;
|
|
467
|
+
|
|
468
|
+
if (catalogConfig.catalogsMergePath.endsWith(PATHSEP)) {
|
|
469
|
+
const extension = (0, _formats.default)(config.format).catalogExtension;
|
|
470
|
+
const correctPath = catalogConfig.catalogsMergePath.slice(0, -1);
|
|
471
|
+
const examplePath = correctPath.replace(LOCALE_REPLACE_RE, // Show example using one of configured locales (if any)
|
|
472
|
+
(config.locales || [])[0] || "en") + extension;
|
|
473
|
+
throw new Error( // prettier-ignore
|
|
474
|
+
`Remove trailing slash from "${catalogConfig.catalogsMergePath}". Catalog path isn't a directory,` + ` but translation file without extension. For example, catalog path "${correctPath}"` + ` results in translation file "${examplePath}".`);
|
|
475
|
+
} // catalog name is the last directory of catalogPath.
|
|
476
|
+
// If the last part is {locale}, then catalog doesn't have an explicit name
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
const name = function () {
|
|
480
|
+
const _name = _path.default.basename(normalizeRelativePath(catalogConfig.catalogsMergePath));
|
|
481
|
+
|
|
482
|
+
return _name !== LOCALE ? _name : null;
|
|
483
|
+
}();
|
|
484
|
+
|
|
485
|
+
const catalog = new Catalog({
|
|
486
|
+
name,
|
|
487
|
+
path: normalizeRelativePath(catalogConfig.catalogsMergePath),
|
|
488
|
+
include: [],
|
|
489
|
+
exclude: []
|
|
490
|
+
}, config);
|
|
491
|
+
return catalog;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Merge origins and extractedComments for messages found in different places. All other attributes
|
|
495
|
+
* should be the same (raise an error if defaults are different).
|
|
496
|
+
*/
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
function mergeOriginsAndExtractedComments(msgId, prev, next) {
|
|
500
|
+
if (prev.defaults !== next.defaults) {
|
|
501
|
+
throw new Error(`Encountered different defaults for message ${_chalk.default.yellow(msgId)}` + `\n${_chalk.default.yellow((0, _utils.prettyOrigin)(prev.origin))} ${prev.defaults}` + `\n${_chalk.default.yellow((0, _utils.prettyOrigin)(next.origin))} ${next.defaults}`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return { ...next,
|
|
505
|
+
extractedComments: R.concat(prev.extractedComments, next.extractedComments),
|
|
506
|
+
origin: R.concat(prev.origin, next.origin)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Ensure that value is always array. If not, turn it into an array of one element.
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
const ensureArray = value => {
|
|
515
|
+
if (value == null) return [];
|
|
516
|
+
return Array.isArray(value) ? value : [value];
|
|
517
|
+
};
|
|
518
|
+
/**
|
|
519
|
+
* Remove ./ at the beginning: ./relative => relative
|
|
520
|
+
* relative => relative
|
|
521
|
+
* Preserve directories: ./relative/ => relative/
|
|
522
|
+
* Preserve absolute paths: /absolute/path => /absolute/path
|
|
523
|
+
*/
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
function normalizeRelativePath(sourcePath) {
|
|
527
|
+
if (_path.default.isAbsolute(sourcePath)) {
|
|
528
|
+
// absolute path
|
|
529
|
+
return (0, _normalizePath.default)(sourcePath, false);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const isDir = _fsExtra.default.existsSync(sourcePath) && _fsExtra.default.lstatSync(sourcePath).isDirectory();
|
|
533
|
+
|
|
534
|
+
return (0, _normalizePath.default)(_path.default.relative(process.cwd(), sourcePath), false) + (isDir ? "/" : "");
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const cleanObsolete = R.filter(message => !message.obsolete);
|
|
538
|
+
exports.cleanObsolete = cleanObsolete;
|
|
539
|
+
|
|
540
|
+
function order(by) {
|
|
541
|
+
return {
|
|
542
|
+
messageId: orderByMessageId,
|
|
543
|
+
origin: orderByOrigin
|
|
544
|
+
}[by];
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Object keys are in the same order as they were created
|
|
548
|
+
* https://stackoverflow.com/a/31102605/1535540
|
|
549
|
+
*/
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
function orderByMessageId(messages) {
|
|
553
|
+
const orderedMessages = {};
|
|
554
|
+
Object.keys(messages).sort().forEach(function (key) {
|
|
555
|
+
orderedMessages[key] = messages[key];
|
|
556
|
+
});
|
|
557
|
+
return orderedMessages;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function orderByOrigin(messages) {
|
|
561
|
+
function getFirstOrigin(messageKey) {
|
|
562
|
+
const sortedOrigins = messages[messageKey].origin.sort((a, b) => {
|
|
563
|
+
if (a[0] < b[0]) return -1;
|
|
564
|
+
if (a[0] > b[0]) return 1;
|
|
565
|
+
return 0;
|
|
566
|
+
});
|
|
567
|
+
return sortedOrigins[0];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return Object.keys(messages).sort(function (a, b) {
|
|
571
|
+
const [aFile, aLineNumber] = getFirstOrigin(a);
|
|
572
|
+
const [bFile, bLineNumber] = getFirstOrigin(b);
|
|
573
|
+
if (aFile < bFile) return -1;
|
|
574
|
+
if (aFile > bFile) return 1;
|
|
575
|
+
if (aLineNumber < bLineNumber) return -1;
|
|
576
|
+
if (aLineNumber > bLineNumber) return 1;
|
|
577
|
+
return 0;
|
|
578
|
+
}).reduce((acc, key) => {
|
|
579
|
+
acc[key] = messages[key];
|
|
580
|
+
return acc;
|
|
581
|
+
}, {});
|
|
582
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createCompiledCatalog = createCompiledCatalog;
|
|
7
|
+
exports.compile = compile;
|
|
8
|
+
|
|
9
|
+
var t = _interopRequireWildcard(require("@babel/types"));
|
|
10
|
+
|
|
11
|
+
var _generator = _interopRequireDefault(require("@babel/generator"));
|
|
12
|
+
|
|
13
|
+
var _compile = require("@lingui/core/compile");
|
|
14
|
+
|
|
15
|
+
var _pseudoLocalize = _interopRequireDefault(require("./pseudoLocalize"));
|
|
16
|
+
|
|
17
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
18
|
+
|
|
19
|
+
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
|
|
20
|
+
|
|
21
|
+
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
22
|
+
|
|
23
|
+
function createCompiledCatalog(locale, messages, options) {
|
|
24
|
+
const {
|
|
25
|
+
strict = false,
|
|
26
|
+
namespace = "cjs",
|
|
27
|
+
pseudoLocale,
|
|
28
|
+
compilerBabelOptions = {}
|
|
29
|
+
} = options;
|
|
30
|
+
const shouldPseudolocalize = locale === pseudoLocale;
|
|
31
|
+
const compiledMessages = Object.keys(messages).reduce((obj, key) => {
|
|
32
|
+
const value = messages[key]; // If the current ID's value is a context object, create a nested
|
|
33
|
+
// expression, and assign the current ID to that expression
|
|
34
|
+
|
|
35
|
+
if (typeof value === "object") {
|
|
36
|
+
obj[key] = Object.keys(value).reduce((obj, contextKey) => {
|
|
37
|
+
obj[contextKey] = compile(value[contextKey], shouldPseudolocalize);
|
|
38
|
+
return obj;
|
|
39
|
+
}, {});
|
|
40
|
+
return obj;
|
|
41
|
+
} // Don't use `key` as a fallback translation in strict mode.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const translation = messages[key] || (!strict ? key : "");
|
|
45
|
+
obj[key] = compile(translation, shouldPseudolocalize);
|
|
46
|
+
return obj;
|
|
47
|
+
}, {});
|
|
48
|
+
const ast = buildExportStatement( //build JSON.parse(<compiledMessages>) statement
|
|
49
|
+
t.callExpression(t.memberExpression(t.identifier('JSON'), t.identifier('parse')), [t.stringLiteral(JSON.stringify(compiledMessages))]), namespace);
|
|
50
|
+
const code = (0, _generator.default)(ast, {
|
|
51
|
+
minified: true,
|
|
52
|
+
jsescOption: {
|
|
53
|
+
minimal: true
|
|
54
|
+
},
|
|
55
|
+
...compilerBabelOptions
|
|
56
|
+
}).code;
|
|
57
|
+
return "/*eslint-disable*/" + code;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildExportStatement(expression, namespace) {
|
|
61
|
+
if (namespace === "es" || namespace === "ts") {
|
|
62
|
+
// export const messages = { message: "Translation" }
|
|
63
|
+
return t.exportNamedDeclaration(t.variableDeclaration("const", [t.variableDeclarator(t.identifier("messages"), expression)]));
|
|
64
|
+
} else {
|
|
65
|
+
let exportExpression = null;
|
|
66
|
+
const matches = namespace.match(/^(window|global)\.([^.\s]+)$/);
|
|
67
|
+
|
|
68
|
+
if (namespace === "cjs") {
|
|
69
|
+
// module.exports.messages = { message: "Translation" }
|
|
70
|
+
exportExpression = t.memberExpression(t.identifier("module"), t.identifier("exports"));
|
|
71
|
+
} else if (matches) {
|
|
72
|
+
// window.i18nMessages = { messages: { message: "Translation" }}
|
|
73
|
+
exportExpression = t.memberExpression(t.identifier(matches[1]), t.identifier(matches[2]));
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error(`Invalid namespace param: "${namespace}"`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return t.expressionStatement(t.assignmentExpression("=", exportExpression, t.objectExpression([t.objectProperty(t.identifier("messages"), expression)])));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compile string message into AST tree. Message format is parsed/compiled into
|
|
83
|
+
* JS arrays, which are handled in client.
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
function compile(message, shouldPseudolocalize = false) {
|
|
88
|
+
return (0, _compile.compileMessage)(message, value => shouldPseudolocalize ? (0, _pseudoLocalize.default)(value) : value);
|
|
89
|
+
}
|