y ofreciendo uno extra por si en el código se necesite conocer la cultura en uso:
La clase ThreadCultureScope asegura cambios temporales de cultura localizados en el hilo actual:
public class ThreadCultureScope : IDisposable
{
private readonly CultureInfo _originalCulture;
private readonly CultureInfo _originalUICulture;
public ThreadCultureScope(string language)
{
_originalCulture = Thread.CurrentThread.CurrentCulture;
_originalUICulture = Thread.CurrentThread.CurrentUICulture;
CultureInfo culture = new CultureInfo(language);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
public void Dispose()
{
Thread.CurrentThread.CurrentCulture = _originalCulture;
Thread.CurrentThread.CurrentUICulture = _originalUICulture;
}
}
4. Registrar Servicios en Program.cs
Para usar IStringCulture en la aplicación, registramos los servicios necesarios:
var builder = WebApplication.CreateBuilder(args);
// Agregar servicios
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton(typeof(IStringCulture< >), typeof(StringCulture< >));
// Configurar recursos de localización
builder.Services.AddLocalization();
var app = builder.Build();
app.Run();
5. Uso de la Nueva Abstracción
Aqui un ejemplo de cómo un controlador o endpoints de Minimal API para usar IStringCulture en lugar de IStringLocalizer y mostrar un texto en el idioma que nos entre:
app.MapPost("/process-request", async (IStringCulture localizer) =>
{
string message = localizer["WelcomeMessage"];
return Results.Ok(new { message });
});
Recuerda que si estas en un proyecto limpio, respetando la inversión de dependencias, podrías utilizar IStringCulture en tu caso de uso, y listo.
Beneficios de Este Enfoque
- Localización por Solicitud: Los cambios de idioma están aislados para cada solicitud, garantizando la seguridad en hilos.
- Integración Transparente: Diseñamos IStringCulture para imitar IStringLocalizer, logrando mínimos cambios de código Además de un fácil aprendizaje para otros.
- Lógica Centralizada: La lógica de determinación de idioma ahora está centralizada en StringCulture, mejorando el mantenimiento.
- Extensibilidad: Futuros cambios (por ejemplo, recuperar idiomas de cookies) pueden hacerse en un solo lugar sin tocar otras partes del código.
Conclusión
Refactorizar IStringLocalizer a IStringCulture nos permitió abordar limitaciones clave en un entorno multi-hilo mientras asegurábamos un impacto mínimo en la base de código existente. Este patrón es invaluable para aplicaciones que requieren localización específica por solicitud.
Al aislar la determinación del idioma y mantener la compatibilidad con IStringLocalizer, logramos una solución escalable y limpia que puede adaptarse a futuros requisitos.
English version
This english version is translated and review using AI, my apologies if some it's not very good.
In modern web applications, proper localization is essential for enhancing user experience in multi-language environments. .NET's IStringLocalizer is a powerful tool to handle this. However, in some cases, we may need a more customized approach to manage request-specific languages without interfering with global state, such as CultureInfo, which can be problematic in multi-threaded scenarios.
This article describes how we replaced IStringLocalizer with a custom abstraction, IStringCulture, for better request-scoped localization without impacting the overall structure of our code.
Our Use Case
Our application is built using a clean architecture pattern with .NET Minimal APIs. The goal is to:
- Extract the Accept-Language header from HTTP requests.
- Provide localized strings based on this header.
- Avoid altering global CultureInfo to ensure multi-threaded request independence.
- Maintain minimal impact on the existing codebase.
To achieve this, we needed a layer of abstraction that integrates seamlessly with the existing IStringLocalizer but isolates language determination to each request.
Solution Overview
We introduced a new interface, IStringCulture< TModel >, as a drop-in replacement for IStringLocalizer< TModel >. This interface encapsulates language resolution and provides localized strings without globally changing the CultureInfo.
Why Replace IStringLocalizer?
IStringLocalizer uses CultureInfo for determining the current language. This global approach can lead to conflicts in multi-threaded environments where simultaneous requests require different languages.
- We needed per-request language isolation to ensure that one request does not affect another.
- By creating IStringCulture, we achieve the following:
Centralized language resolution logic.
- Per-request culture management through a temporary ThreadCultureScope.
- A seamless way to adapt the existing usage of IStringLocalizer to our new abstraction.
How I did the Implementation
1. Defining the New Interface
We started by creating the IStringCulture interface:
Check code above
2. Implementing the Class
Our implementation handles language extraction and uses ThreadCultureScope to isolate culture changes.
Check code above
3. Handling Temporary Culture Scopes
The ThreadCultureScope class ensures temporary, thread-local culture changes for localization:
Check code above
4. Registering Services in Program.cs
To use IStringCulture in the application, we registered the necessary services:
Check code above
5. Using the New Abstraction
We modified controllers or Minimal API endpoints to use IStringCulture instead of IStringLocalizer:
Check code above
Remember that if you are in a clean project, respecting the dependency inversion, you could use IStringCulture in your use case, and that's it.
Benefits of This Approach
- Request-Scoped Localization: Language changes are isolated to each request, ensuring thread safety.
- Seamless Integration: By designing IStringCulture to mimic IStringLocalizer, we achieved minimal code changes in existing implementations.
- Centralized Logic: Language determination logic is now centralized in StringCulture, improving maintainability.
- Extensibility: Future changes (e.g., retrieving languages from cookies) can be made in one place without touching other parts of the codebase.
Conclusion
Refactoring IStringLocalizer to IStringCulture allowed us to address key limitations in a multi-threaded environment while ensuring minimal impact on the existing codebase. This pattern proves invaluable for applications requiring request-specific localization.
By isolating the language determination and maintaining compatibility with IStringLocalizer, we achieved a scalable and clean solution that can adapt to future requirements.