@formatjs/cli-lib 7.0.3 → 7.1.1
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/lib_esnext/src/cli.js +21 -0
- package/lib_esnext/src/verify/checkMissingKeys.d.ts +1 -0
- package/lib_esnext/src/verify/checkMissingKeys.js +37 -0
- package/lib_esnext/src/verify/checkStructuralEquality.d.ts +1 -0
- package/lib_esnext/src/verify/checkStructuralEquality.js +65 -0
- package/lib_esnext/src/verify/index.d.ts +6 -0
- package/lib_esnext/src/verify/index.js +24 -0
- package/package.json +3 -3
- package/index.d.ts +0 -7
- package/index.js +0 -10
- package/main.d.ts +0 -2
- package/main.js +0 -4
- package/src/cli.d.ts +0 -2
- package/src/cli.js +0 -156
- package/src/compile.d.ts +0 -54
- package/src/compile.js +0 -95
- package/src/compile_folder.d.ts +0 -2
- package/src/compile_folder.js +0 -11
- package/src/console_utils.d.ts +0 -7
- package/src/console_utils.js +0 -76
- package/src/extract.d.ts +0 -74
- package/src/extract.js +0 -197
- package/src/formatters/crowdin.d.ts +0 -7
- package/src/formatters/crowdin.js +0 -27
- package/src/formatters/default.d.ts +0 -6
- package/src/formatters/default.js +0 -13
- package/src/formatters/index.d.ts +0 -9
- package/src/formatters/index.js +0 -40
- package/src/formatters/lokalise.d.ts +0 -9
- package/src/formatters/lokalise.js +0 -24
- package/src/formatters/simple.d.ts +0 -4
- package/src/formatters/simple.js +0 -12
- package/src/formatters/smartling.d.ts +0 -23
- package/src/formatters/smartling.js +0 -50
- package/src/formatters/transifex.d.ts +0 -9
- package/src/formatters/transifex.js +0 -24
- package/src/gts_extractor.d.ts +0 -1
- package/src/gts_extractor.js +0 -17
- package/src/hbs_extractor.d.ts +0 -1
- package/src/hbs_extractor.js +0 -48
- package/src/parse_script.d.ts +0 -7
- package/src/parse_script.js +0 -50
- package/src/pseudo_locale.d.ts +0 -22
- package/src/pseudo_locale.js +0 -122
- package/src/vue_extractor.d.ts +0 -2
- package/src/vue_extractor.js +0 -71
package/lib_esnext/src/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import compile from './compile';
|
|
|
5
5
|
import compileFolder from './compile_folder';
|
|
6
6
|
import { debug } from './console_utils';
|
|
7
7
|
import extract from './extract';
|
|
8
|
+
import { verify } from './verify';
|
|
8
9
|
const KNOWN_COMMANDS = ['extract'];
|
|
9
10
|
async function main(argv) {
|
|
10
11
|
loudRejection();
|
|
@@ -143,6 +144,26 @@ This is especially useful to convert from a TMS-specific format back to react-in
|
|
|
143
144
|
debug('Files to compile:', files);
|
|
144
145
|
await compileFolder(files, outFolder, opts);
|
|
145
146
|
});
|
|
147
|
+
program
|
|
148
|
+
.command('verify [translation_files...]')
|
|
149
|
+
.description(`Run a series of checks on a list of translation files. <translation_files> can be a glob like "foo/**/en.json"`)
|
|
150
|
+
.option('--source-locale <sourceLocale>', `The source locale of the translation files.
|
|
151
|
+
There must be a file named <sourceLocale>.json in the list of translation files.
|
|
152
|
+
This is used as source to verify other translations against.`)
|
|
153
|
+
.option('--missing-keys', `Whether to check for missing keys in target locale compared to source locale.
|
|
154
|
+
This basically guarantees that no messages are untranslated.`)
|
|
155
|
+
.option('--structural-equality', `Whether to check for structural equality of messages between source and target locale.
|
|
156
|
+
This makes sure translations are formattable and are not missing any tokens.`)
|
|
157
|
+
.action(async (filePatterns, opts) => {
|
|
158
|
+
debug('File pattern:', filePatterns);
|
|
159
|
+
debug('Options:', opts);
|
|
160
|
+
const files = globSync(filePatterns);
|
|
161
|
+
if (!files.length) {
|
|
162
|
+
throw new Error(`No input file found with pattern ${filePatterns}`);
|
|
163
|
+
}
|
|
164
|
+
debug('Files to verify:', files);
|
|
165
|
+
await verify(files, opts);
|
|
166
|
+
});
|
|
146
167
|
if (argv.length < 3) {
|
|
147
168
|
program.help();
|
|
148
169
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkMissingKeys(translationFilesContents: Record<string, any>, sourceLocale: string): Promise<boolean>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { debug, writeStderr } from '../console_utils';
|
|
2
|
+
/**
|
|
3
|
+
* Flatten nested obj into list of keys, delimited by `.`
|
|
4
|
+
* @param obj
|
|
5
|
+
* @param parentKey
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
function extractKeys(obj, parentKey = '') {
|
|
9
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
return Object.keys(obj)
|
|
13
|
+
.map(k => [parentKey ? `${parentKey}.${k}` : k, ...extractKeys(obj[k], k)])
|
|
14
|
+
.flat();
|
|
15
|
+
}
|
|
16
|
+
export async function checkMissingKeys(translationFilesContents, sourceLocale) {
|
|
17
|
+
debug('Checking translation files:');
|
|
18
|
+
const enUSContent = translationFilesContents[sourceLocale];
|
|
19
|
+
if (!enUSContent) {
|
|
20
|
+
throw new Error(`Missing source ${sourceLocale}.json file`);
|
|
21
|
+
}
|
|
22
|
+
const enUSKeys = extractKeys(enUSContent);
|
|
23
|
+
return Object.entries(translationFilesContents)
|
|
24
|
+
.filter(([locale]) => locale !== sourceLocale)
|
|
25
|
+
.reduce((result, [locale, content]) => {
|
|
26
|
+
const localeKeys = new Set(extractKeys(content));
|
|
27
|
+
const missingKeys = new Set(enUSKeys.filter(r => !localeKeys.has(r)));
|
|
28
|
+
// We're being lenient here since only missing keys are currently considered breaking
|
|
29
|
+
if (!missingKeys.size) {
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
writeStderr('---------------------------------\n');
|
|
33
|
+
writeStderr(`Missing translation keys for locale ${locale}:\n`);
|
|
34
|
+
missingKeys.forEach(r => writeStderr(`${r}\n`));
|
|
35
|
+
return false;
|
|
36
|
+
}, true);
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function checkStructuralEquality(translationFilesContents: Record<string, any>, sourceLocale: string): Promise<boolean>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isStructurallySame, parse, } from '@formatjs/icu-messageformat-parser';
|
|
2
|
+
import { debug, writeStderr } from '../console_utils';
|
|
3
|
+
import { error } from 'console';
|
|
4
|
+
/**
|
|
5
|
+
* Flatten nested obj into list of keys, delimited by `.`
|
|
6
|
+
* @param obj
|
|
7
|
+
* @param parentKey
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
function flatten(obj, parentKey = '') {
|
|
11
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
return Object.keys(obj).reduce((all, k) => {
|
|
15
|
+
const value = obj[k];
|
|
16
|
+
const key = parentKey ? `${parentKey}.${k}` : k;
|
|
17
|
+
if (typeof value === 'object') {
|
|
18
|
+
Object.assign(all, flatten(value, key));
|
|
19
|
+
}
|
|
20
|
+
all[key] = value;
|
|
21
|
+
return all;
|
|
22
|
+
}, {});
|
|
23
|
+
}
|
|
24
|
+
export async function checkStructuralEquality(translationFilesContents, sourceLocale) {
|
|
25
|
+
debug('Checking translation files:');
|
|
26
|
+
const enUSContent = translationFilesContents[sourceLocale];
|
|
27
|
+
if (!enUSContent) {
|
|
28
|
+
throw new Error(`Missing source ${sourceLocale}.json file`);
|
|
29
|
+
}
|
|
30
|
+
const enUSMessages = Object.entries(flatten(enUSContent)).reduce((all, [key, value]) => {
|
|
31
|
+
try {
|
|
32
|
+
all[key] = parse(value);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
error('Error parsing message', key, value, e);
|
|
36
|
+
}
|
|
37
|
+
return all;
|
|
38
|
+
}, {});
|
|
39
|
+
return Object.entries(translationFilesContents)
|
|
40
|
+
.filter(([locale]) => locale !== sourceLocale)
|
|
41
|
+
.reduce((result, [locale, content]) => {
|
|
42
|
+
const localeMessages = flatten(content);
|
|
43
|
+
const problematicKeys = Object.keys(enUSMessages).filter(k => {
|
|
44
|
+
if (!localeMessages[k]) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const enUSMessage = enUSMessages[k];
|
|
48
|
+
try {
|
|
49
|
+
const localeMessage = parse(localeMessages[k]);
|
|
50
|
+
return !isStructurallySame(enUSMessage, localeMessage);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
error('Error comparing message', k, enUSMessage, localeMessages[k], e);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (!problematicKeys.length) {
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
writeStderr('---------------------------------\n');
|
|
61
|
+
writeStderr(`These translation keys for locale ${locale} are structurally different from ${sourceLocale}:\n`);
|
|
62
|
+
problematicKeys.forEach(r => writeStderr(`${r}\n`));
|
|
63
|
+
return false;
|
|
64
|
+
}, true);
|
|
65
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { basename } from 'path';
|
|
2
|
+
import { debug } from '../console_utils';
|
|
3
|
+
import { checkMissingKeys } from './checkMissingKeys';
|
|
4
|
+
import { readJSON } from 'fs-extra';
|
|
5
|
+
import { checkStructuralEquality } from './checkStructuralEquality';
|
|
6
|
+
export async function verify(files, { sourceLocale, missingKeys, structuralEquality }) {
|
|
7
|
+
debug('Checking translation files:');
|
|
8
|
+
files.forEach(fn => debug(fn));
|
|
9
|
+
const translationFilesContents = (await Promise.all(files.map(async (fn) => [basename(fn, '.json'), await readJSON(fn)]))).reduce((all, [locale, content]) => {
|
|
10
|
+
all[locale] = content;
|
|
11
|
+
return all;
|
|
12
|
+
}, {});
|
|
13
|
+
debug('Verifying files:', files);
|
|
14
|
+
let exitCode = 0;
|
|
15
|
+
if (missingKeys &&
|
|
16
|
+
!(await checkMissingKeys(translationFilesContents, sourceLocale))) {
|
|
17
|
+
exitCode = 1;
|
|
18
|
+
}
|
|
19
|
+
if (structuralEquality &&
|
|
20
|
+
!(await checkStructuralEquality(translationFilesContents, sourceLocale))) {
|
|
21
|
+
exitCode = 1;
|
|
22
|
+
}
|
|
23
|
+
process.exit(exitCode);
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formatjs/cli-lib",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.1",
|
|
4
4
|
"description": "Lib for CLI for formatjs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"intl",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"loud-rejection": "2",
|
|
45
45
|
"tslib": "2",
|
|
46
46
|
"typescript": "5",
|
|
47
|
-
"@formatjs/icu-messageformat-parser": "2.
|
|
48
|
-
"@formatjs/ts-transformer": "3.13.
|
|
47
|
+
"@formatjs/icu-messageformat-parser": "2.10.0",
|
|
48
|
+
"@formatjs/ts-transformer": "3.13.28",
|
|
49
49
|
"@formatjs/icu-skeleton-parser": "1.8.12"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
package/index.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { default as extractAndWrite, extract } from './src/extract';
|
|
2
|
-
export type { ExtractCLIOptions, ExtractOpts } from './src/extract';
|
|
3
|
-
export type { MessageDescriptor } from '@formatjs/ts-transformer';
|
|
4
|
-
export type { FormatFn, CompileFn } from './src/formatters/default';
|
|
5
|
-
export type { Element, Comparator } from 'json-stable-stringify';
|
|
6
|
-
export { default as compileAndWrite, compile } from './src/compile';
|
|
7
|
-
export type { CompileCLIOpts, Opts as CompileOpts } from './src/compile';
|
package/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compile = exports.compileAndWrite = exports.extract = exports.extractAndWrite = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
var extract_1 = require("./src/extract");
|
|
6
|
-
Object.defineProperty(exports, "extractAndWrite", { enumerable: true, get: function () { return tslib_1.__importDefault(extract_1).default; } });
|
|
7
|
-
Object.defineProperty(exports, "extract", { enumerable: true, get: function () { return extract_1.extract; } });
|
|
8
|
-
var compile_1 = require("./src/compile");
|
|
9
|
-
Object.defineProperty(exports, "compileAndWrite", { enumerable: true, get: function () { return tslib_1.__importDefault(compile_1).default; } });
|
|
10
|
-
Object.defineProperty(exports, "compile", { enumerable: true, get: function () { return compile_1.compile; } });
|
package/main.d.ts
DELETED
package/main.js
DELETED
package/src/cli.d.ts
DELETED
package/src/cli.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const commander_1 = require("commander");
|
|
5
|
-
const fast_glob_1 = require("fast-glob");
|
|
6
|
-
const loud_rejection_1 = tslib_1.__importDefault(require("loud-rejection"));
|
|
7
|
-
const compile_1 = tslib_1.__importDefault(require("./compile"));
|
|
8
|
-
const compile_folder_1 = tslib_1.__importDefault(require("./compile_folder"));
|
|
9
|
-
const console_utils_1 = require("./console_utils");
|
|
10
|
-
const extract_1 = tslib_1.__importDefault(require("./extract"));
|
|
11
|
-
const KNOWN_COMMANDS = ['extract'];
|
|
12
|
-
async function main(argv) {
|
|
13
|
-
(0, loud_rejection_1.default)();
|
|
14
|
-
commander_1.program
|
|
15
|
-
// TODO: fix this
|
|
16
|
-
.version('5.0.6', '-v, --version')
|
|
17
|
-
.usage('<command> [flags]')
|
|
18
|
-
.action(command => {
|
|
19
|
-
if (!KNOWN_COMMANDS.includes(command)) {
|
|
20
|
-
commander_1.program.help();
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
commander_1.program
|
|
24
|
-
.command('help', { isDefault: true })
|
|
25
|
-
.description('Show this help message.')
|
|
26
|
-
.action(() => commander_1.program.help());
|
|
27
|
-
// Long text wrapping to available terminal columns: https://github.com/tj/commander.js/pull/956
|
|
28
|
-
// NOTE: please keep the help text in sync with babel-plugin-formatjs documentation.
|
|
29
|
-
commander_1.program
|
|
30
|
-
.command('extract [files...]')
|
|
31
|
-
.description(`Extract string messages from React components that use react-intl.
|
|
32
|
-
The input language is expected to be TypeScript or ES2017 with JSX.`)
|
|
33
|
-
.option('--format <path>', `Path to a formatter file that controls the shape of JSON file from \`--out-file\`.
|
|
34
|
-
The formatter file must export a function called \`format\` with the signature
|
|
35
|
-
\`\`\`
|
|
36
|
-
type FormatFn = <T = Record<string, MessageDescriptor>>(
|
|
37
|
-
msgs: Record<string, MessageDescriptor>
|
|
38
|
-
) => T
|
|
39
|
-
\`\`\`
|
|
40
|
-
This is especially useful to convert from our extracted format to a TMS-specific format.
|
|
41
|
-
`)
|
|
42
|
-
.option('--out-file <path>', `The target file path where the plugin will output an aggregated
|
|
43
|
-
\`.json\` file of all the translations from the \`files\` supplied.`)
|
|
44
|
-
.option('--id-interpolation-pattern <pattern>', `If certain message descriptors don't have id, this \`pattern\` will be used to automatically
|
|
45
|
-
generate IDs for them. Default to \`[sha512:contenthash:base64:6]\` where \`contenthash\` is the hash of
|
|
46
|
-
\`defaultMessage\` and \`description\`.
|
|
47
|
-
See https://github.com/webpack/loader-utils#interpolatename for sample patterns`, '[sha512:contenthash:base64:6]')
|
|
48
|
-
.option('--extract-source-location', `Whether the metadata about the location of the message in the source file should be
|
|
49
|
-
extracted. If \`true\`, then \`file\`, \`start\`, and \`end\` fields will exist for each
|
|
50
|
-
extracted message descriptors.`, false)
|
|
51
|
-
.option('--remove-default-message', 'Remove `defaultMessage` field in generated js after extraction', false)
|
|
52
|
-
.option('--additional-component-names <comma-separated-names>', `Additional component names to extract messages from, e.g: \`'FormattedFooBarMessage'\`.
|
|
53
|
-
**NOTE**: By default we check for the fact that \`FormattedMessage\`
|
|
54
|
-
is imported from \`moduleSourceName\` to make sure variable alias
|
|
55
|
-
works. This option does not do that so it's less safe.`, (val) => val.split(','))
|
|
56
|
-
.option('--additional-function-names <comma-separated-names>', `Additional function names to extract messages from, e.g: \`'$t'\`.`, (val) => val.split(','))
|
|
57
|
-
.option('--ignore <files...>', 'List of glob paths to **not** extract translations from.')
|
|
58
|
-
.option('--throws', 'Whether to throw an exception when we fail to process any file in the batch.')
|
|
59
|
-
.option('--pragma <pragma>', `parse specific additional custom pragma. This allows you to tag certain file with metadata such as \`project\`. For example with this file:
|
|
60
|
-
|
|
61
|
-
\`\`\`
|
|
62
|
-
// @intl-meta project:my-custom-project
|
|
63
|
-
import {FormattedMessage} from 'react-intl';
|
|
64
|
-
|
|
65
|
-
<FormattedMessage defaultMessage="foo" id="bar" />;
|
|
66
|
-
\`\`\`
|
|
67
|
-
|
|
68
|
-
and with option \`{pragma: "intl-meta"}\`, we'll parse out \`// @intl-meta project:my-custom-project\` into \`{project: 'my-custom-project'}\` in the result file.`)
|
|
69
|
-
.option('--preserve-whitespace', 'Whether to preserve whitespace and newlines.')
|
|
70
|
-
.option('--flatten', `Whether to hoist selectors & flatten sentences as much as possible. E.g:
|
|
71
|
-
"I have {count, plural, one{a dog} other{many dogs}}"
|
|
72
|
-
becomes "{count, plural, one{I have a dog} other{I have many dogs}}".
|
|
73
|
-
The goal is to provide as many full sentences as possible since fragmented
|
|
74
|
-
sentences are not translator-friendly.`)
|
|
75
|
-
.action(async (filePatterns, cmdObj) => {
|
|
76
|
-
(0, console_utils_1.debug)('File pattern:', filePatterns);
|
|
77
|
-
(0, console_utils_1.debug)('Options:', cmdObj);
|
|
78
|
-
const files = (0, fast_glob_1.sync)(filePatterns, {
|
|
79
|
-
ignore: cmdObj.ignore,
|
|
80
|
-
});
|
|
81
|
-
(0, console_utils_1.debug)('Files to extract:', files);
|
|
82
|
-
await (0, extract_1.default)(files, {
|
|
83
|
-
outFile: cmdObj.outFile,
|
|
84
|
-
idInterpolationPattern: cmdObj.idInterpolationPattern || '[sha1:contenthash:base64:6]',
|
|
85
|
-
extractSourceLocation: cmdObj.extractSourceLocation,
|
|
86
|
-
removeDefaultMessage: cmdObj.removeDefaultMessage,
|
|
87
|
-
additionalComponentNames: cmdObj.additionalComponentNames,
|
|
88
|
-
additionalFunctionNames: cmdObj.additionalFunctionNames,
|
|
89
|
-
throws: cmdObj.throws,
|
|
90
|
-
pragma: cmdObj.pragma,
|
|
91
|
-
format: cmdObj.format,
|
|
92
|
-
// It is possible that the glob pattern does NOT match anything.
|
|
93
|
-
// But so long as the glob pattern is provided, don't read from stdin.
|
|
94
|
-
readFromStdin: filePatterns.length === 0,
|
|
95
|
-
preserveWhitespace: cmdObj.preserveWhitespace,
|
|
96
|
-
flatten: cmdObj.flatten,
|
|
97
|
-
});
|
|
98
|
-
process.exit(0);
|
|
99
|
-
});
|
|
100
|
-
commander_1.program
|
|
101
|
-
.command('compile [translation_files...]')
|
|
102
|
-
.description(`Compile extracted translation file into react-intl consumable JSON We also verify that the messages are valid ICU and not malformed. <translation_files> can be a glob like "foo/**/en.json"`)
|
|
103
|
-
.option('--format <path>', `Path to a formatter file that converts \`<translation_file>\` to \`Record<string, string>\` so we can compile. The file must export a function named \`compile\` with the signature:
|
|
104
|
-
\`\`\`
|
|
105
|
-
type CompileFn = <T = Record<string, MessageDescriptor>>(
|
|
106
|
-
msgs: T
|
|
107
|
-
) => Record<string, string>;
|
|
108
|
-
\`\`\`
|
|
109
|
-
This is especially useful to convert from a TMS-specific format back to react-intl format
|
|
110
|
-
`)
|
|
111
|
-
.option('--out-file <path>', `Compiled translation output file. If this is not provided, result will be printed to stdout`)
|
|
112
|
-
.option('--ast', `Whether to compile to AST. See https://formatjs.github.io/docs/guides/advanced-usage#pre-parsing-messages for more information`)
|
|
113
|
-
.option('--skip-errors', `Whether to continue compiling messages after encountering an error. Any keys with errors will not be included in the output file.`)
|
|
114
|
-
.option('--pseudo-locale <pseudoLocale>', `Whether to generate pseudo-locale files. See https://formatjs.github.io/docs/tooling/cli#--pseudo-locale-pseudolocale for possible values. "--ast" is required for this to work.`)
|
|
115
|
-
.option('--ignore-tag', `Whether the parser to treat HTML/XML tags as string literal instead of parsing them as tag token. When this is false we only allow simple tags without any attributes.`)
|
|
116
|
-
.action(async (filePatterns, opts) => {
|
|
117
|
-
(0, console_utils_1.debug)('File pattern:', filePatterns);
|
|
118
|
-
(0, console_utils_1.debug)('Options:', opts);
|
|
119
|
-
const files = (0, fast_glob_1.sync)(filePatterns);
|
|
120
|
-
if (!files.length) {
|
|
121
|
-
throw new Error(`No input file found with pattern ${filePatterns}`);
|
|
122
|
-
}
|
|
123
|
-
(0, console_utils_1.debug)('Files to compile:', files);
|
|
124
|
-
await (0, compile_1.default)(files, opts);
|
|
125
|
-
});
|
|
126
|
-
commander_1.program
|
|
127
|
-
.command('compile-folder <folder> <outFolder>')
|
|
128
|
-
.description(`Batch compile all extracted translation JSON files in <folder> to <outFolder> containing react-intl consumable JSON. We also verify that the messages are valid ICU and not malformed.`)
|
|
129
|
-
.option('--format <path>', `Path to a formatter file that converts JSON files in \`<folder>\` to \`Record<string, string>\` so we can compile. The file must export a function named \`compile\` with the signature:
|
|
130
|
-
\`\`\`
|
|
131
|
-
type CompileFn = <T = Record<string, MessageDescriptor>>(
|
|
132
|
-
msgs: T
|
|
133
|
-
) => Record<string, string>;
|
|
134
|
-
\`\`\`
|
|
135
|
-
This is especially useful to convert from a TMS-specific format back to react-intl format
|
|
136
|
-
`)
|
|
137
|
-
.option('--ast', `Whether to compile to AST. See https://formatjs.github.io/docs/guides/advanced-usage#pre-parsing-messages for more information`)
|
|
138
|
-
.action(async (folder, outFolder, opts) => {
|
|
139
|
-
(0, console_utils_1.debug)('Folder:', folder);
|
|
140
|
-
(0, console_utils_1.debug)('Options:', opts);
|
|
141
|
-
// fast-glob expect `/` in Windows as well
|
|
142
|
-
const files = (0, fast_glob_1.sync)(`${folder}/*.json`);
|
|
143
|
-
if (!files.length) {
|
|
144
|
-
throw new Error(`No JSON file found in ${folder}`);
|
|
145
|
-
}
|
|
146
|
-
(0, console_utils_1.debug)('Files to compile:', files);
|
|
147
|
-
await (0, compile_folder_1.default)(files, outFolder, opts);
|
|
148
|
-
});
|
|
149
|
-
if (argv.length < 3) {
|
|
150
|
-
commander_1.program.help();
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
commander_1.program.parse(argv);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
exports.default = main;
|
package/src/compile.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Formatter } from './formatters';
|
|
2
|
-
export type CompileFn = (msgs: any) => Record<string, string>;
|
|
3
|
-
export type PseudoLocale = 'xx-LS' | 'xx-AC' | 'xx-HA' | 'en-XA' | 'en-XB';
|
|
4
|
-
export interface CompileCLIOpts extends Opts {
|
|
5
|
-
/**
|
|
6
|
-
* The target file that contains compiled messages.
|
|
7
|
-
*/
|
|
8
|
-
outFile?: string;
|
|
9
|
-
}
|
|
10
|
-
export interface Opts {
|
|
11
|
-
/**
|
|
12
|
-
* Whether to compile message into AST instead of just string
|
|
13
|
-
*/
|
|
14
|
-
ast?: boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Whether to continue compiling messages after encountering an error.
|
|
17
|
-
* Any keys with errors will not be included in the output file.
|
|
18
|
-
*/
|
|
19
|
-
skipErrors?: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* Path to a formatter file that converts <translation_files> to
|
|
22
|
-
* `Record<string, string>` so we can compile.
|
|
23
|
-
*/
|
|
24
|
-
format?: string | Formatter<unknown>;
|
|
25
|
-
/**
|
|
26
|
-
* Whether to compile to pseudo locale
|
|
27
|
-
*/
|
|
28
|
-
pseudoLocale?: PseudoLocale;
|
|
29
|
-
/**
|
|
30
|
-
* Whether the parser to treat HTML/XML tags as string literal
|
|
31
|
-
* instead of parsing them as tag token.
|
|
32
|
-
* When this is false we only allow simple tags without
|
|
33
|
-
* any attributes
|
|
34
|
-
*/
|
|
35
|
-
ignoreTag?: boolean;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Aggregate `inputFiles` into a single JSON blob and compile.
|
|
39
|
-
* Also checks for conflicting IDs.
|
|
40
|
-
* Then returns the serialized result as a `string` since key order
|
|
41
|
-
* makes a difference in some vendor.
|
|
42
|
-
* @param inputFiles Input files
|
|
43
|
-
* @param opts Options
|
|
44
|
-
* @returns serialized result in string format
|
|
45
|
-
*/
|
|
46
|
-
export declare function compile(inputFiles: string[], opts?: Opts): Promise<string>;
|
|
47
|
-
/**
|
|
48
|
-
* Aggregate `inputFiles` into a single JSON blob and compile.
|
|
49
|
-
* Also checks for conflicting IDs and write output to `outFile`.
|
|
50
|
-
* @param inputFiles Input files
|
|
51
|
-
* @param compileOpts options
|
|
52
|
-
* @returns A `Promise` that resolves if file was written successfully
|
|
53
|
-
*/
|
|
54
|
-
export default function compileAndWrite(inputFiles: string[], compileOpts?: CompileCLIOpts): Promise<void>;
|
package/src/compile.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.compile = compile;
|
|
4
|
-
exports.default = compileAndWrite;
|
|
5
|
-
const tslib_1 = require("tslib");
|
|
6
|
-
const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser");
|
|
7
|
-
const fs_extra_1 = require("fs-extra");
|
|
8
|
-
const json_stable_stringify_1 = tslib_1.__importDefault(require("json-stable-stringify"));
|
|
9
|
-
const console_utils_1 = require("./console_utils");
|
|
10
|
-
const formatters_1 = require("./formatters");
|
|
11
|
-
const pseudo_locale_1 = require("./pseudo_locale");
|
|
12
|
-
/**
|
|
13
|
-
* Aggregate `inputFiles` into a single JSON blob and compile.
|
|
14
|
-
* Also checks for conflicting IDs.
|
|
15
|
-
* Then returns the serialized result as a `string` since key order
|
|
16
|
-
* makes a difference in some vendor.
|
|
17
|
-
* @param inputFiles Input files
|
|
18
|
-
* @param opts Options
|
|
19
|
-
* @returns serialized result in string format
|
|
20
|
-
*/
|
|
21
|
-
async function compile(inputFiles, opts = {}) {
|
|
22
|
-
(0, console_utils_1.debug)('Compiling files:', inputFiles);
|
|
23
|
-
const { ast, format, pseudoLocale, skipErrors, ignoreTag } = opts;
|
|
24
|
-
const formatter = await (0, formatters_1.resolveBuiltinFormatter)(format);
|
|
25
|
-
const messages = {};
|
|
26
|
-
const messageAsts = {};
|
|
27
|
-
const idsWithFileName = {};
|
|
28
|
-
const compiledFiles = await Promise.all(inputFiles.map(f => (0, fs_extra_1.readJSON)(f).then(formatter.compile)));
|
|
29
|
-
(0, console_utils_1.debug)('Compiled files:', compiledFiles);
|
|
30
|
-
for (let i = 0; i < inputFiles.length; i++) {
|
|
31
|
-
const inputFile = inputFiles[i];
|
|
32
|
-
(0, console_utils_1.debug)('Processing file:', inputFile);
|
|
33
|
-
const compiled = compiledFiles[i];
|
|
34
|
-
for (const id in compiled) {
|
|
35
|
-
if (messages[id] && messages[id] !== compiled[id]) {
|
|
36
|
-
throw new Error(`Conflicting ID "${id}" with different translation found in these 2 files:
|
|
37
|
-
ID: ${id}
|
|
38
|
-
Message from ${idsWithFileName[id]}: ${messages[id]}
|
|
39
|
-
Message from ${inputFile}: ${compiled[id]}
|
|
40
|
-
`);
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
const msgAst = (0, icu_messageformat_parser_1.parse)(compiled[id], { ignoreTag });
|
|
44
|
-
messages[id] = compiled[id];
|
|
45
|
-
switch (pseudoLocale) {
|
|
46
|
-
case 'xx-LS':
|
|
47
|
-
messageAsts[id] = (0, pseudo_locale_1.generateXXLS)(msgAst);
|
|
48
|
-
break;
|
|
49
|
-
case 'xx-AC':
|
|
50
|
-
messageAsts[id] = (0, pseudo_locale_1.generateXXAC)(msgAst);
|
|
51
|
-
break;
|
|
52
|
-
case 'xx-HA':
|
|
53
|
-
messageAsts[id] = (0, pseudo_locale_1.generateXXHA)(msgAst);
|
|
54
|
-
break;
|
|
55
|
-
case 'en-XA':
|
|
56
|
-
messageAsts[id] = (0, pseudo_locale_1.generateENXA)(msgAst);
|
|
57
|
-
break;
|
|
58
|
-
case 'en-XB':
|
|
59
|
-
messageAsts[id] = (0, pseudo_locale_1.generateENXB)(msgAst);
|
|
60
|
-
break;
|
|
61
|
-
default:
|
|
62
|
-
messageAsts[id] = msgAst;
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
idsWithFileName[id] = inputFile;
|
|
66
|
-
}
|
|
67
|
-
catch (e) {
|
|
68
|
-
(0, console_utils_1.warn)('Error validating message "%s" with ID "%s" in file "%s"', compiled[id], id, inputFile);
|
|
69
|
-
if (!skipErrors) {
|
|
70
|
-
throw e;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return (0, json_stable_stringify_1.default)(ast ? messageAsts : messages, {
|
|
76
|
-
space: 2,
|
|
77
|
-
cmp: formatter.compareMessages || undefined,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Aggregate `inputFiles` into a single JSON blob and compile.
|
|
82
|
-
* Also checks for conflicting IDs and write output to `outFile`.
|
|
83
|
-
* @param inputFiles Input files
|
|
84
|
-
* @param compileOpts options
|
|
85
|
-
* @returns A `Promise` that resolves if file was written successfully
|
|
86
|
-
*/
|
|
87
|
-
async function compileAndWrite(inputFiles, compileOpts = {}) {
|
|
88
|
-
const { outFile, ...opts } = compileOpts;
|
|
89
|
-
const serializedResult = (await compile(inputFiles, opts)) + '\n';
|
|
90
|
-
if (outFile) {
|
|
91
|
-
(0, console_utils_1.debug)('Writing output file:', outFile);
|
|
92
|
-
return (0, fs_extra_1.outputFile)(outFile, serializedResult);
|
|
93
|
-
}
|
|
94
|
-
await (0, console_utils_1.writeStdout)(serializedResult);
|
|
95
|
-
}
|
package/src/compile_folder.d.ts
DELETED
package/src/compile_folder.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default = compileFolder;
|
|
4
|
-
const compile_1 = require("./compile");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
const fs_extra_1 = require("fs-extra");
|
|
7
|
-
async function compileFolder(files, outFolder, opts = {}) {
|
|
8
|
-
const results = await Promise.all(files.map(f => (0, compile_1.compile)([f], opts)));
|
|
9
|
-
const outFiles = files.map(f => (0, path_1.join)(outFolder, (0, path_1.basename)(f)));
|
|
10
|
-
return Promise.all(outFiles.map((outFile, i) => (0, fs_extra_1.outputFile)(outFile, results[i])));
|
|
11
|
-
}
|
package/src/console_utils.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const writeStderr: (arg1: string | Uint8Array) => Promise<void>;
|
|
2
|
-
export declare const writeStdout: (arg1: string | Uint8Array) => Promise<void>;
|
|
3
|
-
export declare function clearLine(terminal: (typeof process)['stderr']): Promise<void>;
|
|
4
|
-
export declare function debug(message: string, ...args: any[]): Promise<void>;
|
|
5
|
-
export declare function warn(message: string, ...args: any[]): Promise<void>;
|
|
6
|
-
export declare function error(message: string, ...args: any[]): Promise<void>;
|
|
7
|
-
export declare function getStdinAsString(): Promise<string>;
|
package/src/console_utils.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.writeStdout = exports.writeStderr = void 0;
|
|
4
|
-
exports.clearLine = clearLine;
|
|
5
|
-
exports.debug = debug;
|
|
6
|
-
exports.warn = warn;
|
|
7
|
-
exports.error = error;
|
|
8
|
-
exports.getStdinAsString = getStdinAsString;
|
|
9
|
-
const tslib_1 = require("tslib");
|
|
10
|
-
const chalk_1 = require("chalk");
|
|
11
|
-
const readline_1 = tslib_1.__importDefault(require("readline"));
|
|
12
|
-
const util_1 = require("util");
|
|
13
|
-
const CLEAR_WHOLE_LINE = 0;
|
|
14
|
-
exports.writeStderr = (0, util_1.promisify)(process.stderr.write).bind(process.stderr);
|
|
15
|
-
exports.writeStdout = (0, util_1.promisify)(process.stdout.write).bind(process.stdout);
|
|
16
|
-
const nativeClearLine = (0, util_1.promisify)(readline_1.default.clearLine).bind(readline_1.default);
|
|
17
|
-
const nativeCursorTo = (0, util_1.promisify)(readline_1.default.cursorTo).bind(readline_1.default);
|
|
18
|
-
// From:
|
|
19
|
-
// https://github.com/yarnpkg/yarn/blob/53d8004229f543f342833310d5af63a4b6e59c8a/src/reporters/console/util.js
|
|
20
|
-
async function clearLine(terminal) {
|
|
21
|
-
if (!chalk_1.supportsColor) {
|
|
22
|
-
if (terminal.isTTY) {
|
|
23
|
-
// terminal
|
|
24
|
-
if (terminal.columns > 0) {
|
|
25
|
-
await (0, exports.writeStderr)(`\r${' '.repeat(terminal.columns - 1)}`);
|
|
26
|
-
}
|
|
27
|
-
await (0, exports.writeStderr)(`\r`);
|
|
28
|
-
}
|
|
29
|
-
// ignore piping to file
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
await nativeClearLine(terminal, CLEAR_WHOLE_LINE);
|
|
33
|
-
await nativeCursorTo(terminal, 0, undefined);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const LEVEL_COLORS = {
|
|
37
|
-
debug: chalk_1.green,
|
|
38
|
-
warn: chalk_1.yellow,
|
|
39
|
-
error: chalk_1.red,
|
|
40
|
-
};
|
|
41
|
-
function label(level, message) {
|
|
42
|
-
return `[@formatjs/cli] [${LEVEL_COLORS[level](level.toUpperCase())}] ${message}`;
|
|
43
|
-
}
|
|
44
|
-
async function debug(message, ...args) {
|
|
45
|
-
if (process.env.LOG_LEVEL !== 'debug') {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
await clearLine(process.stderr);
|
|
49
|
-
await (0, exports.writeStderr)((0, util_1.format)(label('debug', message), ...args));
|
|
50
|
-
await (0, exports.writeStderr)('\n');
|
|
51
|
-
}
|
|
52
|
-
async function warn(message, ...args) {
|
|
53
|
-
await clearLine(process.stderr);
|
|
54
|
-
await (0, exports.writeStderr)((0, util_1.format)(label('warn', message), ...args));
|
|
55
|
-
await (0, exports.writeStderr)('\n');
|
|
56
|
-
}
|
|
57
|
-
async function error(message, ...args) {
|
|
58
|
-
await clearLine(process.stderr);
|
|
59
|
-
await (0, exports.writeStderr)((0, util_1.format)(label('error', message), ...args));
|
|
60
|
-
await (0, exports.writeStderr)('\n');
|
|
61
|
-
}
|
|
62
|
-
function getStdinAsString() {
|
|
63
|
-
let result = '';
|
|
64
|
-
return new Promise(resolve => {
|
|
65
|
-
process.stdin.setEncoding('utf-8');
|
|
66
|
-
process.stdin.on('readable', () => {
|
|
67
|
-
let chunk;
|
|
68
|
-
while ((chunk = process.stdin.read())) {
|
|
69
|
-
result += chunk;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
process.stdin.on('end', () => {
|
|
73
|
-
resolve(result);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|