准备好所有应用程序以进行本地化

目录

介绍

本地化更长的字符串

设计理由

与ResourceManager结合使用

使用字符串插值进行本地化

源代码


介绍

几年来,我一直在构建一个主题为应该构建到.NET框架中的东西,但不是的库。但我一直推迟写关于应该内置的东西的文章,但是,你知道,不是。再也不是!它被称为Loyc.Essentials,您可以通过NuGet获取它(它以Loyc命名,但这并不重要。)

Loyc.Essentials有一个Localize类,它是一个全局钩子,可以在其中安装字符串映射本地化器。如果你还在使用Loyc.Essentials,你应该使用它。它准备将您的程序翻译成其他语言,几乎不费吹灰之力。

这个想法是通过让本地化变得非常容易来说服程序员支持它。默认情况下,它没有连接到任何翻译器(它只是通过字符串),所以只为一种语言市场编写程序的人可以轻松地使他们的代码多语言准备,而无需做任何额外的工作。

你所要做的就是调用.Localized()扩展方法,这实际上比编写传统string.Format()方法要短。(同时:using Loyc;

编辑:一般来说,这不适用于C6的插值字符串($"..."),因为C6是如何设计的,但本文末尾描述了一种解决方法。

翻译系统本身独立于Localize,并通过委托连接Localized(),以便多语言翻译系统成为可能。此类应该适用于任何.NET程序,并且使用此实用程序的某些程序将希望使用不同的本地化程序。

像这样使用它:

string result = "Hello, {0}".Localized(userName);

或者,为了提高清晰度,请使用命名占位符:

string result = "Hello, {person's name}".Localized("person's name", userName);

无论安装何种本地化程序,都会在其数据库中查找文本并返回翻译。如果没有最终用户语言的翻译,则应返回适当的默认翻译:原始文本或某些默认语言的翻译,例如英语。

本地化程序需要一个外部翻译表,在概念上如下:

关键名称

语言

翻译文本

Hello,{0}”

“ES”

“Hola{0}”

Hello,{0}”

“FR”

“Bonjour{0}”

Load

“ES”

“Cargar”

Load

“FR”

Charge

Save

“ES”

Guardar

Save

“FR”

“Enregistrer”

许多开发人员使用resx文件来存储翻译。支持这一点,如下所述。

本地化更长的字符串

对于较长的消息,最好使用短名称来表示消息,以便在编辑英文文本时,不必更改其他语言的转换表。为此,请使用以下Symbol方法:

// The translation table will be searched for "ConfirmQuitWithoutSaving"
string result = Localize.Symbol("ConfirmQuitWithoutSaving",
    "Are you sure you want to quit without saving '{filename}'?", "filename", fileName);

// Enhanced C# syntax with symbol literal
string result = Localize.Symbol(@@ConfirmQuitWithoutSaving,
    "Are you sure you want to quit without saving '{filename}'?", "filename", fileName);

这对于长字符串或文本段落最有用,但我希望某些项目,作为策略,使用符号表示所有可本地化的文本。

同样,您可以在不设置任何转换表的情况下调用此方法。但是,允许实际的消息为null。在这种情况下,如果没有设置翻译器或没有可用的翻译,则Localize.Symbol返回符号本身(第一个参数)作为最后的手段。

如果变量参数列表不为空,则Localize.Formatter被调用以从格式字符串构建完成的字符串。可以单独进行格式化——例如:

Console.WriteLine("{0} is {0:X} in hexadecimal".Localized(), N);

在此示例中,WriteLine它本身执行格式化,而不是Localized

如上所示,Localize默认格式化程序StringExt.FormatCore具有标准格式化程序不具备的额外功能:命名参数。这是一个例子:

...
string verb = (IsFileLoaded ? "parse" : "load").Localized();
MessageBox.Show(
    "Not enough memory to {load/parse} '{filename}'.".Localized(
      "load/parse", verb, "filename", FileName));

如您所见,通过指定参数名称{filename}而不是像{0}这样的数字,在格式字符串中提到了命名参数。变量参数列表包含相同的名称,后跟其值,例如"filename", FileName。此功能使您(开发人员)有机会告诉写作翻译人员特定参数的目的是什么。

译者不得改变任何论点:这个词{filename}不能被翻译。

在运行时,带有命名参数的格式字符串将转换为带有编号参数的普通格式字符串。上面的示例将变为Could not {1} the file: {3},然后传递给string.Format

设计理由

许多开发人员不想花时间编写国际化或本地化代码,并且很想编写仅适用于一种语言的代码。毫无疑问,因为与硬编码字符串相比,这是一个痛苦的问题。Microsoft建议代码携带一个ResourceManager对象并直接从中请求字符串:

private ResourceManager rm;
   
rm = new ResourceManager("RootNamespace.Resources", this.GetType().Assembly);
   
Console.Writeline(rm.GetString("StringIdentifier"));

这种方法有缺点:

  • 在可能包含可本地化字符串的所有类之间传递ResourceManager实例可能很麻烦全局工具更方便。
  • 程序员必须将所有翻译放在资源文件中因此,编写代码很麻烦,因为程序员必须切换到资源文件并将字符串添加到代码中。反过来,读取代码的人无法分辨出字符串的含义,并且必须加载资源文件才能找到答案。
  • 改变本地化管理并不容易例如,如果有人想将翻译存储在an.ini.xml.les文件中而不是在程序集内部,该怎么办?如果用户想要集中一组程序集的所有翻译,而不是在每个程序集中拥有单独的资源,该怎么办?
  • 如果找不到请求的标识符,则GetString返回null,可能导致空白输出或一个NullReferenceException异常 

Microsoft确实通过提供Visual Studio内置的代码生成器来解决第一个缺点,它为每个字符串提供了一个全局属性这里

即便如此,您可能会发现此类提供了一种更方便的方法,因为您的本地语言字符串是在代码中编写的,并且如果所需的语言不可用,您可以保证在运行时获取字符串(非空)。

ResourceManager结合使用

该类通过UseResourceManager帮助类方法支持ResourceManager 。例如,在调用Localize.UseResourceManager(resourceManager)之后,如果你编写

"Save As...".Localized()

然后resourceManager.GetString("Save As...") 被调用以获取已翻译的字符串,如果未找到翻译则调用原始字符串(是的,在您的resx文件中,您可以在左侧使用空格和标点符号)。您甚至可以添加名称计算器来编码resx文件的命名约定,例如删除空格和标点符号(有关详细信息,请查看UseResourceManager方法。)

.NET程序中,通常有一个”resx文件,例如Resources.resx,它包含默认字符串,以及其他非英语翻译文件(例如,西班牙语的Resources.es.resx)。当使用Localized()时您可能会使用稍微不同的方法:您仍然为项目创建一个Resources.resx文件,但是您将字符串表保留为空(您仍然可以将其用于其他资源,例如图标)。这会导致Visual Studio生成一个具有ResourceManager属性的Resources类,以便您可以轻松获取所需的ResourceManager实例。

  • 程序启动时,调用Localize.UseResourceManager(Resources.ResourceManager)
  • 使用Localized()扩展方法获取短字符串的翻译。
  • 对于长串,请使用Localize.Symbol("ShortAlias", "Long string", params...)。第一个参数是传递给ResourceManager.GetString()的字符串

使用字符串插值进行本地化

可以将本地化与C6内插字符串结合起来,就像在  $"this string {...}"中一样(并感谢  Florian Rappl 让我注意到这一点。)

不幸的是,Localize() 不能与他们合作。

最初我认为它根本不可能,因为通常字符串插值被转换为  string.Format,其行为无法自定义。但是,与lambda方法有时成为表达式树的方式大致相同,如果目标方法接受System.FormattableString对象,编译器将切换  string.Format FormattableStringFactory.Create.NET 4.6方法)。

问题是,如果可能的话,编译器会更喜欢调用string.Format,所以如果有一个接受FormattableStringLocalized()重载  ,它就不能用于字符串插值,因为C#编译器会简单地忽略它(因为Localized()它已经可以接受一个字符串)。实际上,它比这更糟糕:编译器在调用扩展方法时也拒绝使用FormattableString 

如果您使用非扩展方法,它可以工作。例如:

static class Loca
{
    public static string lize(this FormattableString message)
        { return message.Format.Localized(message.GetArguments()); }
}

然后你可以像这样使用它:

public class Program
{
    public static void Main(string[] args)
    {
        Localize.UseResourceManager(Resources.ResourceManager);

        var name = "Dave";
        Console.WriteLine(Loca.lize($"Hello, {name}"));
    }
}

重要的是要意识到编译器将$"..."字符串转换为旧式格式字符串。所以在这个例子中,Loca.lize 实际上接收"Hello, {0}"的格式字符串而不是"Hello, {name}"

不幸的是,有点令人困惑的是,与普通字符串相比,我们需要一种完全不同的本地化插值字符串方式,如果你忘了——如果你写了$"Hello, {name}".Localized()——你的代码将被破坏,因为格式化将在本地化之前发生,因此没有翻译将被找到。

为了避免这种混淆,我不打算扩展我的库以支持字符串插值,但如果您更喜欢在应用程序中使用字符串插值,您仍然可以通过添加类似Loca.lize 项目的帮助方法来对其进行本地化。

源代码

源代码在这里。不幸的是,它使用了一些特定于Loyc.Essentials的类型(Symbol ThreadLocalVariable<T>SavedValue<T>ScratchBuffer<T>),所以如果你想要在不使用Loyc.Essentials NuGet包的情况下使用Localize,你必须花费一些时间来转换到plain-old “C#。

 

原文地址:https://www.codeproject.com/Articles/1165045/Prepare-all-your-apps-for-localization

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/87736723
今日推荐