Best way to expose resource strings to JavaScript files in ASP.NET MVC?
Asked Answered
S

7

20

It's easy to use resource strings (.resx) in Razor view, but how to do that in JavaScript files? Currently I'm manually passing strings from Razor view to scripts in JavaScript constructor argument but I would like a way to do this more automatically so I don't have to pass each and every resource string that I need.

Squamosal answered 20/4, 2013 at 16:48 Comment(0)
T
8

My solution is based on this http://codethug.com/2013/08/02/using-net-resources-in-javascript/ and quoting Tim Larson's (author) words:

We don’t have to do much to determine what culture to use. The web browser, when it hits the server, includes header information showing what cultures it supports. ASP.Net automatically puts this information into CultureInfo.CurrentUICulture. We pass this into the GetResourceSet method, which then returns the resource set that matches the current browser settings.

Thus, if the browser is set to French, the French resources, and only the French Resources, will be returned.

Steep 1. Create a resource file RLocalizedText.resx into App_GlobalResources folder. And create fill it with all the string.

Steep 2. Create ResourcesController

using System.Web.Mvc;

namespace PortalACA.Controllers
{
    public class ResourcesController : Controller
    {
        // GET: Resources
        public ActionResult Index()
        {
            Response.ContentType = "text/javascript";
            return View();
        }
    }
}

Steep 3. Create Index.cshtml view from ResourcesController

@using System.Collections
@using System.Globalization
@using System.Resources
@using Resources
@{
    Layout = null;
    // Get a set of resources appropriate to
    // the culture defined by the browser
    ResourceSet resourceSet =
      @RLocalizedText.ResourceManager.GetResourceSet
        (CultureInfo.CurrentUICulture, true, true);
}

// Define the empty object in javascript
var Resources = {};
@foreach (DictionaryEntry res in resourceSet)
{
    // Create a property on the javascript object for each text resource
    @:[email protected] = "@Html.Raw(
        HttpUtility.JavaScriptStringEncode(res.Value.ToString()))";
}

The choose where you want use it.

Steep 4A (Layout). If you want use it on whole site, then need put this script reference on _Layout (Shared View) over @RenderSection

<script src="@Url.Content("~/Resources/Index")"></script>
@RenderSection("scripts", required: false)

Steep 4B (View). If you want to see only in some views.

@section Scripts
{
  <script src="@Url.Content("~/Resources/Index")"></script>
}

Steep 5. Now it is the time to use it. Select a view where you need see the strings on Resources files and put this code.

@section Scripts
{
  <script>
    $(document).ready(function () {
      alert(Resources.General_Excepcion);
    });
  </script>
}

That's all Folks! Hope it help others!

Thormora answered 25/3, 2016 at 1:28 Comment(0)
A
7

The solution I have used is to use a Razor to create a json object that contains all the resource strings for a given application area eg "Customers". Like so:

<script  type="text/jscript">

@{

    ResourceSet resourceSet = JsCustomersRes.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
    var sbInitial = " var CustomersResourceObj = {"; 
    var sb = new StringBuilder(sbInitial);
    var resEnum = resourceSet.GetEnumerator(); 
    while (resEnum.MoveNext()) 
    {
        if (sb.ToString() != sbInitial)
        {
            sb.Append(",");
        }
        sb.Append("\"" + resEnum.Key + "\":\"" + resEnum.Value.ToString().Replace("\r\n", "").Replace("\"", "\\\"") + "\"");
    } 
    sb.Append("}");
} 

@(Html.Raw( sb.ToString()));

The resource file "JsCustomersRes" can be placed along with the particular controller views directory or in the shared view directory. It should have "Custom Tool" set to "PublicResXFileCodeGenerator" in the file advanced properties.

You can then get the resource string from the json object in your script:

var resString = CustomersResourceObj[key];

where "key" is the key string of the resource string you want. Just add the Razor as a partial view to your layout page or individual page as you require and that's it!

Ajmer answered 29/5, 2013 at 1:18 Comment(1)
this is a nice way to get the resources. my suggestion is that you should have keys in the resources that are used ONLY in scripts (key starting with 'script'), so that you don't load all keys from resource file (imagine that you have resource file of over 1000 keys). so in the while you would have something like: if(!resEnum.Key.StartsWith('script')) continue;Dictator
P
2

I would do something like the following:

<script type="text/javascript">
    var resourceStrings = {
        @foreach (var resource in ViewBag.Resources)
        {
            { '@resource.Key' : '@resource.Value' },
        }
    };
</script>

This assumes that you've created a dictionary of resource key/values and set a view bag property with the dictionary. You can then access those resources as a javascript object lookup.

$('label[name=gender]').html(resourceStrings['gender']);

You could potentially have a dictionary of dictionaries if you wanted to access keys by culture:

$('label[name=gender]').html(resourceStrings['en_US']['gender']);
Poler answered 20/4, 2013 at 19:5 Comment(0)
V
1

I prefer to create a Javascript object that has all the resources as properties. Also I avoid creating a partial view. Instead I pass the required language as a parameter to the script. This way you can add the script in a global template, instead of adding it in each page.

Basically I have a helper static class with the following static method:

    public static JavaScriptResult JavascriptResources(ResourceManager manager, string resourceObjectName, CultureInfo culture)
    {
        ResourceSet resourceSet = manager.GetResourceSet(culture, true, true);

        StringBuilder sb = new StringBuilder();

        sb.AppendFormat("var {0}=new Object();", resourceObjectName);

        var enumerator = resourceSet.GetEnumerator();
        while (enumerator.MoveNext())
        {
            sb.AppendFormat("{0}.{1}='{2}';", resourceObjectName, enumerator.Key,
                System.Web.HttpUtility.JavaScriptStringEncode(enumerator.Value.ToString()));
        }

        return new JavaScriptResult()
        {
            Script = sb.ToString()
        };
    }

In the project I've added the following controller:

public class ResourcesController : Controller
{
    [OutputCache(Duration = 36000, VaryByParam = "lang")]
    // Duration can be many hours as embedded resources cannot change without recompiling.
    // For the clients, I think 10 hours is good.
    public JavaScriptResult Index(string lang)
    {
        var culture = new CultureInfo(lang);
        return Helpers.JavascriptResources(Resources.Client.ResourceManager,
            "Client", culture);
    }
}

Note that I have added a parameter give the global javascript object any name you want. In the example above, I could access the translations like this in JS:

alert(Client.Info_Message);

Now to call this script, I register it in the default template (for example in ~/Shared/_layout.cshtml)

<script type="text/javascript" src="~/resources/?lang=@(ViewBag.Language)"></script>

The best way for this to work, is to create an action filter to automatically enter the language in the ViewData. This could be done like this:

public class LanguageInViewBagAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var controller = filterContext.Controller;
        if (controller != null)
            controller.ViewBag.Language = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
    }
}

You can then decorate controllers or actions with it where applicable, for example:

[LanguageInViewBag]
public class HomeController : Controller

or register as a global filter in /App_start/Filters.cs

Note: My implementation assumes you set per request the Thread.CurrentThread.CurrentCulture to the user's culture. This way the actionfilter knows the currently selected language.

Vuong answered 7/5, 2016 at 22:21 Comment(0)
T
1

All solutions, better or worse, have been mentioned. Just for completion I want to propose another solution: Do not set text from within Javascript.

Adding words from Javascript to the page that are not directly loaded from the server, is as if you would write css in an html file.

Tracy answered 6/6, 2018 at 13:40 Comment(0)
C
1

Create an action which expose your Resource Set to json

public class HomeController: Controller {

   [Route("localization.js")]
   public ActionResult Localization()
   {
       ResourceSet resourceSet = Globalization.Resources.ResourceManager.GetResourceSet(
                                       CultureInfo.CurrentUICulture, true, true);
       var result = resourceSet.Cast<DictionaryEntry>()
           .ToDictionary(x => x.Key.ToString(),
                    x => x.Value.ToString());

       return View((object)(JsonConvert.SerializeObject(result)));
   }

}

add ~/Views/Home/localization.cshtml file with the content

@model string
@{ 
    Layout = null;
}
var Resources = @Html.Raw(Model);

In Web.config, add the handler for localization.js

 <system.webServer>
   <handlers>
     <add name="Localization" path="*/localization.js" verb="GET" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
   </handlers>
 </system.webServer>

Call the action in your _Layout.cshtml or wherever you want to expose your Resources

<script type="text/javascript">
    @(Html.Action("Localization", "Home"))
</script>

Result

<script type="text/javascript">
    var Resources = {"HelloWorld" : "こんにちは世界", "NiceJob": "いいね!" };
    //Sample usage
    console.log(Resources.HelloWorld);
    console.log(Resources.NiceJob);
</script>
Contradistinction answered 19/11, 2019 at 15:11 Comment(2)
Seems nice! But the @(Html.Action("Localization", "Home")) returns a "The view 'Localization' or its master was not found or no view engine supports the searched locations. The following locations were searched:"Holytide
@NicolasBelley, yes, you're right. I forgot to add the view file. updated my answer, thanks.Contradistinction
S
0

You could do that in a more graceful way using AJAX and a JSON ActionResult.

<button />
<label for="gender"></label>

<script type="text/javascript">
    $("button").click(function () {
        $('label[for=gender]').load('@Url.Action("ResourceLabel", new { labelKey = "Gender" })');
    });
</script>

And server side, greatly simplified, but you get the idea:

public ContentResult ResourceLabel(string labelKey) {
    return Content(ResourceClass.GetString(labelKey));
}
Saxtuba answered 20/4, 2013 at 17:5 Comment(2)
Wont that create a separate request to the server for EACH label?Globuliferous
It will; there are better answers on the page for multiple labels. I'll leave it here as a proof of concept for a single label.Wachter

© 2022 - 2024 — McMap. All rights reserved.