Changing the language of an IStringLocalizer
Asked Answered
I

4

7

I am attempting to dynamically set the language of an IStringLocalizer at run time. The only available method that seems to do this is IStringLocalizer.WithCulture. However, attempting to use it results in a deprecation warning.

public IStringLocalizer GetLocalizer(string locale) {
    this.localizerFactory.Create(typeof(CommonResources)).WithCulture(new CultureInfo(locale));
}

I am not using ASP, I am doing this in an IHostedService that handles user interaction from another source (various web chat APIs). This service needs to conform to the language set for the chat server or channel by the admins (stored in database).

What is the correct, current way of setting the language of an IStringLocalizer? Should I use another class entirely?

Ironwork answered 14/3, 2020 at 14:48 Comment(4)
Would this SO answer help you?Zanthoxylum
@SimonWilson Unfornutately, no. In the answer they only seem to provide languages at the start of the application, in the form of a default value and a collection of available values. If they programmatically set the lagnuage of the IStringLocalizer, it is very well hidden. Moreover, the example is for ASP applications, which is not my case.Ironwork
Moreover, in a comment the OP themselves ask how the provided answer changes the cuture dynamically.Ironwork
probably you can create a middleware that, based on every request can set the currentCulture or the UICulture. The localizer would automatically resolve the string based on the culture set.Meingoldas
B
23

This should do it. You need to set CultureInfo.CurrentUICulture to the culture you want before getting the string value.

    private string GetStringValue(string stringName, string culture)
    {
        var specifiedCulture = new CultureInfo(culture);
        CultureInfo.CurrentCulture = specifiedCulture;
        CultureInfo.CurrentUICulture = specifiedCulture;
        var options = Options.Create(new LocalizationOptions { ResourcesPath = "Resources" });
        var factory = new ResourceManagerStringLocalizerFactory(options, new LoggerFactory());
        var localizer = new StringLocalizer<RecipeController>(factory);
        return localizer[stringName];
    }

If you have an instance of IStringLocalizer you can use it instead of creating a new one.

Becquerel answered 1/7, 2020 at 22:19 Comment(4)
There used to be method IStringLocalizer.WithCulture but it's now obsolete. To set CurrentCulture and CurrentUICulture is recommended solution from Microsoft documentation.Unclose
The answer seems correct to me. The comment from @LukášKmoch may be correct (I did not find such documentation), but I noticed that CurrentCulture does not do any difference on this scenario. CurrentUICulture seems to be the main thing to be set.Glidebomb
@Glidebomb CurrentCulture will impact currency, date/time and number conversions. CurrentUICulture will control which string that is being looked up. Say you had the localized string TOTAL_AMOUNT "The total amount is {0:C}". Then you will need to set both in order for StringLocalizer["TOTAL_AMOUNT", endOfYearAmount] to ensure both the string and the currency argument is displayed correctly.Skinhead
I don’t have much experience with CultureInfo, is it thread-safe to set CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture? At first glance it appears as though you are setting a static property and thus the change would affect all threads at once.Eraste
S
2

Because the IStringLocalizer is implemented on ResourceManagerStringLocalizer setting CultureInfo.CurrentUICulture is the only way to change the culture on the fly.

IStringLocalizer Localizer = ServiceHelper.GetService<IStringLocalizer<AppStrings>>();
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
var HelloEN = Localizer["Hello"];
CultureInfo.CurrentUICulture = new CultureInfo("fr-FR");
var HelloFR = Localizer["Hello"];

However, the automatically generated ResX file has ResourceManager which it access with either CultureInfo.CurrentUICulture or its local Culture static member. Here's a demonstration of both:

CultureInfo.CurrentUICulture = new CultureInfo("en-US");
var HelloEN = AppStrings.Hello;
CultureInfo.CurrentUICulture = new CultureInfo("fr-FR");
var HelloFR = AppStrings.Hello;
AppString.Culture = new CultureInfo("es-ES");
var HelloES = AppStrings.Hello;

The current ResourceManagerStringLocalizer is unaware of the AppStrings.Culture property, so, you cannot use it with ResrouceManagerStringLocalizer.

A solution is to build your own using reflection to retrieve the Culture and ResourceManager from the ResX:

public class ResourceStringLocalizer<TStringResource> : IStringLocalizer
{
    private Type _stringResource;
    private PropertyInfo _cultureProperty;
    private PropertyInfo _resourceManagerProperty;
    private ResourceManager _resourceManager;

    private CultureInfo Culture
        => _cultureProperty?.GetValue(null) as CultureInfo;

    public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
    {
        throw new NotImplementedException();
    }

    public LocalizedString this[string name]
    {
        get
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            var value = _resourceManager?.GetString(name, Culture);
            return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
        }
    }

    public LocalizedString this[string name, params object[] arguments]
    {
        get
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            var format = _resourceManager?.GetString(name, Culture);
            var value = string.Format(format ?? name, arguments);
            return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
        }
    }

    public ResourceStringLocalizer()
    {
        _stringResource = typeof(TStringResource);
        _cultureProperty = _stringResource?.GetProperty("Culture", BindingFlags.Static | BindingFlags.NonPublic);
        _resourceManagerProperty = _stringResource?.GetProperty("ResourceManager", BindingFlags.Static | BindingFlags.NonPublic);
        _resourceManager = _resourceManagerProperty?.GetValue(null) as ResourceManager;
    }
}

Here's a demo of the above class:

IStringLocalizer Localizer = new ResourceStringLocalizer<AppStrings>();
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
var HelloEN = Localizer["Hello"];
CultureInfo.CurrentUICulture = new CultureInfo("fr-FR");
var HelloFR = Localizer["Hello"];
AppStrings.Culture = new CultureInfo("es-ES");
var HelloES = Localizer["Hello"];
Skinhead answered 11/10, 2023 at 10:43 Comment(0)
D
0

Use .resx files like:

  • SharedResource.resx (default language)
  • SharedResource.en.resx (english)
  • SharedResource.fr.resx (french)
  • ...

You can give an argument regarding which culture do you want translated to:

var cultureInfo = new CultureInfo("fr");  // Set the desired culture (e.g., French)
var resourceManager = new ResourceManager("MyNamespace.Resources", Assembly.GetExecutingAssembly());

string subject = resourceManager.GetString("EmailSubject", cultureInfo);
string body = string.Format(resourceManager.GetString("EmailBody", cultureInfo), userName);

SendEmail(userEmail, subject, body);

I use it like this (carint about the .resx without a language (which is in my case german "de" as the default:

public class EmailLocalizationService
{
    private readonly ResourceManager _resourceManager;
    public EmailLocalizationService()
    {
        _resourceManager = new ResourceManager(typeof(SharedResource).GetTypeInfo().FullName, Assembly.GetExecutingAssembly());
    }

    public string Get(string key, string language)
    {   
        string result = string.Empty;

        if(!string.IsNullOrEmpty(language))
        {
            if(language == "de")
            {
                // Return default langauge "de".
                return key;
            }
            else
            {
                result = _resourceManager.GetString(key, new CultureInfo(language));
            }
        }
        else
        {
            result = _resourceManager.GetString(key);
        }

        return result;
    }
}
Dael answered 28/9 at 15:40 Comment(0)
M
-4

you may write your own culture middleware which can set the culture based on the user or maybe using the Http Header Accept-Language

app.UseRequestLocalization(roptions =>
        {
            IList<CultureInfo> supportedCultures = new List<CultureInfo>
            {
                new CultureInfo("en-US"),
                new CultureInfo("fr"),
            };
            roptions.DefaultRequestCulture = new RequestCulture("en-US");
            roptions.SupportedCultures = supportedCultures;
            roptions.SupportedUICultures = supportedCultures;
            roptions.RequestCultureProviders.Add(new YourCustomCultureProvider());
        });

Sample Middleware

public class YourCustomCultureProvider : RequestCultureProvider
{
    public override Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
    {
        if (httpContext == null)
            throw new ArgumentNullException(nameof(httpContext));


        var culture = //Some Logic

        if (string.IsNullOrEmpty(culture))
        {
            // No values specified for either so no match
            return Task.FromResult((ProviderCultureResult)null);
        }

        var requestCulture = new ProviderCultureResult(culture);

        return Task.FromResult(requestCulture);
    }
}

Refer this link if it could help you - https://joonasw.net/view/aspnet-core-localization-deep-dive

Meingoldas answered 14/3, 2020 at 16:52 Comment(1)
There is no HTTP at all. As I said I'm using webchat APIs such as Discord.Net. I am not using ASP, this is not a web app. Moreover your solution doesn't provide a way to pass any object to the culture provider when creating the localizer, it can only recieve objects when configuring the app.Ironwork

© 2022 - 2024 — McMap. All rights reserved.