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:
- Install i18next-scanner with npm install --save-dev i18next-scanner or yarn add -D i18next-scanner.
- Add the code below in i18next-scanner.config.js file at the root of your project.
- 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.