-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLocaleResolutionMiddleware.cs
More file actions
130 lines (112 loc) · 4.59 KB
/
LocaleResolutionMiddleware.cs
File metadata and controls
130 lines (112 loc) · 4.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
using System.Globalization;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using SimpleModule.Core.Inertia;
using SimpleModule.Core.Settings;
using SimpleModule.Localization.Contracts;
using SimpleModule.Localization.Services;
using SimpleModule.Settings.Contracts;
namespace SimpleModule.Localization.Middleware;
public sealed class LocaleResolutionMiddleware(
RequestDelegate next,
IConfiguration configuration,
TranslationLoader loader,
IMemoryCache cache)
{
private static readonly TimeSpan UserLocaleCacheDuration = TimeSpan.FromMinutes(5);
private static readonly TimeSpan AcceptLanguageCacheDuration = TimeSpan.FromMinutes(30);
public async Task InvokeAsync(HttpContext context)
{
var locale = await ResolveLocaleAsync(context);
CultureInfo culture;
try
{
culture = CultureInfo.GetCultureInfo(locale);
}
catch (CultureNotFoundException)
{
locale = GetDefaultLocale();
culture = CultureInfo.GetCultureInfo(locale);
}
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
var sharedData = context.RequestServices.GetService<InertiaSharedData>();
if (sharedData is not null)
{
sharedData.Set(LocalizationConstants.LocaleSharedDataKey, locale);
sharedData.Set(LocalizationConstants.TranslationsSharedDataKey, loader.GetAllTranslations(locale));
}
await next(context);
}
private async Task<string> ResolveLocaleAsync(HttpContext context)
{
var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (userId is not null)
{
// Only cache the user's explicit DB setting — not Accept-Language fallbacks.
// This avoids cross-browser cache pollution where one browser's Accept-Language
// leaks to another browser for the same user.
var cacheKey = UserLocaleKey(userId);
if (cache.TryGetValue(cacheKey, out string? cachedLocale) && cachedLocale is not null)
{
return cachedLocale;
}
var settings = context.RequestServices.GetService<ISettingsContracts>();
if (settings is not null)
{
var userLocale = await settings.GetSettingAsync<string>(
LocalizationConstants.UserLanguageSetting, SettingScope.User, userId);
if (!string.IsNullOrEmpty(userLocale))
{
cache.Set(cacheKey, userLocale, UserLocaleCacheDuration);
return userLocale;
}
}
}
// No explicit user setting — resolve from Accept-Language header or default
return ResolveFromAcceptLanguage(context);
}
private string ResolveFromAcceptLanguage(HttpContext context)
{
var rawHeader = context.Request.Headers.AcceptLanguage.ToString();
if (string.IsNullOrEmpty(rawHeader))
{
return GetDefaultLocale();
}
var cacheKey = AcceptLanguageKey(rawHeader);
if (cache.TryGetValue(cacheKey, out string? cached) && cached is not null)
{
return cached;
}
var acceptLanguageHeaders = context.Request.GetTypedHeaders().AcceptLanguage;
if (acceptLanguageHeaders is { Count: > 0 })
{
var supportedLocales = loader.SupportedLocalesSet;
foreach (var lang in acceptLanguageHeaders.OrderByDescending(l => l.Quality ?? 1.0))
{
var tag = lang.Value.ToString();
if (supportedLocales.Contains(tag))
{
cache.Set(cacheKey, tag, AcceptLanguageCacheDuration);
return tag;
}
var twoLetter = tag.Split('-')[0];
if (supportedLocales.Contains(twoLetter))
{
cache.Set(cacheKey, twoLetter, AcceptLanguageCacheDuration);
return twoLetter;
}
}
}
return GetDefaultLocale();
}
private string GetDefaultLocale()
{
return configuration["Localization:DefaultLocale"] ?? LocalizationConstants.DefaultLocale;
}
private static string UserLocaleKey(string userId) => string.Concat("locale:user:", userId);
private static string AcceptLanguageKey(string headerValue) => string.Concat("locale:accept:", headerValue);
}