Files
git.stella-ops.org/src/__Libraries/StellaOps.Localization/MiddlewareExtensions.cs

100 lines
3.4 KiB
C#

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
namespace StellaOps.Localization;
/// <summary>
/// ASP.NET Core middleware and startup extensions for the localization system.
/// </summary>
public static class MiddlewareExtensions
{
/// <summary>
/// Adds localization middleware that resolves the request locale from headers
/// and initializes the static <see cref="T"/> entry point.
/// Call after <c>Build()</c> and before endpoint mapping.
/// </summary>
public static IApplicationBuilder UseStellaOpsLocalization(this IApplicationBuilder app)
{
// Initialize the static T singleton from DI
var registry = app.ApplicationServices.GetRequiredService<TranslationRegistry>();
T.Initialize(registry);
return app.Use(async (context, next) =>
{
var options = context.RequestServices.GetRequiredService<IOptions<TranslationOptions>>().Value;
// Priority: X-Locale header > Accept-Language first entry > default
var locale = context.Request.Headers["X-Locale"].FirstOrDefault();
if (string.IsNullOrWhiteSpace(locale))
{
var acceptLanguage = context.Request.Headers.AcceptLanguage.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(acceptLanguage))
{
// Take the first entry (highest quality) before any comma
locale = acceptLanguage.Split(',')[0].Split(';')[0].Trim();
}
}
locale = NormalizeLocale(locale, options);
LocaleContext.Current = locale;
try
{
await next(context).ConfigureAwait(false);
}
finally
{
LocaleContext.Current = null;
}
});
}
/// <summary>
/// Loads all translation bundles from registered providers.
/// Call after <c>Build()</c>, before <c>Run()</c>.
/// </summary>
public static async Task<WebApplication> LoadTranslationsAsync(this WebApplication app)
{
var registry = app.Services.GetRequiredService<TranslationRegistry>();
var providers = app.Services.GetServices<ITranslationBundleProvider>();
await registry.LoadAsync(providers, CancellationToken.None).ConfigureAwait(false);
return app;
}
private static string NormalizeLocale(string? locale, TranslationOptions options)
{
if (string.IsNullOrWhiteSpace(locale))
{
return options.DefaultLocale;
}
locale = locale.Trim();
// Check if the locale is in the supported list
foreach (var supported in options.SupportedLocales)
{
if (string.Equals(locale, supported, StringComparison.OrdinalIgnoreCase))
{
return supported; // Return the canonical casing
}
}
// Try language-only match (e.g., "de" matches "de-DE")
var dashIndex = locale.IndexOf('-');
var languageOnly = dashIndex > 0 ? locale[..dashIndex] : locale;
foreach (var supported in options.SupportedLocales)
{
if (supported.StartsWith(languageOnly, StringComparison.OrdinalIgnoreCase))
{
return supported;
}
}
return options.DefaultLocale;
}
}