Extract translations from your Aurelia app

Posted on 2019-08-03 in Aurelia

If you use Aurelia with the i18n plugin, you will have to maintain JSON files that will map a translation key to an actual translation. Manually editing the file to add/remove keys can be tedious.

Luckily, keys can be extracted automatically thanks to i18next-scanner. It can extract translation keys from JavaScript, TypeScript and HTML files.

To do this:

  1. Install i18next-scanner with npm install --save-dev i18next-scanner or yarn add -D i18next-scanner.
  2. Add the code below in i18next-scanner.config.js file at the root of your project.
  3. Run ./node_modules/.bin/i18next-scanner src/**/*.{js,ts,html} to extract the translations. They will be under src/locales/{{lng}}/translation.json. Keys to be translated will have the value __STRING_NOT_TRANSLATED__.

The extraction supports the following patterns:

  • In code files: it will extract keys directly from i18next (usage like i18next.t('my-key')) and from the injected I18N service (ie if you use it like this: this.i18n.tr('my-key')).
  • In HMTL files:
    • With the following attributes: data-i18n, data-t, t, i18n (usage like this <p t='my-key'></p>).
    • With the following behaviors: t (usage ${ 'myKey' | t } and ${ 'myKey' & t } – the whitespace is not important).

If this doesn't exactly work for your use cases, please adapt the code below and ask a question in the comments if needed.

 1 const fs = require('fs');
 2 const path = require('path');
 3 const typescript = require("typescript");
 4 
 5 module.exports = {
 6     input: [
 7         'src/**/*.{ts}',
 8     ],
 9     output: './',
10     options: {
11         debug: true,
12         func: {
13             list: ['i18next.t', 'i18n.t', 'this.i18n.tr'],
14             extensions: ['.js'],
15         },
16         lngs: ['en', 'fr'],
17         ns: [
18             'translation',
19         ],
20         defaultLng: 'en',
21         defaultNs: 'translation',
22         defaultValue: '__STRING_NOT_TRANSLATED__',
23         resource: {
24             loadPath: 'src/locales/{{lng}}/{{ns}}.json',
25             savePath: 'src/locales/{{lng}}/{{ns}}.json',
26             jsonIndent: 4,
27             lineEnding: '\n'
28         },
29         nsSeparator: false, // namespace separator
30         keySeparator: false, // key separator
31         interpolation: {
32             prefix: '{{',
33             suffix: '}}'
34         },
35         removeUnusedKeys: true,
36     },
37 
38     transform: function customTransform(file, enc, done) {
39         const {ext} = path.parse(file.path);
40         const content = fs.readFileSync(file.path, enc);
41 
42         if (ext === '.ts') {
43             const {outputText} = typescript.transpileModule(content.toString(), {
44                 compilerOptions: {
45                     target: 'es2018',
46                 },
47                 fileName: path.basename(file.path),
48             });
49 
50             this.parser.parseFuncFromString(outputText, {list: ['i18next.t', 'i18next.tr', 'this.i18n.tr']});
51         } else if (ext === '.html') {
52             this.parser
53                 .parseAttrFromString(content, {list: ['data-i18n', 'data-t', 't', 'i18n'] });
54 
55             // We extra behaviours `${ 'myKey' | t }` and `${ 'myKey' & t }` from the file.
56             const extractBehaviours = /\${ *'([a-zA-Z0-9]+)' *[&|] *t *}/g;
57             const strContent = content.toString();
58             let group;
59             while (true) {
60                 group = extractBehaviours.exec(strContent);
61                 if (group === null) {
62                     break;
63                 }
64                 this.parser.set(group[1]);
65             }
66         }
67 
68         done();
69     },
70 };

You can also view the code in gitlab: https://gitlab.com/snippets/1881948

Because of the removeUnusedKeys: true options, keys that are not found will be removed from the JSON files. If you don't want this behavior, switch this option to false.

It is compatible with Javascript and Typescript. If you don't use Typescript, remove the line const typescript = require("typescript"); to avoid issues at import.