Localization
The localization concern analyzes incoming requests for language preferences and allows the application logic to serve content based on those.
Basic Usage
By default, the concern will read the Accept-Language
header
and set the CultureInfo.CurrentUICulture
accordingly.
using System.Globalization;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.I18n;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Practices;
using GenHTTP.Modules.Webservices;
var localization = Localization.Create();
var app = Layout.Create()
.AddService<LocalizedService>("service")
.Add(localization);
await Host.Create()
.Handler(app)
.Defaults()
.Development()
.Console()
.RunAsync();
public class LocalizedService
{
[ResourceMethod]
public string GetLocalized() => CultureInfo.CurrentUICulture.ToString();
}
using System.Globalization;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Functional;
using GenHTTP.Modules.I18n;
using GenHTTP.Modules.Practices;
var localization = Localization.Create();
var app = Inline.Create()
.Get("/service/", () => CultureInfo.CurrentUICulture.ToString())
.Add(localization);
await Host.Create()
.Handler(app)
.Defaults()
.Development()
.Console()
.RunAsync();
using System.Globalization;
using GenHTTP.Api.Protocol;
using GenHTTP.Engine.Internal;
using GenHTTP.Modules.Controllers;
using GenHTTP.Modules.I18n;
using GenHTTP.Modules.Layouting;
using GenHTTP.Modules.Practices;
var localization = Localization.Create();
var app = Layout.Create()
.AddController<LocalizedController>("service")
.Add(localization);
await Host.Create()
.Handler(app)
.Defaults()
.Development()
.Console()
.RunAsync();
public class LocalizedController
{
[ControllerAction(RequestMethod.Get)]
public string Index() => CultureInfo.CurrentUICulture.ToString();
}
Running and accessing this example app via http://localhost:8080/service/ in your browser will print the preferred locale of your client.
Language Negotiation
Typically your application will not support any language,
so the client and the server need to negotiate a language
that suits the client and the server can provide. Therefore,
the client will send a list of supported languages and their
ranking in the Accept-Language
header, such as de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,de-CH;q=0.5
.
On the server side, you need to specify the cultures that are supported by your application, either statically or dynamically:
var localization = Localization.Create();
// statically set supported cultures
CultureInfo[] supported = [CultureInfo.CreateSpecificCulture("de"), CultureInfo.CreateSpecificCulture("fr")];
localization.Supports(supported);
// dynamically evaluate support
localization.Supports(culture => culture.EnglishName.Contains("ish"));
The localization concern will then try to find the language requested by the client which is supported by the server and has the highest priority on the client-side. If there is no intersection between the two, the logic will fallback to the default locale that is used by the server environment. The default value can be overridden as needed:
var localization = Localization.Create()
.Default(CultureInfo.CreateSpecificCulture("pl-PL"));
Parameter Source
As shown above, the server will analyze the Accept-Language
header by
default to check the languages requested by the client. For API or web
applications you might want a different source instead:
// read a query parameter, e.g. ?language=es
localization.FromQuery("language");
// read a cookie
localization.FromCookie("__my_app_language");
// read a custom header
localization.FromHeader("X-Custom-Language");
// read directly from the request, e.g. /de/...
localization.FromRequest(r => r.Target.Current?.Value);
If needed, you can specify multiple sources at the same time.
Consuming the Language
As shown above, the server will set the CurrentUICulture
of the current
thread. This behavior can be adjusted as needed:
// set current culture instead
localization.Setter(currentCulture: true, currentUICulture: false);
// use a custom logic
localization.Setter((r, c) => r.GetUser<MyAppUser>()?.Language = c);
Running in Docker
If you run your app in Docker (especially using an alpine-based images), setting the current culture in .NET will throw an exception. For this to work, you will need to set the following settings.
In your .csproj
file:
<InvariantGlobalization>false</InvariantGlobalization>
In your docker file:
RUN apk add --no-cache icu-data-full icu-libs
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false