Skip to content

Commit

Permalink
feat(@formatjs/cli): add compile-folder command, fix #1938
Browse files Browse the repository at this point in the history
  • Loading branch information
longlho committed Aug 9, 2020
1 parent 671eb55 commit 46b21bb
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 2 deletions.
37 changes: 35 additions & 2 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as commander from 'commander';
import * as loudRejection from 'loud-rejection';
import extract, {ExtractCLIOptions} from './extract';
import compile, {CompileCLIOpts} from './compile';
import compile, {CompileCLIOpts, Opts} from './compile';
import compileFolder from './compile_folder';
import {sync as globSync} from 'glob';
import {join} from 'path';

const KNOWN_COMMANDS = ['extract'];

Expand Down Expand Up @@ -144,7 +146,6 @@ See https://github.com/webpack/loader-utils#interpolatename for sample patterns`
process.exit(0);
});

// Long text wrapping to available terminal columns: https://github.com/tj/commander.js/pull/956
commander
.command('compile <translation_files>')
.description(
Expand Down Expand Up @@ -182,6 +183,38 @@ for more information`
await compile(files, opts);
});

commander
.command('compile-folder <folder> <outFolder>')
.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.`
)
.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:
\`\`\`
type CompileFn = <T = Record<string, MessageDescriptor>>(
msgs: T
) => Record<string, string>;
\`\`\`
This is especially useful to convert from a TMS-specific format back to react-intl format
`
)
.option(
'--ast',
`Whether to compile to AST. See https://formatjs.io/docs/guides/advanced-usage#pre-parsing-messages
for more information`
)
.action(async (folder: string, outFolder: string, opts?: Opts) => {
const files = globSync(join(folder, '*.json'));
if (!files.length) {
throw new Error(`No JSON file found in ${folder}`);
}
await compileFolder(files, outFolder, opts);
});

if (argv.length < 3) {
commander.help();
} else {
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/src/compile_folder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Opts, compile} from './compile';
import {join, basename} from 'path';
import {outputFile} from 'fs-extra';
export default async function compileFolder(
files: string[],
outFolder: string,
opts: Opts = {}
) {
const results = await Promise.all(files.map(f => compile([f], opts)));
const outFiles = files.map(f => join(outFolder, basename(f)));

return Promise.all(
outFiles.map((outFile, i) => outputFile(outFile, results[i]))
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`basic case 1`] = `
Array [
Object {},
Object {
"a1d12": "I have {count, plural, one{a dog} other{many dogs}}",
"a1dd2": "my name is {name}",
"ashd2": "a message",
},
Object {
"1": "another message",
"2": "my name is {foo}",
"3": "I have {count, plural, one{a cat} other{many cats}}",
},
]
`;

exports[`basic case: help 1`] = `
Object {
"stderr": "",
"stdout": "Usage: formatjs compile-folder [options] <folder> <outFolder>
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.
Options:
--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:
\`\`\`
type CompileFn = <T = Record<string, MessageDescriptor>>(
msgs: T
) => Record<string, string>;
\`\`\`
This is especially useful to convert from a TMS-specific format back to react-intl format
--ast Whether to compile to AST. See
https://formatjs.io/docs/guides/advanced-usage#pre-parsing-messages
for more information
-h, --help display help for command
",
}
`;
33 changes: 33 additions & 0 deletions packages/cli/tests/compile_folder/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {exec as nodeExec} from 'child_process';
import {join, resolve} from 'path';
import * as _rimraf from 'rimraf';
import {promisify} from 'util';
import {sync as globSync} from 'glob';
import {basename} from 'path';
import {mkdtempSync} from 'fs';
import {readJSON} from 'fs-extra';
const exec = promisify(nodeExec);
const BIN_PATH = resolve(__dirname, '../../bin/formatjs');

test('basic case: help', async () => {
await expect(
exec(`${BIN_PATH} compile-folder --help`)
).resolves.toMatchSnapshot();
}, 20000);

test('basic case', async () => {
const inputFiles = globSync(join(__dirname, 'lang', '*.json'));
const outFolder = mkdtempSync('formatjs-cli');
await exec(
`${BIN_PATH} compile-folder ${join(__dirname, 'lang')} ${outFolder}`
);

const outputFiles = globSync(join(outFolder, '*.json'));
expect(outputFiles.map(f => basename(f))).toEqual(
inputFiles.map(f => basename(f))
);

await expect(
Promise.all(outputFiles.map(f => readJSON(f)))
).resolves.toMatchSnapshot();
}, 20000);
1 change: 1 addition & 0 deletions packages/cli/tests/compile_folder/lang/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
14 changes: 14 additions & 0 deletions packages/cli/tests/compile_folder/lang/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"ashd2": {
"defaultMessage": "a message",
"description": "foo"
},
"a1dd2": {
"defaultMessage": "my name is {name}",
"description": "foo"
},
"a1d12": {
"defaultMessage": "I have {count, plural, one{a dog} other{many dogs}}",
"description": "foo"
}
}
14 changes: 14 additions & 0 deletions packages/cli/tests/compile_folder/lang/en2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"1": {
"defaultMessage": "another message",
"description": "foo"
},
"2": {
"defaultMessage": "my name is {foo}",
"description": "foo"
},
"3": {
"defaultMessage": "I have {count, plural, one{a cat} other{many cats}}",
"description": "foo"
}
}
24 changes: 24 additions & 0 deletions website/docs/tooling/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,30 @@ The target file that contains compiled messages.

Whether to compile message into AST instead of just string. See [Advanced Usage](../guides/advanced-usage.md)

## Folder Compilation

Batch compile a folder with extracted files from `formatjs extract` to a folder containing react-intl consumable JSON files. This also does ICU message verification. See [Message Distribution](../getting-started/message-distribution.md) for more details.

```sh
formatjs compile-folder [options] <folder> <outFolder>
```

### `--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:

```tsx
type CompileFn = <T = Record<string, MessageDescriptor>>(
msgs: T
) => Record<string, string>;
```

This is especially useful to convert from a TMS-specific format back to react-intl format

### `--ast`

Whether to compile message into AST instead of just string. See [Advanced Usage](../guides/advanced-usage.md)

## Builtin Formatters

We provide the following built-in formatters to integrate with 3rd party TMSes:
Expand Down

0 comments on commit 46b21bb

Please sign in to comment.