100 lines
3.4 KiB
C#
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;
|
|
}
|
|
}
|