The package which is used in this article is avaible on MyGet on this url: https://www.myget.org/F/orchardcore-preview/api/v3/index.json
This article walks you through the steps for using Portable Object files (PO files) inside your ASP.NET Core application.
PO files are files that contain the translated strings for a given language. They reveal very useful as contrary to standard resx files, PO files support pluralization and are distributed as text files.
Here is a sample PO file that contains the translation for two strings in French, including one with its plural form.
fr.po
#: Services/EmailService.cs:29
msgid "Enter a comma separated list of email addresses."
msgstr "Entrez une liste d'emails séparés par une virgule."
#: Views/Email.cshtml:112
msgid "The email address is \"{0}\"."
msgid_plural "The email addresses are \"{0}\"."
msgstr[0] "L'adresse email est \"{0}\"."
msgstr[1] "Les adresses email sont \"{0}\""
This example uses the following syntax:
#:
: A comment used for editors to understand what the context of the string to translate is, as the same string might be translated differently depending on where it is being used.msgid
: The untranslated string.msgstr
: The translated string.
In the case of pluralization support, more entries can be defined.
msgid_plural
: The unstranslated plural string.msgstr[0]
: The translated string for the case 0.msgstr[N]
: The translated string for the case N.
The specification for PO files can be found on this site: https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
This example is based on a sample ASP.NET Core MVC application generated from a Visual Studio 2017 template.
Add a reference to the Nuget package named OrchardCore.Localization.Core
.
The project file should contain a new line similar to this:
<PackageReference Include="OrchardCore.Localization.Core" Version="1.0.0-beta1-*" />
In your Startup class add the required services:
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.AddPortableObjectLocalization();
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
};
opts.DefaultRequestCulture = new RequestCulture("en-US");
opts.SupportedCultures = supportedCultures;
opts.SupportedUICultures = supportedCultures;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseRequestLocalization();
...
}
...
Edit the file About.cshtml as is:
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>
<p>@Localizer["Hello world!"]</p>
An IViewLocalizer
instance is injected, and then used to translate the text "Hello world!"
.
Next step is about creating a PO file which will contain the translations in French.
Create the file named fr.po
in your application root folder with this content:
fr.po
msgid "Hello world!"
msgstr "Bonjour le monde!"
This file only contains the string to translate and the translated string.
Translations will fallback to their parent culture if necessary. In this example if the requested
culture is fr-FR
or fr-CA
the fr.po
file will still be used.
Run your application an navigate to the url /Home/About
.
The text Hello world! should be displayed.
Navigate to the url /Home/About?culture=fr-FR
.
The text Bonjour le monde! should be displayed.
PO files support pluralization forms, which is useful when the same string needs to be translated differently based on a cardinality. This task is made complicated by the fact that each language defines custom rules to select which string to use based on the cardinality.
The Orchard Localization package provides an API to invoke these different plural forms automatically.
If the previous fr.po
file, add the following content:
msgid "There is one item."
msgid_plural "There are {0} items."
msgstr[0] "Il y a un élément."
msgstr[1] "Il y a {0} éléments."
In the previous example we used English and French strings. English and French have only two pluralization forms and share the same form rules, which is that a cardinality of 1 is mapped to the first plural form, and any other cardinality is mapped to the second plural form.
Unfortunately all the languages don't share the same rules. As an example we will use the Czech language as it has three plural forms.
Create the cs.po
file as is, and not how the pluralization needs three different translations:
msgid "Hello world!"
msgstr "Ahoj světe!!"
msgid "There is one item."
msgid_plural "There are {0} items."
msgstr[0] "Existuje jedna položka."
msgstr[1] "Existují {0} položky."
msgstr[2] "Existuje {0} položek."
Add "cs"
to the list of supported cultures in Startup.cs to accept Czech localizations:
...
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
new CultureInfo("cs"),
};
...
Edit the view Views/Home/About.cshtml
to render localized plural strings for several cardinalities:
<p>@Localizer.Plural(1, "There is one item.", "There are {0} items.")</p>
<p>@Localizer.Plural(2, "There is one item.", "There are {0} items.")</p>
<p>@Localizer.Plural(5, "There is one item.", "There are {0} items.")</p>
In a real world scenario you would use a variable to represent the
count
variable. Here we repeat the same code with three different values to expose a very specific case.
Now by switching cultures you should see:
For /Home/About
:
There is one item.
There are 2 items.
There are 5 items.
For /Home/About?culture=fr
:
Il y a un élément.
Il y a 2 éléments.
Il y a 5 éléments.
For /Home/About?culture=cs
:
Existuje jedna položka.
Existují 2 položky.
Existuje 5 položek.
Note that for the Czech culture the three tranlations are different, while the French and English one have the same construction for the two last translated strings.
Often applications contain the same strings to be translated in several places while requiring the flexibility to define different translations. A PO files supports the notion of context that can be used to categorize the string that is represented.
The Portable Object localization services can use the name of the full class name or view name that is used when translating a string. This is done
by setting the value on the msgctx
entry.
Taking the previous example, the entry could have been written as is: fr.po
msgctx Views.Home.About
msgid "Hello world!"
msgstr "Bonjour le monde!"
When no context is specified, it will be used as a fallback for any string whose context doesn't match any specific one.
You can change the default location of PO files in the application by setting a base folder name.
services.AddPortableObjectLocalization(options => options.ResourcesPath = "Localization");
In this example the PO files will be loaded from the Localization
folder.
In case some more complex logic is needed to locate PO files, the interface OrchardCore.Localization.PortableObject.ILocalizationFileLocationProvider
can be implemented
and registered as a service. For instance if PO files can be in a difference locations, or have to be found in a hierarchy of folders.
The package includes a Plural
extension method that is specific to two plural forms as this is the most common. If your main language is different and requires more plural forms for instance, you should create your own extension method. This way you won't need to provide any localization file for the default language as all the original strings will already be available directly in the code.
You can also use the more generic Plural(int count, string[] pluralForms, params object[] arguments)
overload that accepts a string array of translation strings.
How to localize DataAnnotation?
`using System;
using System.ComponentModel.DataAnnotations;
namespace WebBaseProject.ViewModels {
public class UserViewModel
{
public UserViewModel(String pName) {
Name = pName;
}
}
`