Pseudorandom Knowledge

Translating Angular 2 with ASP.NET

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’ 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.

TemplateController.cs
namespace AngularApplication.Controllers
{
    public class TemplateController : Controller
    {
        public ActionResult Welcome()
        {
            return View();
        }
 
        public ActionResult Locale()
        {
            return View();
        }
    }
}
Welcome.cshtml
@using AngularApplication.Resources
<h2>@WelcomeTexts.Welcome</h2>
 
<p>
    @WelcomeTexts.Introduction
</p>
app.component.ts
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.

locale.component.ts
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);
    }
}
Locale.cshtml
@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>
locale.service.ts
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.

TemplateController.cs
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.

Index.cshtml
<div style="display:none">
    <div id="language" data-language="@ViewBag.Language"></div>
</div>
language-init.js
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.

app.component.ts
import { Component } from '@angular/core';
 
declare var document: any;
 
@Component({
    selector: 'angular-application',
    templateUrl: `/Template/Welcome?language=${document.language}`
})
export class AppComponent {}