43

I'm currently using .resx files to manage my server side resources for .NET.

the application that I am dealing with also allows developers to plugin JavaScript into various event handlers for client side validation, etc.. What is the best way for me to localize my JavaScript messages and strings?

Ideally, I would like to store the strings in the .resx files to keep them with the rest of the localized resources.

I'm open to suggestions.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
SaaS Developer
  • 9,835
  • 7
  • 34
  • 45

12 Answers12

27

A basic JavaScript object is an associative array, so it can easily be used to store key/value pairs. So using JSON, you could create an object for each string to be localized like this:

var localizedStrings={
    confirmMessage:{
        'en/US':'Are you sure?',
        'fr/FR':'Est-ce que vous êtes certain?',
        ...
    },

    ...
}

Then you could get the locale version of each string like this:

var locale='en/US';
var confirm=localizedStrings['confirmMessage'][locale];
Frédéric Hamidi
  • 258,201
  • 41
  • 486
  • 479
Joel Anair
  • 13,832
  • 3
  • 31
  • 36
  • 22
    The problem with this approach is that you load all strings for all languages. However its likely the server will know either through browser provided clues or through user preferences what language is needed. Sending a single language file would be better. – AnthonyWJones Sep 19 '08 at 18:03
  • Since I write OO JS, each object defines default English strings that it uses on the prototype. If I need another language, I just load another file the modifies the prototype strings. That way I don't need to load all the strings for one language at once (they're dynamically loaded). The drawback is that you may need to repeat some strings across objects. – Ruan Mendes Aug 12 '11 at 16:40
  • 3
    simply inverting the order 'localizedStrings[culture][key]' resolves the issue about loading multiple cultures.. it could also allow the 'neutral' culture to be used as the prototype for all the others (providing a seamless fallback when a culture doesn't define a particular key) – Andrew Theken Feb 04 '12 at 12:38
  • 1
    I'm using [T4](http://jiffie.blogspot.com/2012/11/localized-javascript-resources-based-on.html) to generate exactly the code suggested above. – Jochen van Wylick Nov 24 '12 at 06:35
13

Inspired by SproutCore You can set properties of strings:

'Hello'.fr = 'Bonjour';
'Hello'.es = 'Hola';

and then simply spit out the proper localization based on your locale:

var locale = 'en';
alert( message[locale] );
11

After Googling a lot and not satisfied with the majority of solutions presented, I have just found an amazing/generic solution that uses T4 templates. The complete post by Jochen van Wylick you can read here:

Using T4 for localizing JavaScript resources based on .resx files

Main advantages are:

  1. Having only 1 place where resources are managed ( namely the .resx files )
  2. Support for multiple cultures
  3. Leverage IntelliSense - allow for code completion

Disadvantages:

The shortcomings of this solution are of course that the size of the .js file might become quite large. However, since it's cached by the browser, we don't consider this a problem for our application. However - this caching can also result in the browser not finding the resource called from code.


How this works?

Basically he defined a T4 template that points to your .resx files. With some C# code he traverses each and every resource string and add it to JavaScript pure key value properties that then are output in a single JavaScript file called Resources.js (you can tweak the names if you wish).


T4 template [ change accordingly to point to your .resx files location ]

<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ assembly name="System.Windows.Forms" #>
<#@ import namespace="System.Resources" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.IO" #>
<#@ output extension=".js"#>
<#
 var path = Path.GetDirectoryName(Host.TemplateFile) + "/../App_GlobalResources/";
 var resourceNames = new string[1]
 {
  "Common"
 };

#>
/**
* Resources
* ---------
* This file is auto-generated by a tool
* 2012 Jochen van Wylick
**/
var Resources = {
 <# foreach (var name in resourceNames) { #>
 <#=name #>: {},
 <# } #>
};
<# foreach (var name in resourceNames) {
 var nlFile = Host.ResolvePath(path + name + ".nl.resx" );
 var enFile = Host.ResolvePath(path + name + ".resx" );
 ResXResourceSet nlResxSet = new ResXResourceSet(nlFile);
 ResXResourceSet enResxSet = new ResXResourceSet(enFile);
#>

<# foreach (DictionaryEntry item in nlResxSet) { #>
Resources.<#=name#>.<#=item.Key.ToString()#> = {
 'nl-NL': '<#= ("" + item.Value).Replace("\r\n", string.Empty).Replace("'","\\'")#>',
 'en-GB': '<#= ("" + enResxSet.GetString(item.Key.ToString())).Replace("\r\n", string.Empty).Replace("'","\\'")#>'
 };
<# } #>
<# } #>

In the Form/View side

To have the correct translation picked up, add this in your master if you're using WebForms:

<script type="text/javascript">

    var locale = '<%= System.Threading.Thread.CurrentThread.CurrentCulture.Name %>';

</script>

<script type="text/javascript" src="/Scripts/Resources.js"></script>

If you're using ASP.NET MVC (like me), you can do this:

<script type="text/javascript">

    // Setting Locale that will be used by JavaScript translations
    var locale = $("meta[name='accept-language']").attr("content");

</script>

<script type="text/javascript" src="/Scripts/Resources.js"></script>

The MetaAcceptLanguage helper I got from this awesome post by Scott Hanselman:

Globalization, Internationalization and Localization in ASP.NET MVC 3, JavaScript and jQuery - Part 1

public static IHtmlString MetaAcceptLanguage<T>(this HtmlHelper<T> html)
{
     var acceptLanguage =
         HttpUtility.HtmlAttributeEncode(
                     Thread.CurrentThread.CurrentUICulture.ToString());

      return new HtmlString(
      String.Format("<meta name=\"{0}\" content=\"{1}\">", "accept-language",
                    acceptLanguage));
 }

Use it

var msg = Resources.Common.Greeting[locale];
alert(msg);
Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
4

I would use an object/array notation:

var phrases={};
phrases['fatalError'] ='On no!';

Then you can just swap the JS file, or use an Ajax call to redefine your phrase list.

Tracker1
  • 19,103
  • 12
  • 80
  • 106
Diodeus - James MacFarlane
  • 112,730
  • 33
  • 157
  • 176
  • 2
    Note that this isn't correct. phrases will be an Array, but you're just setting one of its properties to a string (eg phrases.fatalError = 'Oh no'; is equivalent to your second line). Use object literals for cleanliness instead. – millenomi Sep 19 '08 at 18:03
4

With a satellite assembly (instead of a resx file) you can enumerate all strings on the server, where you know the language, thus generating a Javascript object with only the strings for the correct language.

Something like this works for us (VB.NET code):

Dim rm As New ResourceManager([resource name], [your assembly])
Dim rs As ResourceSet = 
    rm.GetResourceSet(Thread.CurrentThread.CurrentCulture, True, True)
For Each kvp As DictionaryEntry In rs
    [Write out kvp.Key and kvp.Value]
Next

However, we haven't found a way to do this for .resx files yet, sadly.

Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
Erik Hesselink
  • 2,420
  • 21
  • 25
4

JSGettext does an excellent job -- dynamic loading of GNU Gettext .po files using pretty much any language on the backend. Google for "Dynamic Javascript localization with Gettext and PHP" to find a walkthrough for JSGettext with PHP (I'd post the link, but this silly site won't let me, sigh...)

Edit: this should be the link

Fabio
  • 18,856
  • 9
  • 82
  • 114
3

There's a library for localizing JavaScript applications: https://github.com/wikimedia/jquery.i18n

It can do parameter replacement, supports gender (clever he/she handling), number (clever plural handling, including languages that have more than one plural form), and custom grammar rules that some languages need.

The strings are stored in JSON files.

The only requirement is jQuery.

Amir E. Aharoni
  • 1,308
  • 2
  • 13
  • 25
2

Well, I think that you can consider this. English-Spanish example:

Write 2 Js Scripts, like that:

en-GB.js
lang = {
    date_message: 'The start date is incorrect',
    ...
};
es-ES.js
lang = {
    date_message: 'Fecha de inicio incorrecta',
    ...
};

Server side - code behind:

Protected Overrides Sub InitializeCulture()
    Dim sLang As String 
    sLang = "es-ES" 

    Me.Culture = sLang
    Me.UICulture = sLang
    Page.ClientScript.RegisterClientScriptInclude(sLang & ".js", "../Scripts/" & sLang & ".js")

    MyBase.InitializeCulture()
End Sub

Where sLang could be "en-GB", you know, depending on current user's selection ...

Javascript calls:

alert (lang.date_message);

And it works, very easy, I think.

Morcilla de Arroz
  • 2,104
  • 22
  • 29
2

I did the following to localize JavaScript for a mobile app running HTML5:

1.Created a set of resource files for each language calling them like "en.js" for English. Each contained the different strings the app as follows:


        var localString = {
        appName: "your app name",
        message1: "blah blah"
      };

2.Used Lazyload to load the proper resource file based on the locale language of the app: https://github.com/rgrove/lazyload

3.Pass the language code via a Query String (As I am launching the html file from Android using PhoneGap)

4.Then I wrote the following code to load dynamically the proper resource file:


var lang = getQueryString("language");
localization(lang);
function localization(languageCode) {
    try {
        var defaultLang = "en";
        var resourcesFolder = "values/";
        if(!languageCode || languageCode.length == 0)
            languageCode = defaultLang;
        // var LOCALIZATION = null;
        LazyLoad.js(resourcesFolder + languageCode + ".js", function() {
            if( typeof LOCALIZATION == 'undefined') {
                LazyLoad.js(resourcesFolder + defaultLang + ".js", function() {
                    for(var propertyName in LOCALIZATION) {
                        $("#" + propertyName).html(LOCALIZATION[propertyName]);
                    }
                });
            } else {
                for(var propertyName in LOCALIZATION) {
                    $("#" + propertyName).html(LOCALIZATION[propertyName]);
                }
            }
        });
    } catch (e) {
        errorEvent(e);
    }
}
function getQueryString(name)
{
  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  var regexS = "[\\?&]" + name + "=([^]*)";
  var regex = new RegExp(regexS);
  var results = regex.exec(window.location.href);
  if(results == null)
    return "";
  else
    return decodeURIComponent(results[1].replace(/\+/g, " "));
}

5.From the html file I refer to the strings as follows:


    span id="appName"
Jaime Botero
  • 2,263
  • 2
  • 22
  • 14
  • Can you explain what exactly this block of code is doing : if( typeof LOCALIZATION == 'undefined') { LazyLoad.js(resourcesFolder + defaultLang + ".js", function() { for(var propertyName in LOCALIZATION) { $("#" + propertyName).html(LOCALIZATION[propertyName]); } }); } else { for(var propertyName in LOCALIZATION) { $("#" + propertyName).html(LOCALIZATION[propertyName]); } } – shailbenq Oct 23 '12 at 01:09
0

Expanding on diodeus.myopenid.com's answer: Have your code write out a file containing a JS array with all the required strings, then load the appropriate file/script before the other JS code.

Lasar
  • 5,175
  • 4
  • 24
  • 22
-1

We use MVC and have simply created a controller action to return a localized string. We maintain the user's culture in session and set the thread culture before any call to retrieve a language string, AJAX or otherwise. This means we always return a localized string.

I'll admit, it isn't the most efficient method but getting a localised string in javascript is seldom required as most localization is done in our partial views.

Global.asax.cs

protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
{
    if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
    {
        // Set the current thread's culture
        var culture = (CultureInfo)Session["CultureInfo"];
        if (culture != null)
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
        }
    }
}

Controller Action

public string GetString(string key)
{
    return Language.ResourceManager.GetString(key);
}

Javascript

/*
    Retrieve a localized language string given a lookup key.
    Example use:
      var str = language.getString('MyString');
*/
var language = new function () {
    this.getString = function (key) {
        var retVal = '';
        $.ajax({
            url: rootUrl + 'Language/GetString?key=' + key,
            async: false,
            success: function (results) {
                retVal = results;
            }
        });
        return retVal;
    }
};
hhh575
  • 1
-1

The MSDN way of doing it, basically is:

You create a separate script file for each supported language and culture. In each script file, you include an object in JSON format that contains the localized resources values for that language and culture.

I can't tell you the best solution for your question, but IMHO this is the worst way of doing it. At least now you know how NOT to do it.

brunosp86
  • 670
  • 1
  • 10
  • 21
  • 9
    Just cause it's MS, it's bad? FAILED attempt at being funny! That is the most widely used way. If this is the worst way, you need to at least explain why! – Ruan Mendes Aug 12 '11 at 16:43
  • @Juan Attempt to be funny? My guesses are the way you're localizing your strings in js is exactly the MSDN way, a.k.a "CPP - Copy and Paste Programming" and couldn't bear someone criticizing it. "The most widely used way" is to violate DRY principle according to your statement, but you probably don't see it as a code smell. – brunosp86 Aug 12 '11 at 19:14
  • 2
    How is that a violation of DRY? Even if your argument is true, the answer is still weak since it doesn't explain anything. – Ruan Mendes Aug 12 '11 at 23:00
  • IMHO, this is one of the most DRY solutions. The strings are only in one file, in one place—and that file is referenced by anywhere that needs to display strings. – Andrew Dunkman Feb 03 '12 at 22:34
  • 1
    jQuery UI also uses the "MSDN way" that you're mocking. Love how everything Microsoft does is "wrong" to some people. – Josh Earl Mar 15 '12 at 23:54
  • The problem is that they are advocating replicating the logic (verbatim) for every single language you want to support. They are also suggesting mixing your string resources with your code. Neither is a good idea. Jquery.i18n mentioned above does not do this. They separate data (string resources) from code, and similarly keep 1 copy of your logic, again separate from data. – Julian Jan 08 '13 at 06:25
  • I believe they are recommending you have a script include, a separate file for each language.. with one exports (which is an object with key/value pairs) for the specific language.. and include that separately from your code/logic.. this is precisely what jQuery.i18n uses. – Tracker1 Sep 30 '13 at 19:00
  • guys guys, chill, the way that is proposed on MSDN isn't as good because if you actually use a .resx file you will need to repeat it in JS. so far the DRYies way is: https://stackoverflow.com/a/14782225/1930376 – João Antunes Sep 27 '17 at 09:46