Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
DamianEdwards opened this issue Sep 23, 2024 · 1 comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates

Comments

@DamianEdwards
Copy link
Member

DamianEdwards commented Sep 23, 2024

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.

@DamianEdwards DamianEdwards added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Sep 23, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label Sep 23, 2024
@DamianEdwards
Copy link
Member Author

FYI @halter73 @BrennanConroy @captainsafia for thoughts on proposed implementation here. Would be good to move this to api-ready-for-review/api-approved so we can accept a PR for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates
Projects
None yet
Development

No branches or pull requests

1 participant