我很快将从事大型c#项目,并希望从一开始就构建多语言支持。我玩过一遍,可以使用每种语言的单独资源文件来使其工作,然后使用资源管理器加载字符串。
我还有什么其他好的方法可以研究吗?
Answers:
我可以从经验中看出这一点,拥有一个包含12 个项目的当前解决方案,其中包括API,MVC,项目库(核心功能),WPF,UWP和Xamarin。值得阅读这篇长文章,因为我认为这是最好的方法。借助VS工具,可以轻松导出和导入,以发送给翻译机构或其他人进行审查。
编辑02/2018:仍然很强大,将其转换为.NET Standard库使其甚至可以在.NET Framework和NET Core中使用它。我添加了一个额外的部分来将其转换为JSON,例如angular可以使用它。
EDIT 2019:继续使用Xamarin,这仍然适用于所有平台。例如Xamarin.Forms建议也使用resx文件。(我尚未在Xamarin.Forms中开发应用程序,但是该文档(详细入门的方法)涵盖了它:Xamarin.Forms文档)。就像将其转换为JSON一样,我们也可以将其转换为Xamarin.Android的.xml文件。
EDIT 2019(2):从WPF升级到UWP时,我遇到了他们在UWP中更喜欢使用另一种文件类型.resw
,即内容相同但用法不同。我发现这样做的方法不同,我认为它比默认解决方案更好。
编辑2020:更新了一些可能需要多种语言项目的大型(模块化)项目的建议。
因此,让我们开始吧。
专业的
ResourceDirectories
。Thread.CurrentThread.CurrentCulture
骗子
建立
在您的解决方案中创建语言项目,为其命名,例如MyProject.Language。向其中添加一个名为Resources的文件夹,然后在该文件夹中创建两个Resources文件(.resx)。一个称为Resources.resx,另一个称为Resources.en.resx(或特定于.en-GB.resx)。在我的实现中,我将NL(荷兰语)语言作为默认语言,因此该文件位于我的第一个文件中,而英语则位于第二个文件中。
安装程序应如下所示:
确保将自定义工具名称空间设置为项目名称空间。原因是在WPF中,您不能引用Resources
XAML内部。
在资源文件中,将访问修饰符设置为Public:
如果您有这么大的应用程序(比如说不同的模块),则可以考虑创建多个如上所述的项目。在这种情况下,您可以在键和资源类的前面加上特定的模块。使用适用于Visual Studio的最佳语言编辑器将所有文件组合到一个概述中。
在另一个项目中使用
对项目的引用:右键单击引用->添加引用-> Prjects \ Solutions。
在文件中使用名称空间: using MyProject.Language;
在后端中像这样使用它:
string someText = Resources.orderGeneralError;
如果还有其他名为Resources的东西,则只需放入整个命名空间即可。
在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);
}
}
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实现源教程:很棒的教程博客
在类库中使用模型
后端使用是相同的,只是在in属性中使用的一个示例
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模板生成枚举
当然要添加对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;
}
}
}
在UWP中,Microsoft使用此解决方案,这意味着您将需要创建新的资源文件。另外,您也不能重复使用文本,因为它们希望您将x:Uid
XAML中的控件设置为资源中的键。并且您必须在资源Example.Text
中填写TextBlock
的文本。我根本不喜欢该解决方案,因为我想重用资源文件。最终,我提出了以下解决方案。我是今天(2019-09-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。当我仍在从事此项目时,我注意到我需要这样做两次。在用户第一次启动时,我可能会选择一种语言。但是,由于将通过Windows Store分发该语言,因此该语言通常等于系统语言。
然后在XAML中使用:
<TextBlock Text="{CustomResource ExampleResourceKey}"></TextBlock>
如今,将Angular之类的框架与组件结合使用更为普遍,因此无需cshtml。翻译内容存储在json文件中,我将不介绍其工作原理,我强烈建议您使用ngx-translate而不是角度多重翻译。因此,如果您想将翻译转换为JSON文件,这非常简单,我使用了一个T4模板脚本,该脚本将Resources文件转换为json文件。我建议安装T4编辑器以阅读语法并正确使用它,因为您需要进行一些修改。
仅需注意一件事:无法生成数据,将其复制,清理数据并以另一种语言生成它。因此,您必须复制下面的代码与您使用的语言一样多的次数,并在“ // choose language here”之前更改条目。目前没有时间解决此问题,但可能会在以后更新(如果有兴趣)。
路径: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);
}
#>
如果您有模块化的应用程序,并且按照我的建议创建了多个语言项目,则必须为每个项目创建一个T4文件。确保json文件是在逻辑上定义的,不一定必须是en.json
,也可以是example-en.json
。要将多个json文件与ngx-translate结合使用,请按照此处的说明进行操作
如上面的更新所述,我使用与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("&", "&");
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文档非常容易,并可以让他人对其进行翻译并再次导入。
特别感谢这个令人惊叹的VS扩展,它对resx
文件的处理非常好。考虑为他的出色工作捐赠给他(我与此无关,我只喜欢扩展名)。
我认为没有“最佳方法”。这实际上取决于您所构建的技术和应用程序类型。
Webapp可以按照其他发布者的建议将信息存储在数据库中,但是我建议使用单独的资源文件。那就是资源文件与您的资源分开。单独的资源文件减少了对相同文件的争用,并且随着项目的增长,您可能会发现本地化将与业务逻辑分开进行。(程序员和翻译者)。
Microsoft WinForm和WPF专家建议使用针对每个语言区域自定义的单独资源程序集。
WPF能够将UI元素调整为内容大小,从而降低了所需的布局工作,例如:(日语单词比英语短得多)。
如果您正在考虑使用WPF:建议您阅读此msdn文章 。老实说,我发现使用Wbuild本地化工具:msbuild,locbaml(以及excel电子表格)非常繁琐,但确实可以使用。
仅有一点相关的东西:我面临的一个常见问题是集成发送错误消息(通常是英语)而不是错误代码的旧系统。这会强制更改旧系统,或者将后端字符串映射到我自己的错误代码,然后再映射到本地字符串... yech。 错误代码是本地化朋友
+1数据库
如果对数据库进行了更正,则您的应用程序中的表单甚至可以即时进行自动转换。
我们使用了一个系统,其中所有控件都以XML文件(每种形式一个)映射到语言资源ID,但是所有ID都在数据库中。
基本上,不是让每个控件都保留ID(实现接口,也不是在VB6中使用tag属性),而是使用了一个事实,即在.NET中,控件树很容易通过反射来发现。如果缺少该表单,则加载表单时的过程将生成XML文件。XML文件会将控件映射到它们的资源ID,因此只需填写并映射到数据库即可。这意味着,如果未标记某些东西,或者需要将其拆分为另一个ID,则无需更改已编译的二进制文件(某些可能同时用作名词和动词的英语单词可能需要翻译成两个不同的单词(在字典中),并且不会重复使用,但是在ID的初始分配期间您可能不会发现它)。
只有在使用带有插入点的阶段时,应用程序才会参与其中。
数据库翻译软件是您基本的CRUD维护屏幕,具有各种工作流程选项,可帮助您完成丢失的翻译等。
我一直在搜索,发现了这一点:
如果您使用WPF或Silverlight,则出于多种原因可以使用WPF LocalizationExtension。
它是开源的,它是免费的(并且将保持免费),处于真正的稳定状态
在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上,方法可能是相同的。
祝好运!
我会处理多个资源文件。配置起来并不难。实际上,我最近回答了关于与表单语言资源文件一起设置基于全局语言的资源文件的类似问题。
我认为至少对于WinForm开发而言,这是最好的方法。
标准资源文件更容易。但是,如果您有任何语言相关的数据(例如查找表),那么您将不得不管理两个资源集。
我还没有做,但是在我的下一个项目中,我将实现一个数据库资源提供者。我发现如何在MSDN上执行此操作:
http://msdn.microsoft.com/en-us/library/aa905797.aspx
我还发现了此实现: