Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Universal with i18n (Server Side Rendering)

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?

like image 608
Stnaire Avatar asked Dec 12 '16 11:12

Stnaire


People also ask

Can angular be used for server-side rendering?

Angular applications are client-side applications that execute on the browser - which means they are rendered on the client, not on the server. You can add server-side rendering to your app using Angular Universal.

Is angular Universal production ready?

Yes, it will be a production build if your angular.

Should I use angular universal?

A primary benefit for using Angular Universal is that it improves web crawler support for enhanced Search Engine Optimization (SEO). With traditional client-side rendered SPAs, anything that is not in that shell of an . html is all rendered by the JavaScript.


1 Answers

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 
like image 190
jornare Avatar answered Oct 03 '22 15:10

jornare