Веб-приложение .Net 8 Blazor (сервер) -> Начните с языка браузера и разрешите пользователю менять язык через раскрывающийся список

Чего я хочу достичь
Я хочу, чтобы мое веб-приложение 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 срабатывает после того, как пользователь изменил язык? Это из-за принудительной перезагрузки? Должны ли обе концепции реализации вообще работать вместе? Если нет, то какое решение желательно для поддержки первоначальной загрузки языка браузера и при этом позволяет пользователю выбирать язык приложения?

Заранее спасибо!

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я воспроизвел вашу проблему на своей стороне и обнаружил, что проблема возникла, когда у меня есть 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 и посмотреть, сработало ли это для вас или нет. Если нет, просто начните с набора, которым я поделился по ссылке, и попробуйте полные примеры кодов. Смотрите мой скриншот ниже, после того как вы прокомментируете фрагмент кода, он заработает.

Кстати, если добавляемый вами компонент <CultureSelector /> не определяет отрисовку на стороне сервера, лучше добавить компонент CultureSelector, например <CultureSelector @rendermode = "InteractiveServer" />.

Tiny Wang 30.07.2024 10:29

Я проверил ваше предложение, но, к сожалению, оно ведет себя не так, как ожидалось. При использовании приведенного выше кода в моем Program.cs веб-приложение не запускается на языке браузера. Протестировано в Firefox и Edge. Сначала я подумал, что строка «SetDefaultCulture(supportedCultures[0])» определяет начальный язык, но это не так, по крайней мере, в моей локальной отладке.

rekcul 31.07.2024 10:43
SetDefaultCulture(supportedCultures[0]) не контролирует исходный язык. Кстати, не могли бы вы создать новое приложение, отображаемое на стороне сервера веб-приложения blaozr, и следовать официальному документу, чтобы снова настроить локализацию? Я тестировал в Chrome и Edge, в Chrome я установил английский язык по умолчанию, а в Edge — китайский, и приложение работало как положено.
Tiny Wang 31.07.2024 10:55
Ответ принят как подходящий

Решение найдено
Итак, ответ Крошки Ванга и пример из официальной документации привели меня в правильном направлении, но было еще одно недоразумение/проблема, которой не хватает в ответе. Поэтому я поделюсь окончательной реализацией в своем ответе, а также дам дополнительную информацию о том, что происходит.

Решение кода

Программа.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 или какой-то еще....?

Tiny Wang 31.07.2024 15:50

Это был не только Firefox, но и Edge использует нейтральные культуры, такие как «en» или «de». Я еще не тестировал Chrome.

rekcul 01.08.2024 08:06

окей... мой код хорошо работал в Edge и Chrome. может другая версия? Не очень уверен в этом. В любом случае, я рад видеть, что вы решили свою проблему :)

Tiny Wang 01.08.2024 08:19

Другие вопросы по теме