29
votes

I try to use Angular official internationalization tools with angular universal. So far I'm able to translate the client side rendering using the following procedure (thanks to this answer https://stackoverflow.com/a/40930110/1110635) :

I add "i18n" attributes as stated in the documentation in my templates :

./src/+app/about/about.component.html :

<h1 i18n="H1 of the about component">About</h1>
...

Then I run :

./node_modules/.bin/ng-xi18n

to generate the base messages.xlf file.

Then I copy this file for each locale I want to support as "messages.[locale].xlf" in a "locale" folder. When ready, I create a "messages.[locale].ts" for each xlf file containing an exported string of its content :

./locale/messages.fr.ts :

// TRANSLATION_FR is only for "messages.fr.ts" of course.
// I would create a TRANSLATION_ES const inside "messages.es.ts" for spanish for example.
export const TRANSLATION_FR: string = `<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a" datatype="html">
        <source>About</source>
        <target>A propos</target>
        <note priority="1" from="description">H1 of the about component</note>
      </trans-unit>
    </body>
  </file>
</xliff>
`;

Finally, my client.ts file looks like the following :

./src/client.ts :

[...]

// i18n
import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID  } from '@angular/core';
import { TRANSLATION_FR } from '../locale/messages.fr';

import { MainModule } from './browser.module';

export const platformRef = platformUniversalDynamic();

// on document ready bootstrap Angular 2
export function main() {
  return platformRef.bootstrapModule(MainModule, {
      providers: [
          {provide: TRANSLATIONS, useValue: TRANSLATION_FR},
          {provide: TRANSLATIONS_FORMAT, useValue: "xlf"},
          {provide: LOCALE_ID, useValue: 'fr'}
      ]
  });
}
bootloader(main);

This works and make the "client side" application work as expected. "About" is replaced by "A propos". BUT, because angular universal pre-render the page on the server side using express the text is not translated until the client side bootstraping is done.

So when you first go on the page you see "About" for about 1 second before the client side kicks in an replace it with "A propos".

The solution seem obvious, simply run the translation service on the server side ! But I have no idea how to do that.

My server.ts looks like this :

./src/server.ts

[...]

// i18n
import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID  } from '@angular/core';
import { TRANSLATION_FR } from '../locale/messages.fr';

const app = express();
const ROOT = path.join(path.resolve(__dirname, '..', 'dist'));

// Express View
app.engine('.html', createEngine({
    ngModule: MainModule,
    providers: [
      /**
       * HERE IS THE IMPORTANT PART.
       * I tried to declare providers but it has no effect.
       */
      {provide: TRANSLATIONS, useValue: TRANSLATION_FR},
      {provide: TRANSLATIONS_FORMAT, useValue: "xlf"},
      {provide: LOCALE_ID, useValue: 'fr'}
    ]
}));
app.set('port', process.env.PORT || 3000);
app.set('views', ROOT);
app.set('view engine', 'html');
[...]

function ngApp(req, res) {
    res.render('index', {
      req,
      res,
      preboot: false,
      baseUrl: '/',
      requestUrl: req.originalUrl,
      originUrl: `http://localhost:${ app.get('port') }`
    });
}
app.get('*', ngApp);

// Server
let server = app.listen(app.get('port'), () => {
    console.log(`Listening on: http://localhost:${server.address().port}`);
});

I have no direct access to the bootstrapModule method like on the client side. The providers key on the "createEngine" parameter object was already there in the original server.ts code.

What am I missing?

1
Hi, I'm trying to do this but I don't want to use *.ts files for translations but it's being so difficult. So, have you been solved it?IvanMoreno
No I'm using ng2-translate because I didn't find a way to make the native module work on the server side. But you can do it the way I describe above and let your build tool (webpack or gulp or anything else) create the .ts files from .xlf files.Stnaire
It does not really solve the issue, but have you considered using the wide adopted nx-translate module? github.com/ngx-translate/coreSonu Kapoor
I've switched to Angular 1 few weeks after this message has been posted. Partially because of this, but because at the time Angular 2 wasn't looking production ready from my point of view. Too many bugs and missing functionalities. I wanted to use Angular 2 mainly for Angular Universal which still needed a lot of work. Thanks for the link anyway, I will look into it for my next project with Angular 2/4.Stnaire

1 Answers

4
votes

A solution is to pre-build packages for each language, and have a proxy detect which bundle to serve as default.

From the Angular docs on i8n:

Merge with the AOT compiler The AOT (Ahead-of-Time) compiler is part of a build process that produces a small, fast, ready-to-run application package.

When you internationalize with the AOT compiler, you must pre-build a separate application package for each language and serve the appropriate package based on either server-side language detection or url parameters.

You also need to instruct the AOT compiler to use your translation file. To do so, you use three options with the ng serve or ng build commands:

--i18nFile: the path to the translation file. --i18nFormat: the format of the translation file. --locale: the locale id. The example below shows how to serve the French language file created in previous sections of this guide:

ng build --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr