search and ai stabilization work, localization stablized.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user