Чего я хочу достичь
Я хочу, чтобы мое веб-приложение Blazor (серверный рендеринг) начиналось с переводов, соответствующих языку браузера, но также предлагало пользователю выбор языка для изменения языка приложения после первоначального запуска.
Реализация для языка браузера
Чтобы получить язык браузера, я добавил следующий код в файл Program.cs, следуя примеру, который нашел в Интернете:
builder.Services.AddLocalization();
app.UseRequestLocalization(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
};
options.DefaultRequestCulture = new RequestCulture(supportedCultures.First());
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Clear();
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
{
var userLanguages = context.Request.Headers["Accept-Language"].ToString();
var primaryLanguage = userLanguages.Split(',').FirstOrDefault();
if (primaryLanguage == "de") primaryLanguage = "de-DE";
var userCulture = new CultureInfo(primaryLanguage);
return await Task.FromResult(new ProviderCultureResult(userCulture?.Name ?? supportedCultures[0].Name));
}));
});
который работает абсолютно нормально: мое приложение всегда запускается на языке браузера
Реализация выбора языка
Реализация выбора языка, также основанная на онлайн-примере, выглядит так:
Программа.cs:
builder.Services.AddLocalization();
builder.Services.AddControllers();
var defaultCulture = new CultureInfo("en-US");
string[] supportedCultures = ["en-US", "de-DE"];
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
КультурКонтроллер:
[Route("[controller]/[action]")]
public class CultureController : Controller
{
public IActionResult Set(string culture, string redirectUri)
{
if (culture != null)
{
var requestCulture = new RequestCulture(culture, culture);
var cookieName = CookieRequestCultureProvider.DefaultCookieName;
var cookieValue = CookieRequestCultureProvider.MakeCookieValue(requestCulture);
HttpContext.Response.Cookies.Append(cookieName, cookieValue);
}
return Redirect(redirectUri);
return LocalRedirect(redirectUri);
}
}
CultureSelector.razor:
@inject NavigationManager Navigation
<div>
<select @bind = "Culture">
<option value = "en-US">English</option>
<option value = "de-DE">Deutsch</option>
</select>
</div>
@code {
protected override void OnInitialized()
{
Culture = CultureInfo.CurrentCulture;
}
private CultureInfo Culture
{
get
{
return CultureInfo.CurrentCulture;
}
set
{
if (CultureInfo.CurrentCulture != value)
{
var uri = new Uri(Navigation.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo($"Culture/Set?culture = {cultureEscaped}&redirectUri = {uriEscaped}", forceLoad: true);
}
}
}
}
и использование компонента CultureSelector.razor в другом компоненте Razor с помощью:
<CultureSelector />
и, конечно же, использование IStringLocalizer для реального отображения перевода, полученного из соответствующих файлов Resx. Эта реализация также работает отлично: выбор другого языка в селекторе меняет перевод в пользовательском интерфейсе.
Где все идет не так
Если я оставлю приведенные выше реализации разделенными, то есть либо использую язык браузера, либо разрешаю пользователю менять язык, обе реализации работают отлично. Но если я хочу иметь обе функции одновременно, выбор пользователя всегда перезаписывается языком браузера. В отладчике я вижу, что после выбора другого языка он все еще перезаписывает настройки языка, но после принудительной перезагрузки он также переходит в CustomRequestCultureProvider, который затем перезаписывает язык языком браузера.
Мои вопросы
Почему CustomRequestCultureProvider срабатывает после того, как пользователь изменил язык? Это из-за принудительной перезагрузки? Должны ли обе концепции реализации вообще работать вместе? Если нет, то какое решение желательно для поддержки первоначальной загрузки языка браузера и при этом позволяет пользователю выбирать язык приложения?
Заранее спасибо!
Я воспроизвел вашу проблему на своей стороне и обнаружил, что проблема возникла, когда у меня есть app.UseRequestLocalization(options => xxx
и коды ниже в моем Program.cs
.
var supportedCultures = new[] { "en-US", "zh-CN" };
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
Честно говоря, у меня на стороне есть только приведенные выше коды, и все работало как часы, по умолчанию отображался язык, установленный в браузере, после того, как я изменил выбор в раскрывающемся списке, веб-сайт изменил язык. И если я закрыл браузер и снова открыл веб-сайт, он отображался на языке браузера по умолчанию. У нас есть официальный документ, в котором представлены подробные конфигурации, отвечающие вашим требованиям. Коды, используемые в руководстве, аналогичны вашим, поэтому я предлагаю вам сначала прокомментировать часть app.UseRequestLocalization(options => xxx
и посмотреть, сработало ли это для вас или нет. Если нет, просто начните с набора, которым я поделился по ссылке, и попробуйте полные примеры кодов. Смотрите мой скриншот ниже, после того как вы прокомментируете фрагмент кода, он заработает.
Я проверил ваше предложение, но, к сожалению, оно ведет себя не так, как ожидалось. При использовании приведенного выше кода в моем Program.cs веб-приложение не запускается на языке браузера. Протестировано в Firefox и Edge. Сначала я подумал, что строка «SetDefaultCulture(supportedCultures[0])» определяет начальный язык, но это не так, по крайней мере, в моей локальной отладке.
SetDefaultCulture(supportedCultures[0])
не контролирует исходный язык. Кстати, не могли бы вы создать новое приложение, отображаемое на стороне сервера веб-приложения blaozr, и следовать официальному документу, чтобы снова настроить локализацию? Я тестировал в Chrome и Edge, в Chrome я установил английский язык по умолчанию, а в Edge — китайский, и приложение работало как положено.
Решение найдено
Итак, ответ Крошки Ванга и пример из официальной документации привели меня в правильном направлении, но было еще одно недоразумение/проблема, которой не хватает в ответе. Поэтому я поделюсь окончательной реализацией в своем ответе, а также дам дополнительную информацию о том, что происходит.
Решение кода
Программа.cs:
builder.Services.AddLocalization();
string[] supportedCultures = ["en", "de", "en-US", "de-DE"];
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])
.AddSupportedCultures(supportedCultures)
.AddSupportedUICultures(supportedCultures);
app.UseRequestLocalization(localizationOptions);
Приложение.бритва:
@code
{
protected override void OnInitialized()
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext != null)
{
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
if (cultureInfo.Name == "de")
cultureInfo = new CultureInfo("de-DE");
if (cultureInfo.Name == "en-US")
cultureInfo = new CultureInfo("en");
if (cultureInfo.Name == "en-GB")
cultureInfo = new CultureInfo("en");
httpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(
cultureInfo,
cultureInfo)));
}
}
}
CultureSelector.razor
@inject NavigationManager Navigation
<div>
<select @bind = "Culture">
<option value = "en">English</option>
<option value = "de-DE">Deutsch</option>
</select>
</div>
@code {
protected override void OnInitialized()
{
Culture = CultureInfo.CurrentCulture;
}
private CultureInfo Culture
{
get
{
return CultureInfo.CurrentCulture;
}
set
{
if (CultureInfo.CurrentCulture != value)
{
var uri = new Uri(Navigation.Uri).GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
var cultureEscaped = Uri.EscapeDataString(value.Name);
var uriEscaped = Uri.EscapeDataString(uri);
Navigation.NavigateTo($"Culture/Set?culture = {cultureEscaped}&redirectUri = {uriEscaped}", forceLoad: true);
}
}
}
}
КультурКонтролер.cs:
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
namespace ProductExplorer.Controllers
{
[Route("[controller]/[action]")]
public class CultureController : Controller
{
public IActionResult Set(string culture, string redirectUri)
{
if (culture != null)
{
var requestCulture = new RequestCulture(culture, culture);
var cookieName = CookieRequestCultureProvider.DefaultCookieName;
var cookieValue = CookieRequestCultureProvider.MakeCookieValue(requestCulture);
HttpContext.Response.Cookies.Append(cookieName, cookieValue);
}
return LocalRedirect(redirectUri);
}
}
}
и, конечно же, строка, в которой я вызываю CultureSelector в компоненте Razor:
<CultureSelector />
Что сейчас изменилось?
Итак, основная разница в том, что то, что я называл «Реализацией языка браузера», исчезло. Итак, вся часть
app.UseRequestLocalization(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"),
new CultureInfo("de-DE"),
};
options.DefaultRequestCulture = new RequestCulture(supportedCultures.First());
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.RequestCultureProviders.Clear();
options.RequestCultureProviders.Insert(0, new CustomRequestCultureProvider(async context =>
{
var userLanguages = context.Request.Headers["Accept-Language"].ToString();
var primaryLanguage = userLanguages.Split(',').FirstOrDefault();
if (primaryLanguage == "de") primaryLanguage = "de-DE";
var userCulture = new CultureInfo(primaryLanguage);
return await Task.FromResult(new ProviderCultureResult(userCulture?.Name ?? supportedCultures[0].Name));
}));
});
не нужен. Вот тут-то и был ответ Крошки Ванга.
НО
некоторые мелкие детали изменились в следующих строках в Program.cs:
string[] supportedCultures = ["en", "de", "en-US", "de-DE"];
а App.razor получил совершенно новую реализацию OnInitialized(). Здесь я также добавил сопоставление различных поддерживаемых культур:
protected override void OnInitialized()
{
var httpContext = httpContextAccessor.HttpContext;
if (httpContext != null)
{
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
if (cultureInfo.Name == "de")
cultureInfo = new CultureInfo("de-DE");
if (cultureInfo.Name == "en-US")
cultureInfo = new CultureInfo("en");
if (cultureInfo.Name == "en-GB")
cultureInfo = new CultureInfo("en");
httpContext.Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(
new RequestCulture(
cultureInfo,
cultureInfo)));
}
}
Корень всего зла (или зачем я добавил поддерживаемые языки)
Изучив языки, поддерживаемые браузерами, мы видим, что, например. Firefox различает нейтральные языки и языки, специфичные для конкретной страны, например
«en» и «en-US» или
«де» и «де-ДЕ»:
и они будут рассматриваться как культуры в C#. Итак, если я предпочитаю язык «де» (не «де-DE»), значение
CurrentCulture.CultureInfo
это «де». Если этого нет в массиве/списке поддерживаемых вами культур (чего не было в моем исходном вопросе):
string[] supportedCultures = ["en-US", "de-DE"];
будет использована настроенная культура по умолчанию, которая была определена как
SetDefaultCulture(supportedCultures[0])
который был «en-US». Таким образом, решение настоящей основной причины состоит в том, чтобы убедиться, что веб-приложение поддерживает все языки (культуры), которые, как вы ожидаете, будут настроены в браузерах ваших пользователей.
Небольшая дополнительная информация
Блок кода App.razor содержит некоторые сопоставления, например
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
if (cultureInfo.Name == "de")
cultureInfo = new CultureInfo("de-DE");
if (cultureInfo.Name == "en-US")
cultureInfo = new CultureInfo("en");
if (cultureInfo.Name == "en-GB")
cultureInfo = new CultureInfo("en");
Почему? Потому что я использую файлы ресурсов. Если бы я не сопоставлял, например. «de» на «de-DE», это будет означать, что мне также нужно создать файл ресурсов для культуры «de». Нравится:
Resource.de-DE.resx
Ресурс.de.resx
В моем приложении я не делаю различий в переводах между «de» и «de-DE», поэтому я выполняю сопоставление культуры, чтобы избежать дополнительных файлов ресурсов, которые для меня избыточны. Кроме того, я установил поддерживаемую культуру по умолчанию на «en», поскольку моя культура по умолчанию для resx также всегда «en».
Спасибо, что поделились, могу ли я понимать это как «проблему, специфичную для браузера»? произошло только в Firefox, наверняка могут быть другие проблемы в других браузерах, таких как Safiri или какой-то еще....?
Это был не только Firefox, но и Edge использует нейтральные культуры, такие как «en» или «de». Я еще не тестировал Chrome.
окей... мой код хорошо работал в Edge и Chrome. может другая версия? Не очень уверен в этом. В любом случае, я рад видеть, что вы решили свою проблему :)
Кстати, если добавляемый вами компонент
<CultureSelector />
не определяет отрисовку на стороне сервера, лучше добавить компонент CultureSelector, например<CultureSelector @rendermode = "InteractiveServer" />
.