SharedResources 的本地化在 .NET Core 2.1 中不起作用

Localization with SharedResources not working in .NET Core 2.1

我已经为这个问题苦苦挣扎了几个小时...但我找不到它是什么...

我只是想本地化 _Layout.cshtml 文件。 IStringLocalizerIHtmlLocalizer 似乎都没有找到资源文件。

我关注并搜索过: https://github.com/MormonJesus69420/SharedResourcesExample .Net Core Data Annotations - localization with shared resources https://whosebug.com/search?q=shared+resources+.net+core https://andrewlock.net/adding-localisation-to-an-asp-net-core-application/

我可能忽略了一些愚蠢的事情。

这是我的 startup.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using EduPlaTools.Data;
using EduPlaTools.Models;
using EduPlaTools.Services;
using System.Globalization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc.Razor;
using Pomelo.EntityFrameworkCore.MySql;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using Microsoft.AspNetCore.HttpOverrides;

namespace EduPlaTools
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            // This is for string translation!
            // Adds Localization Services (StringLocalizer, HtmlLocalizer, etc.)
            // the opts.ResourcesPath = is the path in which the resources are found.
            // In our case the folder is named Resources!
            // There's specific and neutral resources.  (Specific en-US). (Neutral: es)
            /**
             * If no ResourcesPath is specified, the view's resources will be expected to be next to the views.
             * If ResourcesPath were set to "resources", then view resources would be expected to be ina  Resource directory,
             * in a path speicifc to their veiw (Resources/Views/Home/About.en.resx, for example).
             * 
             * */
            services.AddLocalization(opts => opts.ResourcesPath = "Resources");

            // services.AddBContext
            // There are subtle differences between the original and the modified version.

            services.AddDbContextPool<ApplicationDbContext>(options =>
                options.UseMySql(Configuration.GetConnectionString("MySQLConnection"),
                mysqlOptions =>
                {
                    mysqlOptions.ServerVersion(new Version(8, 0, 12), ServerType.MySql); // replace with your Server Version and Type
                }
                ));
                //options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            // Add application services.
            services.AddTransient<IEmailSender, EmailSender>();




            services.AddMvc()
                    .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => options.ResourcesPath = "Resources")
                    .AddDataAnnotationsLocalization();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // This may be dangerous and is not recommended
            using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
                    .CreateScope())
            {
                serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                     .Database.Migrate();
            }

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }


            // These must line up with the ending of the .resx files.
            // Example: SharedResources.en.resx, SharedResources.es.rex

            // If you want to add specific, then do it like:
            // new CultureInfo("en-US")
            List<CultureInfo> supportedCultures = new List<CultureInfo>
            {
                new CultureInfo("es"),
                new CultureInfo("en"),
                new CultureInfo("es-ES"),
                new CultureInfo("en-US")
            };

            // Registers the localization, and changes the localization per request.
            app.UseRequestLocalization(new RequestLocalizationOptions
            {
                // We give the default support of Spanish.
                DefaultRequestCulture = new RequestCulture("es"),
                // Format numbers, dates, etc.
                SupportedCultures = supportedCultures,
                // The strings that we have localized
                SupportedUICultures = supportedCultures
            });

            // This will seed the databse:

            SeedDatabase.Initialize(app.ApplicationServices);

            app.UseStaticFiles();


            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

下面是我尝试在 _Layout.cshtml 中调用它的方式:

@using  Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IHtmlLocalizer<SharedResources> _localizer;

@SharedLocalizer["Menu_Home"]

目录结构如下:

Here are the contents of SharedResources.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace EduPlaTools
{
    /**
     * This is a dummy class that is needed so Localization works.
     * Now in .NET Core Localization works as a service, and implementsw
     * naming conventions (AT the file level). Therefore, if the files do not
     * implement the correct name, there's going to be problems. 
     * 
     * See an example, here:
     * https://github.com/SteinTheRuler/ASP.NET-Core-Localization/blob/master/Resources/SharedResources.cs
     *
     * This is a workaround to create a Resource File that can be read by the entire
     * application. It's left in blank so the convention over configuration
     * picks it up.
     * 
     * */
    public class SharedResources
    {
    }
}

resx 文件的内容如下:

我也试过重命名它们但无济于事..(试过Resources.es.rex,Resources.rex)

我尝试设置断点以查看它的行为。它当然没有找到资源文件。然后我通过回忆一个不存在的密钥将它与 Mormon's repo 进行比较。我将它与我的输出进行了比较,但是 Mormon 的 repo 没有显示 "SearchedLocation"(它是在后来的 .NET Core 版本中引入的吗?)

摩门教的回购:

我的回购:

我知道这可能有点傻...但是已经将近 4 个小时了,我不能停下来因为我有很多事情要做!!

有什么想法吗?

如果要使用共享资源实现本地化,则必须创建自己的文化本地化程序class:

public class CultureLocalizer
    {
        private readonly IStringLocalizer _localizer;
        public CultureLocalizer(IStringLocalizerFactory factory)
        {
            var type = typeof(ViewResource);
            var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
            _localizer = factory.Create("ViewResource", assemblyName.Name);
        }

        // if we have formatted string we can provide arguments         
        // e.g.: @Localizer.Text("Hello {0}", User.Name)
        public LocalizedString Text(string key, params string[] arguments)
        {
            return arguments == null
                ? _localizer[key]
                : _localizer[key, arguments];
        }
    }

然后注册就是启动:

services.AddSingleton<CultureLocalizer>();

并修改视图定位设置:

services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddViewLocalization(o=>o.ResourcesPath = "Resources")

在您看来,您必须在使用之前注入文化定位器 class。

这些是使用共享资源进行视图本地化的初始设置,您还需要为 DataAnnotation、ModelBinding 和 Identity 错误消息配置本地化设置。

这些文章可以帮助开始:

使用 ASP.NET Core 2.1 Razor Pages 开发多文化 Web 应用程序:

http://www.ziyad.info/en/articles/10-Developing_Multicultural_Web_Application

它包括使用共享资源进行本地化的分步教程,此外,本文是关于本地化身份错误消息的:

http://ziyad.info/en/articles/20-Localizing_Identity_Error_Messages

我想添加一个进一步发展 Laz 解决方案的答案。以防万一有人想要拥有单独的本地化视图。

回到 Startup.cs,你有:

services.AddMvc()
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddViewLocalization(o=>o.ResourcesPath = "Resources")

从技术上讲,您是在指示 MVC 在 "Resources" 文件夹中查找作为主路径,然后按照约定查找本地化的资源文件。

因此 如果您想本地化在 Views/Account/Login.chsmtl 中找到的 Login.cshtml 视图,您必须在以下位置创建资源文件:Resources/Views/Account/Login.en.resx

然后您需要在视图中直接添加以下内容 Login.cshtml 或在 _ViewImports.cshtml 中将其引用到所有视图中:

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

之后,您可以在您的代码中执行以下操作: Localizer["My_Resource_file_key"]

你会翻译它。


以下是一些插图:

对之前答案的更新。由于 .NET Core 3 最近的重大变化 (https://github.com/dotnet/docs/issues/16964),接受的答案只有在资源直接位于资源文件夹中时才有效。 我已经创建了一个在视图中使用共享资源的解决方法(同样适用于控制器、数据注释、服务,无论您需要什么...)。

首先您需要为您的资源创建一个空的 class。这个必须位于 YourApp.Resources 命名空间下。然后创建与 class 同名的资源(在我的示例中,我在命名空间 MyApp.Resources.SharedViews.resx 中有 Views.cs)。

然后这里是加载共享资源的助手class:

public class SharedViewLocalizer
{
    private readonly IStringLocalizer _localizer;

    public SharedViewLocalizer(IStringLocalizerFactory factory)
    {
        var assemblyName = new AssemblyName(typeof(Resources.Shared.Views).GetTypeInfo().Assembly.FullName);
        localizer = factory.Create("Shared.Views", assemblyName.Name);
    }

    public string this[string key] => _localizer[key];

    public string this[string key, params object[] arguments] => _localizer[key, arguments];
}

您必须在 Startup.Configure:

中注册
services.AddSingleton<SharedViewLocalizer>();

我想你使用

services.AddLocalization(options => options.ResourcesPath = "Resources");

设置默认资源位置。

然后在您看来,您可以按如下方式使用它:

@inject IViewLocalizer _localizer
@inject SharedViewLocalizer _sharedLocalizer

@_localizer["View spacific resource"] // Resource from Resources/Views/ControllerName/ViewName.resx
@_sharedLocalizer["Shared resource"] // Resource from Resources/Shared/Views.resx
@_sharedLocalizer["Also supports {0} number of arguments", "unlimited"]

相同的原则可以应用于 DataAnnotations,我们可以在 Startup.Configure 中使用 built-in 方法:

services.AddMvc()
    .AddDataAnnotationsLocalization(options => 
    {
        options.DataAnnotationLocalizerProvider = (type, factory) =>
        {
           var assemblyName = new AssemblyName(typeof(DataAnnotations).GetTypeInfo().Assembly.FullName);
           return factory.Create("Shared.DataAnnotations", assemblyName.Name
        };
    })
    .AddViewLocalization();

同样,我希望我的资源位于命名空间 Resources.Shared 中,并创建一个名为 DataAnnotations 的空 class。

希望这有助于克服当前的重大变更问题。