Skip to content

Add new overloads to IStringLocalizerFactory to enable creating IStringLocalizer instances for a specific culture #58037

Open
@DamianEdwards

Description

@DamianEdwards

Background and Motivation

We used to have an API on IStringLocalizer that would enable creating an instance for a specific culture, but it was removed (see #7756) due to reports of confusion by implementors. In doing so, we removed any non-static way of creating a localizer for a specific culture, which is necessary in scenarios where the current culture isn't implicitly set by the execution context, e.g. sending emails from a background worker. We've received feedback that this scenario is quite common and the lack of a first-class way to get a culture-specific instance of IStringLocalizer without resorting to static manipulation is undesirable.

Proposed API

Propose we add two new overloads with default implementations (to ensure the interface change isn't breaking) that allow creating IStringLocalizer instances for a specific culture:

public interface IStringLocalizerFactory
{
+    public IStringLocalizer Create(Type resourceSource, CultureInfo culture)
+    {
+        try
+        {
+            var originalCulture = CultureInfo.CurrentUICulture;
+            CultureInfo.CurrentUICulture = culture;
+            return Create(resourceSource);
+        }
+        finally
+        {
+            CultureInfo.CurentUICulture = originalCulture;
+        }
+    }
+    public IStringLocalizer Create(string baseName, string location, CultureInfo culture)
+    {
+       try
+        {
+            var originalCulture = CultureInfo.CurrentUICulture;
+            CultureInfo.CurrentUICulture = culture;
+            return Create(baseName, location);
+        }
+        finally
+        {
+            CultureInfo.CurentUICulture = originalCulture;
+        }
+    }
}

The implementation class ResourceManagerStringLocalizerFactory would be updated to implement these new methods to create instances of ResourceManagerStringLocalizer using the specified culture.

Usage Examples

public class EmailSender(IStringLocalizerFactory stringLocalizerFactory, CustomerDbContext db, IEmailSender emailSender)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        var since = DateTime.UtcNow - TimeSpan.FromDays(7);
        var customersToVerify = await db.Customers
            .Where(e => e.VerifiedOn == null && e.LastVerificationEmailSentOn >= since).Select(c => new { c.Id, c.FirstName, c.UICulture })
            .ToListAsync(cancellationToken);
        var templateName = "VerifyUser";
        var subject = "Please verify your email address";
        foreach (var customer in customersToVerify )
        {
            object[] parameters = [customer.FirstName];
            await emailSender.SendAsync(templateName, customer.Culture, subject, parameters, cancellationToken);
            await db.Customers.Where(c => c.Id == customer.Id)
                .ExecuteUpdateAsync(setters => setters.SetProperty(c => c.LastVerificationEmailSentOn = DateTime.UtcNow));
        }
    }
}

Alternative Designs

  • A new interface like ICultureAwareStringLocalizerFactory that these methods are defined on but with no default implementation and that implementations have to add the culture-aware overloads for. Then consuming code would need to be updated to type-check for the new interface and if implemented, call the new methods on it, or the code could first attempt to resolve the new interface from DI, and if none is registered, fallback to the original interface and manually set the current culture before creating a localizer instance.
  • Have the default implementations of the new overloads throw NotImplementedException rather than applying the current suggested workaround.

Risks

The default implementation setting static properties on CultureInfo could be problematic if not done correctly resulting in "leaking" culture specifics onto the current thread, but this is the approach we currently recommend to customers anyway.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-needs-workAPI needs work before it is approved, it is NOT ready for implementationarea-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-localization

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions