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

时间:2008-12-17 01:23:56

标签: c# .net localization multilingual

我很快就会开始研究一个大型的c#项目,并希望从一开始就构建多语言支持。我有一个游戏,可以使用每种语言的单独资源文件使其工作,然后使用资源管理器加载字符串。

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

10 个答案:

答案 0 :(得分:40)

使用与资源

的单独项目

我可以从经验中看出这一点,目前的解决方案是 12 24 项目,其中包括API,MVC,项目库(核心功能),WPF和Xamarin。值得一读这篇长篇文章,因为我觉得这是最好的方法。在VS工具的帮助下,可以轻松导出和导入,发送给翻译机构或由其他人审核。

编辑02/2018:仍然强大,将其转换为.NET标准库,甚至可以在.NET Framework和.NET Core中使用它。我添加了一个额外的部分,用于将其转换为JSON,例如angular可以使用它。

编辑:2019年:继续使用Xamarin,这仍适用于所有平台。例如。 Xamarin.Forms也建议使用resx文件。 (我还没有在Xamarin.Forms中开发一个应用程序,但文档,这是详细的入门方式,涵盖了它:Xamarin.Forms Documentation)。就像将其转换为JSON一样,我们也可以将其转换为Xamarin.Android的一个.xml文件(目前正在处理)。

所以,让我们来看看。

<强>临&#39; S

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

<强>精读&#39; S

  • WPF XAML有时会出现问题,新添加的字符串不会直接显示。重建是临时修复(vs2015)。
  • 告诉我。

<强>设置

在解决方案中创建语言项目,为其命名,如 MyProject.Language 。添加一个名为Resources的文件夹,在该文件夹中创建两个Resources文件(.resx)。一个名为 Resources.resx ,另一个名为 Resources.en.resx (或特定的.en-GB.resx)。在我的实现中,我使用NL(荷兰语)语言作为默认语言,因此这是我的第一个文件,英语在我的第二个文件中。

安装程序应如下所示:

language setup project

Resources.resx的属性必须是: properties

确保将自定义工具命名空间设置为项目命名空间。原因是在WPF中,您无法在XAML中引用Resources

在资源文件中,将访问修饰符设置为Public:

access modifier

在其他项目中使用

参考您的项目:右键点击参考文献 - &gt;添加参考 - &gt; Prjects \解。

在文件中使用命名空间:using MyProject.Language;

在后端使用它: string someText = Resources.orderGeneralError;     如果还有其他名为Resources的东西,那么只需输入整个命名空间。

在MVC中使用

在MVC中,您可以设置语言,但我使用参数化网址,可以这样设置:

<强> 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);
    }
}

<强> LocalizationAttribute

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>

或者如果您不想定义使用,那么只需填写整个命名空间,或者您可以在/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; }
    }
}

如果您有重塑器,它将自动检查给定的资源名称是否存在。如果您更喜欢类型安全,可以使用T4 templates to generate an enum

在WPF中使用。

Ofcourse添加对 MyProject.Language 命名空间的引用,我们知道如何在后端使用它。

在XAML中,在Window或UserControl的标题内,添加名为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;
        }

    }
}

在Angular中使用它(转换为JSON)

现在,将Angular这样的框架与组件结合使用更为常见,因此没有cshtml。翻译存储在json文件中,我不打算介绍它是如何工作的,但是如果你想将它转换为JSON文件,那很简单,我使用T4模板脚本将Resources文件转换为json文件。我建议安装T4 editor来阅读语法并正确使用它,因为您需要进行一些修改。

只需注意一件事:无法生成数据,复制数据,清理数据并为其他语言生成数据。因此,您必须将下面的代码复制到您拥有的语言之前,并在“在此处选择语言”之前更改条目&#39;。目前没有时间解决这个问题,但可能会在以后更新(如果感兴趣)。

路径:MyProject.Language / T4 / CreateWebshopLocalizationEN.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/CreateWebshopLocalizationEN.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);
}


#>

在那里,您现在可以为所有项目使用一个资源文件。这使得将所有内容导出到excl文档非常容易,并让某人翻译并再次导入。

答案 1 :(得分:19)

我见过使用多种不同方法实施的项目,每种方法都有其优点和缺点。

  • 一个人在配置文件中做了(不是我最喜欢的)
  • 一个人使用数据库做到了 - 这个效果很好,但是你知道要维护什么是痛苦的。
  • 一个使用的资源文件就像你建议的那样,我不得不说这是我最喜欢的方法。
  • 最基本的一个是使用一个充满字符串的包含文件 - 丑陋。

我会说你选择的资源方法很有意义。看到其他人的答案也会很有趣,因为我常常想知道是否有更好的办法来做这样的事情。我已经看到许多资源都指向使用资源方法,包括one right here on SO

答案 2 :(得分:5)

我认为没有“最佳方式”。它实际上取决于您正在构建的技术和应用程序类型。

Webapps可以像其他海报建议的那样将信息存储在数据库中,但我建议使用单独的资源文件。这是与您的来源分开的资源文件。单独的资源文件减少了对相同文件的争用,并且随着项目的增长,您可能会发现本地化将从业务逻辑中单独完成。 (程序员和翻译)。

Microsoft WinForm和WPF专家建议使用为每个语言环境定制的单独资源程序集。

WPF将UI元素大小调整为内容的能力降低了所需的布局工作,例如:(日语单词比英语短得多)。

如果您正在考虑WPF:I suggest reading this msdn article 说实话,我发现WPF本地化工具:msbuild,locbaml,(可能是一个excel电子表格)使用起来很乏味,但确实有用。

仅略微相关:我遇到的一个常见问题是集成发送错误消息的遗留系统(通常是英文版),而不是错误代码。这会强制更改遗留系统,或将后端字符串映射到我自己的错误代码,然后映射到本地化字符串...... yech。 错误代码是本地化好友

答案 3 :(得分:4)

+1数据库

如果对数据库进行了更正,您的应用中的表单甚至可以即时重新翻译。

我们使用的系统将所有控件都映射到XML文件(每个表单一个)到语言资源ID,但所有ID都在数据库中。

基本上,我们使用的事实是,在.NET中,控件树很容易通过反射发现,而不是让每个控件都持有ID(实现接口,或使用VB6中的tag属性)。加载表单时,如果缺少XML文件,则构建XML文件的过程。 XML文件会将控件映射到其资源ID,因此只需填写并映射到数据库。这意味着如果没有标记某些内容,或者需要将其拆分为另一个ID,则无需更改已编译的二进制文件(英语中的某些单词可能会被用作名词和动词,可能需要转换为两个不同的单词在字典中,不能重复使用,但在初始分配ID时可能不会发现这一点。但事实是整个翻译过程完全独立于你的二进制文件(每个表单都必须从知道如何翻译自身及其所有控件的基本形式继承。)

应用程序更多涉及的唯一方法是使用带有插入点的阶段。

数据库翻译软件是您的基本CRUD维护屏幕,其中包含各种工作流程选项,以便于浏览缺失的翻译等。

答案 4 :(得分:2)

我会使用多个资源文件。配置应该不难。 事实上,我最近回答了一个关于设置基于全局语言的资源文件和表单语言资源文件的类似问题。

Localization in Visual Studio 2008

我认为这是至少对WinForm开发的最佳方法。

答案 5 :(得分:2)

您可以使用Sisulizer等商业工具。它将为每种语言创建附属程序集。你唯一应该注意的是不要混淆形式类名(如果使用混淆器)。

答案 6 :(得分:2)

我一直在搜索,我发现了这个:

如果您使用WPF或Silverlight,则出于多种原因,您的aproach可能会被WPF LocalizationExtension使用。

IT的开源 这是免费的(并将保持免费) 处于真正的稳定状态

在Windows应用程序中,您可以这样做:

public partial class App : Application  
{  
     public App()  
     {             
     }  

     protected override void OnStartup(StartupEventArgs e)  
     {  
         Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); ;  
         Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("de-DE"); ;  

          FrameworkElement.LanguageProperty.OverrideMetadata(  
              typeof(FrameworkElement),  
              new FrameworkPropertyMetadata(  
                  XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));  
          base.OnStartup(e);  
    }  
} 

我认为在Wep Page上,aproach可能是一样的。

祝你好运!

答案 7 :(得分:0)

大多数开源项目都使用GetText来实现此目的。我不知道以前是如何在.Net项目上使用它的。

答案 8 :(得分:0)

标准资源文件更容易。但是,如果您有任何语言相关数据(如查找表),则必须管理两个资源集。

我还没有这样做,但在我的下一个项目中,我将实现一个数据库资源提供程序。我在MSDN上找到了如何做到这一点:

http://msdn.microsoft.com/en-us/library/aa905797.aspx

我也发现了这个实现:

DBResource Provider

答案 9 :(得分:0)

我们使用自定义提供程序来支持多语言,并将所有文本放在数据库表中。除非我们在更新数据库中的文本而不更新Web应用程序时遇到缓存问题,否则它很有效。