Reputation: 1885
I am using IStringLocalizer
approach to localize my Blazor app as discussed here.
Injecting the IStringLocalizer
on razor pages works great. I also need this to localize some services - whether scoped or even singleton services.
Using constructor injection to inject my IStringLocalizer
service into the service works. However, when users change the language via UI, the service (whether singleton or scoped) keeps the initial IStringLocalizer
- i.e. the one with the original language used when starting the app, not the updated language selected by the user.
What is the suggested approach to retrieve the updated IStringLocalizer
from code?
EDIT
To prevent more details, here is some piece of code.
First, I add a Resources
folder and create there a default LocaleResources.resx
(with public modifiers) and a LocaleResources.fr.resx
file, which contain the key-value pairs for each language.
Supported cultures are defined in the appsettings.json
file as
"Cultures": {
"en-US": "English",
"fr": "Français (Suisse)",
...
}
In startup, I register the Resources
folder and the supported cultures :
public void ConfigureServices(IServiceCollection services {
...
services.AddLocalization(options => options.ResourcesPath = "Resources");
...
services.AddSingleton<MySingletonService>();
services.AddScoped<MyScopedService>();
}
// --- helper method to retrieve the Cultures from appsettings.json
protected RequestLocalizationOptions GetLocalizationOptions() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
var supportedCultures = cultures.Keys.ToArray();
var localizationOptions = new RequestLocalizationOptions()
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
return localizationOptions;
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
...
app.UseRequestLocalization(GetLocalizationOptions());
...
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
I created an empty LocaleResources.razor
control at the root of the project (this is a trick used to inject a single resource file to all components).
I included a routing controller to change language :
[Route("[controller]/[action]")]
public class CultureController : Controller {
public IActionResult SetCulture(string culture, string redirectUri) {
if (culture != null) {
HttpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(culture)));
}
return LocalRedirect(redirectUri);
}
}
And the language UI switcher looks like this (I use SyncFusion control here, but it could be any lookup actually, that shouldn't really matter)
@inject NavigationManager NavigationManager
@inject IConfiguration Configuration
<SfComboBox TValue="string" TItem="Tuple<string, string>" Placeholder="Select language" DataSource="@Cultures"
@bind-Value="selectedCulture" CssClass="lan-switch" Width="80%">
<ComboBoxFieldSettings Text="Item2" Value="Item1"></ComboBoxFieldSettings>
</SfComboBox>
<style>
.lan-switch {
margin-left: 5%;
}
</style>
@code {
string _activeCulture = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
private string selectedCulture {
get => _activeCulture;
set {
_activeCulture = value;
SelectionChanged(value);
}
}
List<Tuple<string, string>> Cultures;
protected override void OnInitialized() {
var cultures = Configuration.GetSection("Cultures")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);
Cultures = cultures.Select(p => Tuple.Create<string, string>(p.Key, p.Value)).ToList();
}
protected override void OnAfterRender(bool firstRender) {
if (firstRender && selectedCulture != AgendaSettings.SelectedLanguage) {
selectedCulture = AgendaSettings.SelectedLanguage;
}
}
private void SelectionChanged(string culture) {
if (string.IsNullOrWhiteSpace(culture)) {
return;
}
AgendaSettings.SelectedLanguage = culture;
var uri = new Uri(NavigationManager.Uri)
.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var query = $"?culture={Uri.EscapeDataString(culture)}&" +
$"redirectUri={Uri.EscapeDataString(uri)}";
NavigationManager.NavigateTo("/Culture/SetCulture" + query, forceLoad: true);
}
}
Finally, to the injection. I inject the IStringLocalizer
to pages as follows and it works perfectly fine on razor controls:
@inject IStringLocalizer<LocaleResources> _loc
<h2>@_loc["hello world"]</h2>
Above, when I change language, the page displays the value in the corresponding resource file.
Now, to services: the MySingletonService
and MyScopedService
are registered at startup. They both have a constructor like
protected IStringLocalizer<LocaleResources> _loc;
public MySingletonService(IStringLocalizer<LocaleResources> loc) {
_loc = loc;
}
public void someMethod() {
Console.WriteLine(_loc["hello world"])
}
I run someMethod
on a timer. Strangely, when I break on the above line, the result seems to oscillate : once it returns the default language's value, once the localized one...!
Upvotes: 1
Views: 1652
Reputation: 1885
The answer to my question was: your code is correct!
The reason, I found out, is that I use a Scoped service that is started on the default App's start page:
protected async override Task OnAfterRenderAsync(bool firstRender) {
if (firstRender) {
MyScopedService.StartTimer();
}
await base.OnAfterRenderAsync(firstRender);
}
When users change language, the whole page is refreshed and a new instance of the scoped service is created and timer started. As my service did not implement IDisposable
, the timer was not actually stopped.
So 2 solutions here:
Upvotes: 0