Working with ngx-translate

Working with ngx-translate

Earlier, with AngularJs, we were using Gettext-translate. Now, we have shifted to angular and it has Angular-i18n lib for translations. But unfortunately, it has a different workflow than what we were using with Gettext-translate.
Due to follwoing reasons, we have decided to use ngx-translate instead of Angular-i18n in our projects,

  • Angular-i18n uses XLIFF instead of JSON or PO
  • Workflow that we used for key-values, enums, etc. is not usable, i.e., nit-i18n wont work with Angular-i18n
  • We can directly use PO files to extract translations and it works well with our nit-i18n lib.

Configuration and Usage

Dependencies

@ngx-translate/core: Core lib for internationalization.
@biesbjerg/ngx-translate-extract: Extract translatable (ngx-translate) strings and save as a JSON or Gettext pot file. Merge with existing strings if the output file already exists.
@biesbjerg/ngx-translate-po-http-loader: As we are working with PO files, we need this dependency to load PO file from remote server. We can also write our own custom loader.

Imports

TranslateModule.forRoot({
            loader: {
                provide: TranslateLoader,
                useFactory: MyTranslateLoader,
                deps: [Http]
            },
            missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MyMissingTranslationHandler }
        })

Translate Loader

export function MyTranslateLoader(http: Http) {
    let basePath: string = environment.API_BASE_PATH;
    return new TranslatePoHttpLoader(http, basePath + '/i18n/po', '.po');
}

Extract Translations

Add an extract script to package.json:

"scripts": {
  "extract-translations": "ngx-translate-extract -f pot -i ./src -o ../resources/po-template/template.pot"
}

We can now run npm run extract to extract strings to PO file. But we usually do it by using maven-plugin

<plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <configuration>
    <nodeVersion>v8.0.0</nodeVersion>
    <workingDirectory>
      ${project.basedir}/src/main/frontend
    </workingDirectory>
  </configuration>
  <executions>
    <execution>
      <id>npm run extract-translations</id>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration>
         <arguments>run extract-translations</arguments>
      </configuration>
    </execution>
  </executions>
</plugin>

Init TranslateService

import {TranslateService} from '@ngx-translate/core';
    ...
    constructor(translate: TranslateService) {
        // this language will be used as a fallback when a translation isn't found in the current language
        this.translate.setDefaultLang('en');

         // the lang to use, if the lang isn't available, it will use the current loader to get them
        this.translate.use('en');
    }
    ...

Define the translations

With pipe
{{'Some text' | translate}}
With directive
<p translate>Some text</p>
For text in .ts file
this.translateService.get('Some text').subscribe(response => console.log('Translated text: ' + response));

Handle missing translations

import { MissingTranslationHandler, MissingTranslationHandlerParams } from '@ngx-translate/core';

export class MyMissingTranslationHandler implements MissingTranslationHandler {
    handle(params: MissingTranslationHandlerParams) {
        if (params.translateService.currentLang === params.translateService.defaultLang) {
            return params.key;
        } else {
            return '[MISSING]: ' + params.key;
        }
    }
}

We have added if (params.translateService.currentLang === params.translateService.defaultLang) condition in MissingTranslationHandler because the translation loader we use requires a default language PO file on server. It's a tad awkward to have en.po if we have English as our default language. So, we can have en.po file without translation on server and this check will return same text if default language and current language are same. Lets hope that in future we will have better solutions ;)

By looking at Ocombe's comment on this issue, once Angular-i18n fulfills our needs we may discontinue ngx-translate and move to Angular-i18n.

EDIT

You might get the following warning on the console when you fire the command ng serve,

WARNING in ./~/encoding/lib/iconv-loader.js 
9:12-34 Critical dependency: the request of a dependency is an expression

To avoid such warnings,
Step I : Create postinstall.js file containing following code snippet.

(function() {

'use strict';

// node
var fs = require('fs');
var path = require('path');

// Patch encoding module due to iconv issues -> make it use iconv-lite
(function() {
  var PATCH_VERSION = '0.1.12';
  var PATCH_MODULE = 'encoding';
  var PATCH_REASON = 'Use iconv-lite instead of iconv, helpful for webpack bundling';
  console.log('patching `%s`(%s) module', PATCH_MODULE, PATCH_VERSION);
  var pathToModule = path.join(path.dirname(__dirname), 'node_modules', PATCH_MODULE);
  var pathToModulePackage = path.join(pathToModule, 'package.json');
  var pathToModulePatchedFile1 = path.join(pathToModule, 'lib/iconv-loader.js');
  var pathToModulePatchedFile2 = path.join(pathToModule, 'lib/encoding.js');
  var moduleInfo = require(pathToModulePackage);
  if (moduleInfo.version !== PATCH_VERSION) {
    console.error(
      'patching `encoding` failed - expected `%s` but detected `%s`',
      PATCH_VERSION,
      moduleInfo.version
    );
    process.exit(1);
  }
  var contents;
  if (fs.existsSync(pathToModulePatchedFile1)) {
    contents = [
      '\'use strict\';',
      'module.exports = require(\'iconv-lite\');',
      '',
    ].join('\n');
    fs.writeFileSync(pathToModulePatchedFile1, contents);
  } else {
    console.error('patching `%s` failed because the file does not exist in', PATCH_MODULE, pathToModule);
    process.exit(1);
  }
  if (fs.existsSync(pathToModulePatchedFile2)) {
    contents = fs.readFileSync(pathToModulePatchedFile2).toString();
    contents = contents.replace('console.error(E);','');
    fs.writeFileSync(pathToModulePatchedFile2, contents);
  } else {
    console.error('patching `%s` failed because the file does not exist in', PATCH_MODULE, pathToModule);
    process.exit(1);
  }
  console.log('patching `%s`, reason: `%s` - completed', PATCH_MODULE, PATCH_REASON);
})();

})(this);

Step II : In your package.json,

...
  "scripts": {
    ...
    "postinstall": "node postinstall.js"
  }
...

Now after the command npm install, you will not get that warning.