ASP.NET 8——使用单个Resx文件的多语言应用程序

1099 篇文章 54 订阅
70 篇文章 3 订阅

目录

1. 需要更新的教程

1.1 本系列文章

2. 多语言网站、全球化和本地化

3. 共享资源方法

4. 多语种申请步骤

4.1 配置本地化服务和中间件

4.2 创建标记类SharedResources.cs

4.3 创建语言资源文件

4.4 选择语言/文化

4.5 在控制器中使用本地化服务

4.6 在视图中使用本地化服务

4.7 执行结果

4.8 IHtmlLocalizer的问题

5. 完整代码

6. 参考资料


1. 需要更新的教程

有许多关于如何在ASP.NET Core 8 MVC构建多语言应用程序的教程,但许多教程对于旧版本的.NET来说已经过时,或者对于如何解决将所有语言资源string放在单个文件中的问题含糊不清。因此,该计划是提供有关如何做到这一点的实用说明,并附有代码示例和概念验证示例应用程序。

1.1 本系列文章

本系列中的文章包括:

2. 多语言网站、全球化和本地化

我不打算在这里解释拥有多种语言的网站有什么好处,以及什么是本地化和全球化。您可以在互联网上的许多地方阅读它(参见[4])。我将重点介绍如何在ASP.NET Core 8 MVC中实际构建这样一个站点。如果您不确定什么是.resx文件,这可能不适合您。

3. 共享资源方法

默认情况下,ASP.NET Core 8 MVC技术为每个控制器和视图设想单独的资源文件.resx。但是大多数人不喜欢它,因为大多数多语言string在应用程序的不同位置是相同的,我们希望它都在同一个地方。文献[1]将这种方法称为共享资源方法。为了实现它,我们将创建一个标记类SharedResoureces.cs来对所有资源进行分组。然后,在我们的应用程序中,我们将为该特定类/类型调用依赖注入(DI),而不是特定的控制器/视图。这是Microsoft文档[1]中提到的一个小技巧,在StackOverflow文章[6]中一直是混淆的根源。我们计划在这里揭开它的神秘面纱。虽然一切都在[1]中进行了解释,但需要的是一些实际示例,例如我们在这里提供的示例。

4. 多语种申请步骤

4.1 配置本地化服务和中间件

本地化服务配置Program.cs

private static void AddingMultiLanguageSupportServices(WebApplicationBuilder? builder)
{
    if (builder == null) { throw new Exception("builder==null"); };

    builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
    builder.Services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
    builder.Services.Configure<RequestLocalizationOptions>(options =>
    {
        var supportedCultures = new[] { "en", "fr", "de", "it" };
        options.SetDefaultCulture(supportedCultures[0])
            .AddSupportedCultures(supportedCultures)
            .AddSupportedUICultures(supportedCultures);
    });
}

private static void AddingMultiLanguageSupport(WebApplication? app)
{
    app?.UseRequestLocalization();
}

4.2 创建标记类SharedResources.cs

这只是一个用于对共享资源进行分组的虚拟标记类。我们需要它的名称和类型。

似乎命名空间需要与应用根命名空间相同,而应用根命名空间需要与程序集名称相同。我在更改命名空间时遇到了一些问题,它不起作用。如果它不适合您,您可以尝试在DI指令中使用完整的类名,如下所示:

IStringLocalizer<SharedResources01.SharedResource> StringLocalizer

名称SharedResource没有魔力,您可以将其命名为MyResources并将代码中的所有引用更改为MyResources,所有引用仍然有效。

位置似乎可以是任何文件夹,尽管有些文章([6]声称它需要是根项目文件夹,但我在此示例中没有看到此类问题。对我来说,看起来它可以是任何文件夹,只需保持命名空间整洁即可。

//SharedResource.cs===================================================
namespace SharedResources01
{
    /*
    * This is just a dummy marker class to group shared resources
    * We need it for its name and type
    * 
    * It seems the namespace needs to be the same as app root namespace
    * which needs to be the same as the assembly name.
    * I had some problems when changing the namespace, it would not work.
    * If it doesn't work for you, you can try to use full class name
    * in your DI instruction, like this one:
    * IStringLocalizer<SharedResources01.SharedResource> StringLocalizer
    * 
    * There is no magic in the name "SharedResource", you can
    * name it "MyResources" and change all references in the code
    * to "MyResources" and all will still work
    * 
    * Location seems can be any folder, although some
    * articles claim it needs to be the root project folder
    * I do not see such problems in this example. 
    * To me looks it can be any folder, just keep your
    * namespace tidy. 
    */

    public class SharedResource
    {
    }
}

4.3 创建语言资源文件

资源文件夹中,创建语言资源文件,并确保将其命名为 SharedResources.xx.resx

4.4 选择语言/文化

基于[5],本地化服务有三个默认提供程序:

  1. QueryStringRequestCultureProvider
  2. CookieRequestCultureProvider
  3. AcceptLanguageHeaderRequestCultureProvider

由于大多数应用通常会提供一种机制来设置区域性,用于使用ASP.NET Core区域性Cookie设置区域性,因此在示例中,我们将仅关注该方法。

这是设置.AspNetCore.Culture cookie的代码:

private void ChangeLanguage_SetCookie(HttpContext myContext, string? culture)
{
    if(culture == null) { throw new Exception("culture == null"); };

    //this code sets .AspNetCore.Culture cookie
    myContext.Response.Cookies.Append(
        CookieRequestCultureProvider.DefaultCookieName,
        CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
        new CookieOptions { Expires = DateTimeOffset.UtcNow.AddMonths(1) }
    );
}

使用Chrome DevTools可以很容易地看到Cookie

我构建了一个小型应用程序来演示它,这是我更改语言的屏幕:

请注意,我在页脚中添加了一些调试信息,以显示请求语言cookie 的值,以查看应用程序是否按预期工作。

4.5 在控制器中使用本地化服务

当然,在控制器中,依赖注入(DI)进入并填充所有依赖项。关键是我们要求一个特定的type=SharedResource

如果它不适合您,您可以尝试在DI指令中使用完整的类名,如下所示:IStringLocalizer<SharedResources01.SharedResource> stringLocalizer

下面是代码片段:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly IStringLocalizer<SharedResource> _stringLocalizer;
    private readonly IHtmlLocalizer<SharedResource> _htmlLocalizer;

    /* Here is, of course, the Dependency Injection (DI) coming in and filling 
     * all the dependencies. The key thing is we are asking for a specific 
     * type=SharedResource. 
     * If it doesn't work for you, you can try to use full class name
     * in your DI instruction, like this one:
     * IStringLocalizer<SharedResources01.SharedResource> stringLocalizer
     */
    public HomeController(ILogger<HomeController> logger, 
        IStringLocalizer<SharedResource> stringLocalizer,
        IHtmlLocalizer<SharedResource> htmlLocalizer)
    {
        _logger = logger;
        _stringLocalizer = stringLocalizer;
        _htmlLocalizer = htmlLocalizer;
    }
    
    //================================
    
    public IActionResult LocalizationExample(LocalizationExampleViewModel model)
{
    //so, here we use IStringLocalizer
    model.IStringLocalizerInController = _stringLocalizer["Wellcome"];
    //so, here we use IHtmlLocalizer
    model.IHtmlLocalizerInController = _htmlLocalizer["Wellcome"];
    return View(model);
}

4.6 在视图中使用本地化服务

当然,在视图中,依赖注入(DI)进入并填充所有依赖项。关键是我们要求一个特定的type=SharedResource

如果它不适合您,您可以尝试在DI指令中使用完整的类名,如下所示:

IStringLocalizer<SharedResources01.SharedResource> stringLocalizer

下面是代码片段:

@* LocalizationExample.cshtml ====================================================*@
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization

@model LocalizationExampleViewModel

@* Here is of course the Dependency Injection (DI) coming in and filling
all the dependencies. The key thing is we are asking for a specific
type=SharedResource. 
If it doesn't work for you, you can try to use full class name
in your DI instruction, like this one:
@inject IStringLocalizer<SharedResources01.SharedResource> StringLocalizer
 *@

@inject IStringLocalizer<SharedResource> StringLocalizer
@inject IHtmlLocalizer<SharedResource> HtmlLocalizer

@{
    <div style="width:600px">
        <p class="bg-info">
            IStringLocalizer Localized  in Controller: 
            @Model.IStringLocalizerInController
        </p>

        <p class="bg-info">
            @{
                string? text1 = StringLocalizer["Wellcome"];
            }
            IStringLocalizer Localized  in View: @text1
        </p>

        <p class="bg-info">
            IHtmlLocalizer Localized  in Controller: 
            @Model.IHtmlLocalizerInController
        </p>

        <p class="bg-info">
            @{
                string? text2 = "Wellcome";
            }
            IHtmlLocalizer Localized  in View: @HtmlLocalizer[@text2]
        </p>
    </div>
}

4.7 执行结果

执行结果如下所示:

请注意,我在页脚中添加了一些调试信息,以显示请求语言cookie 的值,以查看应用程序是否按预期工作。

4.8 IHtmlLocalizer<SharedResource>的问题

我在IHtmlLocalizer<SharedResource>上遇到了一些问题。 它解析string并转换它们,这表明设置是正确的。但是,正如宣传的那样,它不适用于HTML。我尝试翻译像<b>Wellcome</b>这样的简单HTML,但它不起作用。但它适用于像“Wellcome”这样的简单字符串。

5. 完整代码

由于大多数人都喜欢可以复制粘贴的代码,因此这是应用程序的完整代码。

//Program.cs===========================================================================
namespace SharedResources01
{
    public class Program
    {
        public static void Main(string[] args)
        {
            //=====Middleware and Services=============================================
            var builder = WebApplication.CreateBuilder(args);

            //adding multi-language support
            AddingMultiLanguageSupportServices(builder);

            // Add services to the container.
            builder.Services.AddControllersWithViews();

            //====App===================================================================
            var app = builder.Build();

            //adding multi-language support
            AddingMultiLanguageSupport(app);

            // Configure the HTTP request pipeline.
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=ChangeLanguage}/{id?}");

            app.Run();
        }

        private static void AddingMultiLanguageSupportServices
                                       (WebApplicationBuilder? builder)
        {
            if (builder == null) { throw new Exception("builder==null"); };

            builder.Services.AddLocalization
                    (options => options.ResourcesPath = "Resources");
            builder.Services.AddMvc()
                    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix);
            builder.Services.Configure<RequestLocalizationOptions>(options =>
            {
                var supportedCultures = new[] { "en", "fr", "de", "it" };
                options.SetDefaultCulture(supportedCultures[0])
                    .AddSupportedCultures(supportedCultures)
                    .AddSupportedUICultures(supportedCultures);
            });
        }

        private static void AddingMultiLanguageSupport(WebApplication? app)
        {
            app?.UseRequestLocalization();
        }
    }
}

//SharedResource.cs===================================================
namespace SharedResources01
{
    /*
    * This is just a dummy marker class to group shared resources
    * We need it for its name and type
    * 
    * It seems the namespace needs to be the same as app root namespace
    * which needs to be the same as the assembly name.
    * I had some problems when changing the namespace, it would not work.
    * If it doesn't work for you, you can try to use full class name
    * in your DI instruction, like this one:
    * IStringLocalizer<SharedResources01.SharedResource> StringLocalizer
    * 
    * There is no magic in the name "SharedResource", you can
    * name it "MyResources" and change all references in the code
    * to "MyResources" and all will still work
    * 
    * Location seems can be any folder, although some
    * articles claim it needs to be the root project folder
    * I do not see such problems in this example. 
    * To me looks it can be any folder, just keep your
    * namespace tidy. 
    */

    public class SharedResource
    {
    }
}

//HomeController.cs================================================================
namespace SharedResources01.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IStringLocalizer<SharedResource> _stringLocalizer;
        private readonly IHtmlLocalizer<SharedResource> _htmlLocalizer;

        /* Here is, of course, the Dependency Injection (DI) coming in and filling 
         * all the dependencies. The key thing is we are asking for a specific 
         * type=SharedResource. 
         * If it doesn't work for you, you can try to use full class name
         * in your DI instruction, like this one:
         * IStringLocalizer<SharedResources01.SharedResource> stringLocalizer
         */
        public HomeController(ILogger<HomeController> logger, 
            IStringLocalizer<SharedResource> stringLocalizer,
            IHtmlLocalizer<SharedResource> htmlLocalizer)
        {
            _logger = logger;
            _stringLocalizer = stringLocalizer;
            _htmlLocalizer = htmlLocalizer;
        }

        public IActionResult ChangeLanguage(ChangeLanguageViewModel model)
        {
            if (model.IsSubmit)
            {
                HttpContext myContext = this.HttpContext;
                ChangeLanguage_SetCookie(myContext, model.SelectedLanguage);
                //doing funny redirect to get new Request Cookie
                //for presentation
                return LocalRedirect("/Home/ChangeLanguage");
            }

            //prepare presentation
            ChangeLanguage_PreparePresentation(model);
            return View(model);
        }

        private void ChangeLanguage_PreparePresentation(ChangeLanguageViewModel model)
        {
            model.ListOfLanguages = new List<SelectListItem>
                        {
                            new SelectListItem
                            {
                                Text = "English",
                                Value = "en"
                            },

                            new SelectListItem
                            {
                                Text = "German",
                                Value = "de",
                            },

                            new SelectListItem
                            {
                                Text = "French",
                                Value = "fr"
                            },

                            new SelectListItem
                            {
                                Text = "Italian",
                                Value = "it"
                            }
                        };
        }

        private void ChangeLanguage_SetCookie(HttpContext myContext, string? culture)
        {
            if(culture == null) { throw new Exception("culture == null"); };

            //this code sets .AspNetCore.Culture cookie
            myContext.Response.Cookies.Append(
                CookieRequestCultureProvider.DefaultCookieName,
                CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
                new CookieOptions { Expires = DateTimeOffset.UtcNow.AddMonths(1) }
            );
        }

        public IActionResult LocalizationExample(LocalizationExampleViewModel model)
        {
            //so, here we use IStringLocalizer
            model.IStringLocalizerInController = _stringLocalizer["Wellcome"];
            //so, here we use IHtmlLocalizer
            model.IHtmlLocalizerInController = _htmlLocalizer["Wellcome"];
            return View(model);
        }

        public IActionResult Error()
        {
            return View(new ErrorViewModel 
            { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}

//ChangeLanguageViewModel.cs=====================================================
namespace SharedResources01.Models.Home
{
    public class ChangeLanguageViewModel
    {
        //model
        public string? SelectedLanguage { get; set; } = "en";

        public bool IsSubmit { get; set; } = false;

        //view model
        public List<SelectListItem>? ListOfLanguages { get; set; }
    }
}

//LocalizationExampleViewModel.cs===============================================
namespace SharedResources01.Models.Home
{
    public class LocalizationExampleViewModel
    {
        public string? IStringLocalizerInController { get; set; }
        public LocalizedHtmlString? IHtmlLocalizerInController { get; set; }
    }
}
@* ChangeLanguage.cshtml ===================================================*@
@model ChangeLanguageViewModel

@{
    <div style="width:500px">
        <p class="bg-info">
            <partial name="_Debug.AspNetCore.CultureCookie" /><br />
        </p>

        <form id="form1">
            <fieldset class="border rounded-3 p-3">
                <legend class="float-none w-auto px-3">Change Language</legend>
                <div class="form-group">
                    <label asp-for="SelectedLanguage">Select Language</label>
                    <select class="form-select" asp-for="SelectedLanguage"
                            asp-items="@Model.ListOfLanguages">
                    </select>
                    <input type="hidden" name="IsSubmit" value="true">
                    <button type="submit" form="form1" 
                     class="btn btn-primary mt-3 float-end"
                            asp-area="" asp-controller="Home" 
                            asp-action="ChangeLanguage">
                        Submit
                    </button>
                </div>
            </fieldset>
        </form>
    </div>
}

@* LocalizationExample.cshtml ====================================================*@
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Localization

@model LocalizationExampleViewModel

@* Here is of course the Dependency Injection (DI) coming in and filling
all the dependencies. The key thing is we are asking for a specific
type=SharedResource. 
If it doesn't work for you, you can try to use full class name
in your DI instruction, like this one:
@inject IStringLocalizer<SharedResources01.SharedResource> StringLocalizer
 *@

@inject IStringLocalizer<SharedResource> StringLocalizer
@inject IHtmlLocalizer<SharedResource> HtmlLocalizer

@{
    <div style="width:600px">
        <p class="bg-info">
            IStringLocalizer Localized  in Controller: 
            @Model.IStringLocalizerInController
        </p>

        <p class="bg-info">
            @{
                string? text1 = StringLocalizer["Wellcome"];
            }
            IStringLocalizer Localized  in View: @text1
        </p>

        <p class="bg-info">
            IHtmlLocalizer Localized  in Controller: 
            @Model.IHtmlLocalizerInController
        </p>

        <p class="bg-info">
            @{
                string? text2 = "Wellcome";
            }
            IHtmlLocalizer Localized  in View: @HtmlLocalizer[@text2]
        </p>
    </div>
}

6. 参考资料

https://www.codeproject.com/Articles/5378651/ASP-NET-8-Multilingual-Application-with-Single-Res

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ASP.NET MVC 是一种基于模型-视图-控制器模式的 Web 应用程序框架,具有高性能和易于开发的优点。同时,它也提供了一种多语言的解决方案,可以实现应用程序的国际化和本地化。 下面我们来介绍一下 ASP.NET MVC 的多语言例子: 首先,我们需要在应用程序中准备多语言资源文件,这些资源文件包含了不同语言的文本信息和翻译。 接着,在 ASP.NET MVC 中,我们可以使用 Resx 功能来管理多语言资源。Resx 是一种基于 XML 的文件格式,它可以存储.NET 程序中使用的本地化数据。Resx 文件可以存储不同语言的字典信息。 例如,我们可以在应用程序的 Resx 文件中添加一个名称为“Hello”的键,然后为每个语言添加不同的本地化值。 之后,我们可以使用 ASP.NET MVC 视图中的内置功能将这些本地化资源引用到我们的应用程序中。在视图中使用 HTML.Helper 和@Html 中的 Resources 方法可以轻松地从资源文件中获取文本信息并显示到视图中,实现不同语言场景下的信息展示。 在控制器中,我们可以通过实例化不同的本地化资源管理器来获取特定语言下的资源文件,一般来说,我们可以根据不同语言的路径或者Cookie等方式来确定用户所选择的语言,然后动态加载相应的本地化资源文件。 这样,在实现了多语言资源和本地化功能后,用户可以根据自己的语言选择来体验应用程序,从而实现应用程序的国际化和本地化。同时,使用 ASP.NET MVC 的多语言功能也极大地提高了应用程序的性能,使得应用程序能够更加快速、高效地运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值