Using ASP Localization with Angular2 +

You already have a ASP.Net project with Localization all set up and localized. Sure, you could convert the resx files and use Internationalization (i18n) built into Angular 2+. Well, another option is to keep the whole setup the same, and still let the server-side handle everything. This small tutorial won’t go over date and time, only string localization. Keep in mind you will not be loading all languages, rather only the one the browser is set to. So if for reasons you wanted to load all languages, for example, no refresh language change, this approach will need some modifications.

The prereqs are that you already have a project set up with your localization file already being used. An example is on any cshtml page, you should simply be able to localize using LocalizationProjectName.The_Key. Moving on, you also have an angular 2+ project already set up. If you don’t, you could use a seed project to test this out.

Step 1 – Pass the Localization file (resx file) to the view

Depending on the resource file (the resx file), we either want to do this in the Layout.cshtml file or the appropriate view/index file:

// LocalizationHelper.GetLocalizationDictionary will return us the dictionary of key:pair values 
//of the given resource file. Also, the returned dictionary will be localized.
// This means for a browser set to another language, you will get back that language and not English.
// For whatever Resource file you use, FileName.ResourceManager is what you need
var resource = LocalizationHelper.GetLocalizationDictionary(NameSpace.ResourceFileName.ResourceManager);

And what exactly is LocalizationHelper.GetLocalizationDictionary?

/// <summary>
/// Given a ResourceManager (Localization class contains the ref ResourceManager),
/// ruturn a formatted key:pair javascript object of the localization file. 
/// We use the current culture which means the correct version of the language should be used,
/// and the object will be in whatever culture was set by browser/user.
/// </summary>
public static string GetLocalizationDictionary(ResourceManager resourceMan)
{
    
    var resourceSet = resourceMan.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true);
    var resourceDictionary = resourceSet.Cast()
        .ToDictionary(r => r.Key.ToString(), r => r.Value.ToString());

    // Convert to a 'key':'value' and return a string of type { 'key':'pair', ... }
    var keyPairs = resourceDictionary.Select(x => "'" + x.Key + "':'" + x.Value.Replace("\r\n", "").Replace("'", "\\'") + "'");
    return "{" + string.Join(",", keyPairs) + "}";

}

Step 2 – Save that to a javascript global variable

globals.dictionaries = [{
    Key: 'Global',
    Value: @Html.Raw(resource)
}]

The reason I use an array is if you want to pass multiple dictionaries to angular.

Step 3 – Create a service that will consume this dictionary

Note: In my case, I combine multiple dictionaries into one to improve lookup performance, but that also means the first instance of duplicate keys will always be returned. You can modify this to your liking or requirements.

declare const globals: any;

@Injectable()
export class LocaleResouceService {

    private dictionariesFound = false;
    private localizedDict = {};

    constructor(
        private logger: SomeLoggerService
    ) {
        if (null != globals && null != globals.dictionaries) {
            if (null != globals.dictionaries) {
                this.loadDictionaries();
            } else {
                this.logger.warn('No dictionaries loaded by LocaleResouce.');
            }
        }
    }

    /**
     * Get a localized version of a string based on the provided key
     *
     * @param {string} key - They key for localizing
     * @returns {string} Localized string. Returns null if no string found.
     * @memberof LocaleResouceService
     */
    public get(key): string {
        // You can add whatever extra validation you wish to do, like typecheck for key
        let loc = this.dictionariesFound && null != key ? this.dict[key] : null;
        // You might also want to catch this during development, so could console.error instead
        if (null == loc) { this.logger.error('Key not found in the dictionaries: ', key); }
        return loc;
    }

    /**
     * Load and combine all dictionaries into one simple dictionary to keep performance in check
     * at runtime.
     *
     * @private
     * @memberof LocaleResouceService
     */
    private loadDictionaries() {
        for (const dic of globals.dictionaries) {
            this.localizedDict =  {...this.dict, ...dic.Value};
            this.logger.debug('this.localizedDict: ', this.localizedDict);
        }
        this.logger.debug('All Dictionaries loaded. this.localizedDict: ', this.localizedDict);
        this.dictionariesFound = true;
    }

}

NOTE: Do not forget to write your unit tests. Ideally, you’d have written your tests first to cover the cases, then updated the service enough to pass those tests.

Step 4: Create a directive for use in HTML

import { LocaleResouceService } from '...';

@Directive({
    selector: '[appResX]'
})
export class ResXDirective implements AfterViewInit {

    @Input('appResX') appResX;

    private dom: any;
    constructor(
        private el: ElementRef,
        private resource: LocaleResouceService
    ) {
        this.dom = this.el.nativeElement;

    }

    ngAfterViewInit() {
        let localized = null;
        if (this.appResX) {
            localized = this.resource.get(this.appResX);
        }

        if (null != localized) {
            // Only change the text if we find a valid localized string
            this.dom.innerText = localized;
        } else {
            // I want to throw an error at runtime regardless of logger
            // since its easy to miss the key, and I wish to catch it while
            // I develop
            console.error('LocaleResouce Key ', this.appResX, ' not found.');
        }
    }

}

Step 5: Use it!

You have two options: Use it in the HTML or use the service if you’re generating custom text within typescript. With HTML: Fallback Text.

I'm a software engineer primarily focusing on frontend work right now, and currently working at Tallie/Nexonia/Certify. Travel, Coding, and Photography is what I live for :)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s