gpt4 book ai didi

c# - 在大型 .NET 项目中实现多语言/全局化的最佳方式

转载 作者:IT王子 更新时间:2023-10-29 03:42:33 26 4
gpt4 key购买 nike

我很快就会从事一个大型 c# 项目,并希望从一开始就构建多语言支持。我已经玩过了,可以使用每种语言的单独资源文件让它工作,然后使用资源管理器加载字符串。

还有其他好的方法可以研究吗?

最佳答案

使用带有资源的单独项目
我可以从经验中看出这一点,目前的解决方案有 12 个 24 个项目,其中包括 API、MVC、项目库(核心功能)、WPF、UWP 和 Xamarin。这篇长文值得一读,因为我认为这是最好的方法。借助 VS 工具,可轻松导出和导入以发送给翻译机构或由其他人审阅。
EDIT 02/2018: 仍然很强大,将其转换为 .NET Standard 库,甚至可以跨 .NET Framework 和 NET Core 使用它。我添加了一个额外的部分来将其转换为 JSON,因此例如 angular 可以使用它。
EDIT 2019: 继续使用 Xamarin,这仍然适用于所有平台。例如。 Xamarin.Forms 建议也使用 resx 文件。 (我还没有在 Xamarin.Forms 中开发应用程序,但是文档,这是刚开始的详细方式,涵盖了它: Xamarin.Forms Documentation )。就像将其转换为 JSON 一样,我们也可以将其转换为 Xamarin.Android 的 .xml 文件。
EDIT 2019 (2): 在从 WPF 升级到 UWP 时,我遇到在 UWP 中他们更喜欢使用另一种文件类型 .resw ,这在内容方面相同但用法不同。我找到了一种不同的方法,在我看来,它比 the default solution 效果更好。
2020 年编辑: 更新了一些可能需要多语言项目的大型(模块化)项目的建议。
所以,让我们开始吧。
专业人士的

  • 几乎无处不在的强类型。
  • 在 WPF 中,您不必处理 ResourceDirectories
  • 据我测试,支持 ASP.NET、类库、WPF、Xamarin、.NET Core、.NET Standard。
  • 不需要额外的第三方库。
  • 支持文化回退:en-US -> en。
  • 不仅是后端,还适用于 WPF 的 XAML 和 Xamarin.Forms,适用于 MVC 的 .cshtml。
  • 通过更改 Thread.CurrentThread.CurrentCulture
  • 轻松操纵语言
  • 搜索引擎可以用不同的语言抓取,用户可以发送或保存特定语言的网址。

  • 骗局
  • WPF XAML 有时会出错,新添加的字符串不会直接显示。重建是临时修复(vs2015)。
  • UWP XAML 不显示智能感知建议,并且在设计时不显示文本。
  • 告诉我。

  • 设置
    在您的解决方案中创建语言项目,给它一个像 MyProject.Language 这样的名字。向其中添加一个名为 Resources 的文件夹,并在该文件夹中创建两个资源文件 (.resx)。一个称为 Resources.resx ,另一个称为 Resources.en.resx (或特定的 .en-GB.resx)。在我的实现中,我将 NL(荷兰语)语言作为默认语言,因此它在我的第一个文件中,而英语在我的第二个文件中。
    设置应如下所示:
    language setup project
    Resources.resx 的属性必须是:
    properties
    确保自定义工具命名空间设置为您的项目命名空间。原因是在 WPF 中,您不能在 XAML 中引用 Resources
    在资源文件中,将访问修饰符设置为 Public:
    access modifier
    如果您有如此大的应用程序(假设不同的模块),您可以考虑创建多个像上面这样的项目。在这种情况下,您可以使用特定的模块作为键​​和资源类的前缀。使用 best language editor,Visual Studio 可以将所有文件合并为一个概览。
    在另一个项目中使用
    引用您的项目:右键单击“引用”->“添加引用”->“项目\解决方案”。
    在文件中使用命名空间: using MyProject.Language;在后端像这样使用它: string someText = Resources.orderGeneralError;如果还有其他叫做资源的东西,那么只需放入整个命名空间。
    在MVC中使用
    在 MVC 中,您可以根据自己的喜好设置语言,但我使用了参数化的 url,可以像这样设置:
    RouteConfig.cs
    在其他映射下方
    routes.MapRoute(
    name: "Locolized",
    url: "{lang}/{controller}/{action}/{id}",
    constraints: new { lang = @"(\w{2})|(\w{2}-\w{2})" }, // en or en-US
    defaults: new { controller = "shop", action = "index", id = UrlParameter.Optional }
    );
    FilterConfig.cs (可能需要添加,如果需要添加 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);Application_start()中的 Global.asax方法
    public class FilterConfig
    {
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
    filters.Add(new ErrorHandler.AiHandleErrorAttribute());
    //filters.Add(new HandleErrorAttribute());
    filters.Add(new LocalizationAttribute("nl-NL"), 0);
    }
    }
    本地化属性
    public class LocalizationAttribute : ActionFilterAttribute
    {
    private string _DefaultLanguage = "nl-NL";
    private string[] allowedLanguages = { "nl", "en" };

    public LocalizationAttribute(string defaultLanguage)
    {
    _DefaultLanguage = defaultLanguage;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
    string lang = (string) filterContext.RouteData.Values["lang"] ?? _DefaultLanguage;
    LanguageHelper.SetLanguage(lang);
    }
    }
    LanguageHelper 只是设置文化信息。
    //fixed number and date format for now, this can be improved.
    public static void SetLanguage(LanguageEnum language)
    {
    string lang = "";
    switch (language)
    {
    case LanguageEnum.NL:
    lang = "nl-NL";
    break;
    case LanguageEnum.EN:
    lang = "en-GB";
    break;
    case LanguageEnum.DE:
    lang = "de-DE";
    break;
    }
    try
    {
    NumberFormatInfo numberInfo = CultureInfo.CreateSpecificCulture("nl-NL").NumberFormat;
    CultureInfo info = new CultureInfo(lang);
    info.NumberFormat = numberInfo;
    //later, we will if-else the language here
    info.DateTimeFormat.DateSeparator = "/";
    info.DateTimeFormat.ShortDatePattern = "dd/MM/yyyy";
    Thread.CurrentThread.CurrentUICulture = info;
    Thread.CurrentThread.CurrentCulture = info;
    }
    catch (Exception)
    {

    }
    }
    .cshtml 中的用法
    @using MyProject.Language;
    <h3>@Resources.w_home_header</h3>
    或者,如果您不想定义 using,则只需填写整个命名空间,或者您可以在/Views/web.config 下定义命名空间:
    <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
    <namespaces>
    ...
    <add namespace="MyProject.Language" />
    </namespaces>
    </pages>
    </system.web.webPages.razor>
    本mvc实现源码教程: Awesome tutorial blog
    在模型类库中使用
    后端使用是一样的,只是在属性中使用的一个例子
    using MyProject.Language;
    namespace MyProject.Core.Models
    {
    public class RegisterViewModel
    {
    [Required(ErrorMessageResourceName = "accountEmailRequired", ErrorMessageResourceType = typeof(Resources))]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }
    }
    }
    如果你有 reshaper,它会自动检查给定的资源名称是否存在。如果你更喜欢类型安全,你可以使用 T4 templates to generate an enum
    在 WPF 中使用。
    当然添加对 MyProject.Language 命名空间的引用,我们知道如何在后端使用它。
    在 XAML 中,在 Window 或 UserControl 的 header 内,添加名为 lang 的命名空间引用,如下所示:
    <UserControl x:Class="Babywatcher.App.Windows.Views.LoginView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:MyProject.App.Windows.Views"
    xmlns:lang="clr-namespace:MyProject.Language;assembly=MyProject.Language" <!--this one-->
    mc:Ignorable="d"
    d:DesignHeight="210" d:DesignWidth="300">
    然后,在标签内:
        <Label x:Name="lblHeader" Content="{x:Static lang:Resources.w_home_header}" TextBlock.FontSize="20" HorizontalAlignment="Center"/>
    由于它是强类型的,因此您确定资源字符串存在。您有时可能需要在安装过程中重新编译项目,WPF 有时会因新命名空间而出错。
    WPF 的另一件事是在 App.xaml.cs 中设置语言。您可以自己实现(在安装过程中选择)或让系统决定。
    public partial class App : Application
    {
    protected override void OnStartup(StartupEventArgs e)
    {
    base.OnStartup(e);
    SetLanguageDictionary();
    }

    private void SetLanguageDictionary()
    {
    switch (Thread.CurrentThread.CurrentCulture.ToString())
    {
    case "nl-NL":
    MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("nl-NL");
    break;
    case "en-GB":
    MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
    break;
    default://default english because there can be so many different system language, we rather fallback on english in this case.
    MyProject.Language.Resources.Culture = new System.Globalization.CultureInfo("en-GB");
    break;
    }

    }
    }
    在 UWP 中使用
    在 UWP 中,Microsoft 使用 this solution ,这意味着您需要创建新的资源文件。另外,您也不能重复使用文本,因为他们希望您将 XAML 中控件的 x:Uid 设置为资源中的键。在您的资源中,您必须执行 Example.Text 来填充 TextBlock 的文本。我根本不喜欢那个解决方案,因为我想重新使用我的资源文件。最终我想出了以下解决方案。我今天(2019 年 9 月 26 日)才发现这一点,所以如果结果证明这不能按预期工作,我可能会带回其他东西。
    将此添加到您的项目中:
    using Windows.UI.Xaml.Resources;

    public class MyXamlResourceLoader : CustomXamlResourceLoader
    {
    protected override object GetResource(string resourceId, string objectType, string propertyName, string propertyType)
    {
    return MyProject.Language.Resources.ResourceManager.GetString(resourceId);
    }
    }
    将此添加到构造函数中的 App.xaml.cs:
    CustomXamlResourceLoader.Current = new MyXamlResourceLoader();
    无论您想在应用程序中的哪个位置,使用它来更改语言:
    ApplicationLanguages.PrimaryLanguageOverride = "nl";
    Frame.Navigate(this.GetType());
    需要最后一行来刷新 UI。当我仍在从事这个项目时,我注意到我需要这样做 2 次。我可能会在用户第一次启动时选择语言。但由于这将通过 Windows 应用商店分发,因此语言通常等于系统语言。
    然后在 XAML 中使用:
    <TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
    在 Angular 中使用它(转换为 JSON)
    现在,将像 Angular 这样的框架与组件结合使用更为常见,因此没有 cshtml。翻译存储在 json 文件中,我不打算介绍它是如何工作的,我强烈推荐 ngx-translate 而不是 angular multi-translation。因此,如果您想将翻译转换为 JSON 文件,这很容易,我使用了一个 T4 模板脚本,将资源文件转换为 json 文件。我建议安装 T4 editor 来读取语法并正确使用它,因为您需要做一些修改。
    只有一件事需要注意:不可能生成数据、复制数据、清理数据并为另一种语言生成数据。因此,您必须根据您拥有的语言多次复制以下代码,并更改“//在此处选择语言”之前的条目。目前没有时间解决这个问题,但可能会稍后更新(如果有兴趣)。
    路径:MyProject.Language/T4/CreateLocalizationEN.tt
    <#@ template debug="false" hostspecific="true" language="C#" #>
    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Windows.Forms" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="System.Resources" #>
    <#@ import namespace="System.Collections" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.ComponentModel.Design" #>
    <#@ output extension=".json" #>
    <#


    var fileNameNl = "../Resources/Resources.resx";
    var fileNameEn = "../Resources/Resources.en.resx";
    var fileNameDe = "../Resources/Resources.de.resx";
    var fileNameTr = "../Resources/Resources.tr.resx";

    var fileResultName = "../T4/CreateLocalizationEN.json";//choose language here
    var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);
    //var fileDestinationPath = "../../MyProject.Web/ClientApp/app/i18n/";

    var fileNameDestNl = "nl.json";
    var fileNameDestEn = "en.json";
    var fileNameDestDe = "de.json";
    var fileNameDestTr = "tr.json";

    var pathBaseDestination = Directory.GetParent(Directory.GetParent(this.Host.ResolvePath("")).ToString()).ToString();

    string[] fileNamesResx = new string[] {fileNameEn }; //choose language here
    string[] fileNamesDest = new string[] {fileNameDestEn }; //choose language here

    for(int x = 0; x < fileNamesResx.Length; x++)
    {
    var currentFileNameResx = fileNamesResx[x];
    var currentFileNameDest = fileNamesDest[x];
    var currentPathResx = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", currentFileNameResx);
    var currentPathDest =pathBaseDestination + "/MyProject.Web/ClientApp/app/i18n/" + currentFileNameDest;
    using(var reader = new ResXResourceReader(currentPathResx))
    {
    reader.UseResXDataNodes = true;
    #>
    {
    <#
    foreach(DictionaryEntry entry in reader)
    {
    var name = entry.Key;
    var node = (ResXDataNode)entry.Value;
    var value = node.GetValue((ITypeResolutionService) null);
    if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\n", "");
    if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\r", "");
    #>
    "<#=name#>": "<#=value#>",
    <#


    }
    #>
    "WEBSHOP_LASTELEMENT": "just ignore this, for testing purpose"
    }
    <#
    }
    File.Copy(fileResultPath, currentPathDest, true);
    }


    #>
    如果您有一个 modulair 应用程序并且您按照我的建议创建了多个语言项目,那么您必须为每个项目创建一个 T4 文件。确保 json 文件是逻辑定义的,它不必是 en.json ,也可以是 example-en.json 。要组合多个 json 文件以与 ngx-translate 一起使用,请按照说明 here
    在 Xamarin.Android 中使用
    正如上面在更新中所解释的,我使用的方法与我对 Angular/JSON 所做的相同。但是 Android 使用 XML 文件,所以我编写了一个 T4 文件来生成这些 XML 文件。
    路径:MyProject.Language/T4/CreateAppLocalizationEN.tt
    #@ template debug="false" hostspecific="true" language="C#" #>
    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Windows.Forms" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="System.Resources" #>
    <#@ import namespace="System.Collections" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.ComponentModel.Design" #>
    <#@ output extension=".xml" #>
    <#
    var fileName = "../Resources/Resources.en.resx";
    var fileResultName = "../T4/CreateAppLocalizationEN.xml";
    var fileResultRexPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileName);
    var fileResultPath = Path.Combine(Path.GetDirectoryName(this.Host.ResolvePath("")), "MyProject.Language", fileResultName);

    var fileNameDest = "strings.xml";

    var pathBaseDestination = Directory.GetParent(Directory.GetParent(this.Host.ResolvePath("")).ToString()).ToString();

    var currentPathDest =pathBaseDestination + "/MyProject.App.AndroidApp/Resources/values-en/" + fileNameDest;

    using(var reader = new ResXResourceReader(fileResultRexPath))
    {
    reader.UseResXDataNodes = true;
    #>
    <resources>
    <#

    foreach(DictionaryEntry entry in reader)
    {
    var name = entry.Key;
    //if(!name.ToString().Contains("WEBSHOP_") && !name.ToString().Contains("DASHBOARD_"))//only include keys with these prefixes, or the country ones.
    //{
    // if(name.ToString().Length != 2)
    // {
    // continue;
    // }
    //}
    var node = (ResXDataNode)entry.Value;
    var value = node.GetValue((ITypeResolutionService) null);
    if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\n", "");
    if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("\r", "");
    if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("&", "&amp;");
    if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("<<", "");
    //if (!String.IsNullOrEmpty(value.ToString())) value = value.ToString().Replace("'", "\'");
    #>
    <string name="<#=name#>">"<#=value#>"</string>
    <#
    }
    #>
    <string name="WEBSHOP_LASTELEMENT">just ignore this</string>
    <#
    #>
    </resources>
    <#
    File.Copy(fileResultPath, currentPathDest, true);
    }

    #>
    Android 使用 values-xx 文件夹,所以上面是 values-en 文件夹中的英文。但是您还必须生成一个进入 values 文件夹的默认值。只需复制上面的 T4 模板并更改上面代码中的文件夹。
    好了,您现在可以为所有项目使用一个资源文件。这使得将所有内容导出到 excl 文档并让某人翻译并再次导入变得非常容易。
    特别感谢 this amazing VS extensionresx 文件一起工作很棒。考虑 donating 给他的出色工作(我与此无关,我只是喜欢扩展名)。

    关于c# - 在大型 .NET 项目中实现多语言/全局化的最佳方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/373388/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com