AppVeyor:
Since ASP.NET Core doesn't provide localization in other formats than .RESX
.
Therefore I made a core package from which can extended from to support multiple formats.
Currently supported formats are
- JSON
- YAML
Install the desired ASP.NET Core extension package:
Install-Package DotNetLocalizer.Json;
Register the localizer in the service collection and set the options:
public void ConfigureServices(IServiceCollection services)
{
// Add specific localization middleware
services.AddJsonLocalization();
// Add Mvc services to also support localization in views and DataAnnotation.
services.AddMvc()
.AddDataAnnotationsLocalization()
.AddViewLocalization();
// Configure RequestLocalizationOptions so the framework knows what we're dealing with.
services.Configure<RequestLocalizationOptions>(options =>
{
const string enUSCulture = "en-US";
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("nl-NL")
};
options.DefaultRequestCulture = new RequestCulture(culture: enUSCulture, uiCulture: enUSCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(context =>
{
// My custom request culture logic
var result = new ProviderCultureResult("en");
return Task.FromResult(result);
}));
});
}
Set your RequestLocalizationOptions:
public void Configure(IApplicationBuilder app)
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("nl-NL")
};
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
// Formatting numbers, dates, etc.
SupportedCultures = supportedCultures,
// UI strings that we have localized.
SupportedUICultures = supportedCultures
});
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
One can also add a custom folder which contains all the localization files:
public void ConfigureServices(IServiceCollection services)
{
// Add specific localization middleware where files are listed in '$PROJECT_ROOT/Some/Other/Location'
services.AddJsonLocalization(options =>
{
options.ResourcesPath = "Some/Other/Location";
});
// Add Mvc services to also support localization in views and DataAnnotation.
services.AddMvc()
.AddDataAnnotationsLocalization()
.AddViewLocalization();
// Configure RequestLocalizationOptions so the framework knows what we're dealing with.
services.Configure<RequestLocalizationOptions>(options =>
{
const string enUSCulture = "en-US";
var supportedCultures = new[]
{
new CultureInfo(enUSCulture),
new CultureInfo("nl-NL")
};
options.DefaultRequestCulture = new RequestCulture(culture: enUSCulture, uiCulture: enUSCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(context =>
{
// My custom request culture logic
var result = new ProviderCultureResult("en");
return Task.FromResult(result);
}));
});
}
Within controllers one can easily inject this service as following:
public class HomeController : Controller
{
private readonly Microsoft.Extensions.Localization.IStringLocalizer<HomeController> localizer;
public HomeController(Microsoft.Extensions.Localization.IStringLocalizer<HomeController> localizer)
{
this.localizer = localizer;
}
public IActionResult Index()
{
// Getting localized string by key
var localizedString = this.localizer["SomeKey"];
// This will get a localized string with extra object values (filled in the string)
var localizedStringWithData = this.localizer.GetString("SomeKeyWithObjects", "Object 1", "Object N");
return this.View(localizedString);
}
}
Within Views one can easily inject this service as following:
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer localizer
<div>
Key 'SomeKey' => @localizer["SomeKey"]<br/>
Key 'OtherKey' => @localizer["OtherKey"]<br/>
Key 'OtherKeyWithObject' => @localizer.GetString("SomeKeyWithObjects", "Object 1", "Object N")<br/>
</div>
Within ViewModels one can easily use localization for example with error messages:
public class UserViewModel
{
[Required(ErrorMessage = "UserNameRequired")]
public string UserName { get; set; }
}
One hell of a job is to keep track of which items have been localized and which haven't.
DotNetLocalizer.Core has an extension method called GetMissingLocalizedStrings()
.
This method can be called directly from IStringLocalizer<T>
and will give you a System.IO.FileStream
with all the missing strings from the current language.
I find it quite handy to make this accessable through an admin panel.
So I'm injecting a class everytime I use this package to keep track of the missing strings.
If you don't include the below class one can find the missing strings per language in the default or given folder: $PROJECT_ROOT/Localization/MissingStrings.{language}.json
public class AdminController : Controller
{
private readonly IStringLocalizer<AdminController> localizer;
private readonly IEnumerable<CultureInfo> cultureList;
public AdminController(IStringLocalizer<AdminController> localizer)
{
this.localizer = localizer;
this.cultureList = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("nl-NL"),
new CultureInfo("de-DE")
};
}
public IActionResult MissingTranslations()
{
var str = string.Empty;
foreach (var cultureInfo in this.cultureList)
{
var filestream = this.localizer.GetMissingLocalizedStrings(null, cultureInfo);
if (filestream == null)
{
continue;
}
str += $"{cultureInfo.TwoLetterISOLanguageName}\n";
str += new StreamReader(filestream).ReadToEnd();
str += "\n\n";
}
return this.Ok(str);
}
}
The default location for this localizer is $PROJECT_ROOT/Localization/
.
If no custom location is given the localizer will search for this directory and then loop over the files found there.
Each file has a name in the format of Strings.{language}.json
.
This means if you want to have a Dutch translation you'll have to add a file with the name Strings.nl.json
.
Right now (as we didn't need it yet) it's not taking the culture in account, so for example English will be Strings.en.json
.
A prerequisite is that the json file is well-formatted. To validate your JSON file try out the JSON Formatter Website. An example JSON file is added as reference here
{
"ComplexListObject": {
"List": [
{
"Name": "Name"
},
{
"Name": "Name Two"
}
]
},
"ComplexListObjectMissingListItems": {
"List": [
{
"Name": "Name"
},
{
"Name": "Name Two"
}
]
},
"Language": "Language",
"NestedObject": {
"NestedProperty": "Een geneste eigenschap"
},
"NestedObjectMissingChilds": {
},
"NotFoundKeyInCurrentCultureButInOtherFallback": "Key not found in current culture ('{0}') but found in other culture (fallback option)",
"ObjectExampleKey": "Give me an object: '{0}'",
"OtherKey": "Other key",
"RequestCultureProvider": "Request Culture Provider",
"SomeKey": "Some key"
}
Each file has a name in the format of Strings.{language}.yml
.
This means if you want to have a Dutch translation you'll have to add a file with the name Strings.nl.yaml
.
Right now (as we didn't need it yet) it's not taking the culture in account, so for example English will be Strings.en.yaml
.
A prerequisite is that the YAML file is well-formatted. To validate your YAML file try out the YAML Formatter Website. An example YAML file is added as reference here