PM> Install-Package I18N
The i18n library is designed to replace the use of .NET resources in favor of an easier, globally recognized standard for localizing web applications. Using this library simplifies localization by making it a first class citizen of views, controllers, and validation attributes.
- Globally recognized interface; localize like the big kids
- Localizes everything; views, controllers, validation attributes, and even routes!
- SEO-friendly; language selection varies the URL, and
Content-Language
is set appropriately - Automatic; no routing changes required, just use an alias method where you want localization
- Smart; knows when to hold them, fold them, walk away, or run, based on i18n best practices
To localize text in your application, use the _("text")
alias method wherever required. That's it.
Here's an example of localizing text in a Razor view:
<div id="content">
<h2>@_("Welcome to my web app!")</h2>
<h3><span>@_("Amazing slogan here")</span></h3>
<p>@_("Ad copy that would make Hiten Shah fall off his chair!")</p>
<span class="button">
<a href="@Url.Action("Plans", "Home", new { area = "" })">
<strong>@_("SEE PLANS & PRICING")</strong>
<span>@_("Free unicorn with all plans!")</span>
</a>
</span>
</div>
And here's an example in a controller:
using i18n;
namespace MyApplication
{
public class HomeController : I18NController
{
public ActionResult Index()
{
ViewBag.Message = _("Welcome to ASP.NET MVC!");
return View();
}
}
}
For use in URL-Helpers or other functions that require a plain string, you can use the __("text")
alias:
@Html.LabelFor(m => m.Name, __("First Name"))
For use in HTML attributes, you can use the _("value", "attrname")
overload:
<img @_("Our logo", "alt") src="...">...</img>
And the same overload can be used for Javascript embedded into your Razor view:
<script type="text/javascript">
$(function () {
alert(@_("Hello world!", ""));
});
</script>
In the view example above, the _("text")
alias is called on the base class of the Razor view page.
Depending on whether you're using the provided base classes or your own base class with ILocalizing
(see below),
you'll want to change the ~/Views/web.config
file to point Razor to the base class containing the alias.
Here is how you'd set up the alias using the provided I18NWebViewPage
class:
<system.web.webPages.razor>
<!-- ... -->
<pages pageBaseType="i18n.I18NWebViewPage">
<!-- ... -->
</pages>
</system.web.webPages.razor>
The central service is ILocalizingService
; anywhere you need localization, implement the ILocalizing
interface.
The package comes with default base classes for convenience, including I18NController
, I18NWebViewPage
, and
I18NWebViewPage<T>
. If your project needs prevent you from using a base class, implement ILocalizing
and defer
to ILocalizingService
; here is what implementing ILocalizing
on a Controller
might look like as a reference:
using System.Web.Mvc;
using i18n;
namespace MyApplication
{
public abstract class MyController : Controller, ILocalizing
{
private static ILocalizingService _service;
protected MyController()
{
_service = new LocalizingService();
}
public string _(string text)
{
// Prefer a stored value to browser-supplied preferences
var stored = LanguageSession.GetLanguageFromSession(ControllerContext.HttpContext);
if (stored != null)
{
return _service.GetText(text, new[] { stored });
}
// Find the most appropriate fit from the user's browser settings
var languages = HttpContext.Request.UserLanguages;
return _service.GetText(text, languages);
}
}
}
To set up automatic PO database building, add the following post-build task to your project, after
adding i18n.PostBuild.exe
as a project reference:
"$(TargetDir)i18n.PostBuild.exe" "$(ProjectDir)"
//using all available parameter:
// msgmerge: optional parameter for msgmerge
// gettext: optional parameter for gettext
// inputpaths: comma delimited input paths. Useful if the project is split in several sub projects. i.e. "inputpaths:C:\temp\Project1,C:\temp\Project2"
"$(TargetDir)i18n.PostBuild.exe" "$(ProjectDir)" "msgmerge:(optional params for msgmerge)" "gettext:(optional params for gettext)" "inputpaths:(comma delimited input paths)"
After a successful build, this task will rip through your source code, finding everywhere you've used the
ILocalizing._("text")
or the ILocalizing.__("text")
alias, and uses this to build a master .PO template file
located at /locale/messages.pot
relative to your web application folder. After the new template is constructed, any
locales that exist inside the /locale
folder are automatically merged with the template, so that new strings can be
flagged for further translation.
From here, you use any of the widely available PO editing tools (like POEdit)
to provide locale-specific text and place them in your /locale
folder relative to the provided language, i.e. locale/fr
.
If you change a PO file on the fly, i18n will update accordingly; you do not need to redeploy your application.
To participate in the automatic routing features of this library, call I18N.Register()
in your startup code;
this will register a global filter and route decorator to provide the feature.
I18N comes with the ability to build on top of your existing routes to automatically redirect language choice to
an appropriate URL suffix. When a regular route is accessed, I18N will inspect the browser's Accept-Language
header
to find the most appropriate choice of language from those you've prepared from PO files; for example, if the user's
most preferred language is fr-CA
followed by en-US
, and your application has PO files for en-US
and fr
, I18N
will select fr
as the best language choice. At this point, the regular route is then redirected to the language
specific URL, and the Content-Language
header is set accordingly.
If your user is surfing in the fr
locale, but then explicitly asks for a route with en
appended, their preference
is persisted, and all subsequent requests will redirect to the en
locale, making it simple to change language choice
for the user. You may also optionally use /?language=fr
style query string parameters to elicit the same redirection
behavior. Route requests for languages you do not have resources for will not redirect to a default resource, they
will 404 as expected.
Generally speaking, the stock validation attributes in ASP.NET MVC are closed to change, which makes it difficult to
use them with a framework like this. Still, I18N contains replacements for several ValidationAttributes
, including
a base class for extending to build your own. These replacements function similarly to the originals, but use the
ILocalizing
interface; just use normally and any derived text will pass through the localization process. Any custom
interactions that occur using these attributes elsewhere in the framework, however, will not work as expected. For the
most part, you should be able to swap System.ComponentModel.DataAnnotations
and i18n.DataAnnotations
namespaces
cleanly.
Your locale
folder is exposed to HTTP requests as-is, just like a typical log directory, so remember to block all requests
to this folder by adding a Web.config
file.
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add path="*" verb="*" type="System.Web.HttpNotFoundHandler"/>
</httpHandlers>
</system.web>
<system.webServer>
<handlers>
<remove name="BlockViewHandler"/>
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
</handlers>
</system.webServer>
</configuration>
There's lot of room for further enhancements and features to this library, and you are encouraged to fork it and contribute back anything new. Specifically, these would be great places to add more functionality:
- Help me fix the bugs! Chances are I don't ship in your language. Fix what hurts. Please?
- Better parsing and handling of PO files for more general purposes / outside editors
- Additional validation attributes (though these days I think this feature should be tossed)
- Generic handling that will work in ASP.NET Web API
- Support for additional storage mechanisms beyond ASP.NET Session (i.e. cookies, closures, etc.)