Translating Angular 2 with ASP.NET MVC

Angular 2 uses templates for the html. I would like to write my templates in Razor and have them processed on the server before they get to the client side. This way I can use resource files for translations.

Note: See my other blog post ‘Translating ASP.NET MVC with resource files’ for an explanation on how to use resource files and how to set the language on the server.

The core concept

The concept is simple, on the server I create a controller which has an action for each template. And each action has the template in its corresponding View. In Angular we simply specify the URL for the action as the URL for the template.

namespace AngularApplication.Controllers
{
    public class TemplateController : Controller
    {
        public ActionResult Welcome()
        {
            return View();
        }

        public ActionResult Locale()
        {
            return View();
        }
    }
}
@using AngularApplication.Resources
<h2>@WelcomeTexts.Welcome</h2>

<p>
    @WelcomeTexts.Introduction
</p>
import { Component } from '@angular/core';

@Component({
    selector: 'angular-application',
    templateUrl: '/Template/Welcome'
})
export class AppComponent {}

Switching languages

To switch language I create an Angular component and an Angular service. The component handles the interface for the user and the service sends the chosen language back to the server. After the user selects a new language the Angular application is reloaded.

import {Component} from '@angular/core';
import {LocaleService} from './locale.service';

@Component({
    selector: 'locale-switcher',
    templateUrl: 'Template/Locale'
})

export class LocaleComponent {
    constructor(private _localeService: LocaleService) {}

    switch(language: string) {
        this._localeService.switchLanguage(language);
    }
}
@using AngularApplication.Resources
<h2>@LocaleTexts.SwitchLanguage</h2>

<ul>
	<li><a (click)='switch("en")'>English</a></li>
	<li><a (click)='switch("sv")'>Svenska</a></li>
</ul>
import {Injectable, ApplicationRef} from '@angular/core';
import {Http, Response} from '@angular/http';
import {Observable} from 'rxjs/Observable';

@Injectable()
export class LocaleService {
    constructor(private _http: Http) {}

    switchLanguage(language: string) {
        this._http
            .post('Locale/Switch', { language: language })
            .subscribe();

        // Must reload to change language in templates
        window.location.reload();
    }
}

But wait, there’s a cache!

While the above sounds good on paper it won’t work terribly well in practice. The content of the templates depend on the language, but this isn’t reflected in the URLs. Hence any caching going on in the browser will interfere when the user changes the language. And any caching on the server will interfere when there are users with different languages.

To fix this we must add the language to the template URLs.

namespace AngularApplication.Controllers
{
    [OutputCache(Duration = 604800, VaryByParam = "language")]
    public class TemplateController : Controller
    {
        public ActionResult Welcome(string language)
        {
            return View();
        }

        public ActionResult Locale(string language)
        {
            return View();
        }
    }
}

The browser then has to use these new URLs when getting the templates. Therefore, we need to send the language to the browser. We do this by adding it to the DOM and using a bit of JavaScript to get it into a global JavaScript variable.

<div style="display:none">
    <div id="language" data-language="@ViewBag.Language"></div>
</div>
window.onload = function() {
    document.language = document
        .getElementById('language')
        .dataset['language'];
};

Lastly, we must add the language to the template URL in the component. We also have to add a type definition for document, otherwise TypeScript will complain about it being an unknown type.

import { Component } from '@angular/core';

declare var document: any;

@Component({
    selector: 'angular-application',
    templateUrl: `/Template/Welcome?language=${document.language}`
})
export class AppComponent {}
Advertisements

Let Angular do the work

From the old battlefield of JavaScript frameworks for single page applications, two seem to be emerging victorious. Angular and React. With the former being the more popular of the two and with Angular 2 on its way, it’s worth to take a look at.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal one file Angular 2 example</title>
  <script src="node/core-js/client/shim.min.js"></script>
  <script src="node/zone.js/dist/zone.js"></script>
  <script src="node/reflect-metadata/Reflect.js"></script>
  <script src="node/rxjs/bundles/Rx.umd.js"></script>
  <script src="node/@angular/core/bundles/core.umd.js"></script>
  <script src="node/@angular/common/bundles/common.umd.js"></script>
  <script src="node/@angular/compiler/bundles/compiler.umd.js"></script>
  <script src="node/@angular/platform-browser/bundles/platform-browser.umd.js"></script>
  <script src="node/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', function() {
      ng.platformBrowserDynamic.bootstrap(ng.core
        .Component({
          selector: 'angular-app',
          template: '<h2>{{greeting}}</h2>'
        })
        .Class({
          constructor: function() {
            var thisClass = this;
            thisClass.greeting = 'Hello world';
            setTimeout(function() { thisClass.greeting = 'Cruel world' }, 1700);
          }
        }));
    });
  </script>
</head>
<body>
  <angular-app>Loading...</angular-app>
</body>
</html>

In practice Angular 2 consists of numerous JavaScript files compiled from TypeScript. Npm is used for getting these files to your computer. Which scripts you then use on your site depends on what you want to do. The set of scripts used in the example are taken from the official quickstart guide.

An Angular 2 application consists of a tree of components with one component at the top. The top component is then used to bootstrap the entire application. In the example there is only component defined directly into the bootstrap function.