本地化Localization

下载示例 下载示例Download Sample Download the sample

可以使用 .NET 资源文件本地化 Xamarin.Forms 应用 。Xamarin.Forms apps can be localized using .NET resources files.

概述Overview

内置的 .NET 应用程序本地化机制使用 RESX 文件以及 System.ResourcesSystem.Globalization 命名空间中的类。The built-in mechanism for localizing .NET applications uses RESX files and the classes in the System.Resources and System.Globalization namespaces. 包含已翻译字符串的 RESX 文件嵌入在 Xamarin.Forms 程序集中,该程序集中还包括编译器生成的类(可提供对翻译的强类型访问)。The RESX files containing translated strings are embedded in the Xamarin.Forms assembly, along with a compiler-generated class that provides strongly-typed access to the translations. 然后可在代码中检索翻译文本。The translated text can then be retrieved in code.

代码示例Sample Code

有两个示例与此文档关联:There are two samples associated with this document:

  • UsingResxLocalization 是关于所述概念的非常简单的演示。UsingResxLocalization is a very simple demonstration of the concepts explained. 如下所示的代码片段均来自此示例。The code snippets shown below are all from this sample.
  • TodoLocalized 是可使用这些本地化技的基本工作应用。TodoLocalized is a basic working app that uses these localization techniques.

TodoLocalized 示例包括共享项目演示,但是,由于生成系统的限制,资源文件不会收到生成的 .designer.cs 文件,这将导致无法访问代码中强类型的已翻译字符串 。The TodoLocalized sample includes a Shared Project demo however due to limitations of the build system the resource files do not get a .designer.cs file generated which breaks the ability to access translated strings strongly-typed in code.

此文档的其余部分与使用 Xamarin.Forms.NET Standard 库模板的项目相关。The remainder of this document relates to projects using the Xamarin.Forms .NET Standard library template.

全球化 Xamarin.Forms 代码Globalizing Xamarin.Forms Code

全球化应用程序是使其“世界通用”的过程 。Globalizing an application is the process of making it "world ready." 这意味着编写能够显示不同语言的代码。This means writing code that is capable of displaying different languages.

全球化应用程序的关键部分之一是构建用户界面,以便没有硬编码文本 。One of the key parts of globalizing an application is building the user-interface so that there is no hard-coded text. 相反,向用户显示的任何内容都应从一组已翻译为所选语言的字符串中进行检索。Instead, anything displayed to the user should be retrieved from a set of strings that have been translated into their chosen language.

本文档将介绍如何使用 RESX 文件来存储这些字符串并对其进行检索,然后根据用户首选项进行显示。In this document we'll examine how to use RESX files to store those strings and retrieve them for display depending on the user's preference.

这些示例提供英语、法语、西班牙语、德语、中文、日语、俄语和葡萄牙语(巴西)等语言版本。The samples target English, French, Spanish, German, Chinese, Japanese, Russian, and Brazilian Portuguese languages. 根据需要,应用程序可翻译为更少或更多种语言。Applications can be translated into as few or as many languages as required.

备注

在通用 Windows 平台上,应将 RESW 文件(而不是 RESX 文件)用于推送通知本地化。On the Universal Windows Platform, RESW files should be used for push notification localization, rather than RESX files. 有关详细信息,请参阅 UWP 本地化For more information, see UWP Localization.

添加资源Adding Resources

全球化 Xamarin.Forms.NET Standard 库应用程序的第一步是添加 RESX 资源文件,用于存储应用中所用的所有文本。The first step in globalizing a Xamarin.Forms .NET Standard library application is adding the RESX resource files that will be used to store all the text used in the app. 我们需要添加包含默认文本的 RESX 文件,然后添加其他适用于我们想要支持的每种语言的 RESX 文件。We need to add a RESX file that contains the default text, and then add additional RESX files for each language we wish to support.

基础语言资源Base Language Resource

基础资源 (RESX) 文件将包含默认语言字符串(这些示例假定默认语言是英语)。The base resources (RESX) file will contain the default language strings (the samples assume English is the default language). 右键单击项目并选择“添加”>“新建文件...”,将文件添加到 Xamarin.Forms 通用代码项目中 。Add the file to the Xamarin.Forms common code project by right-clicking on the project and choosing Add > New File....

选择有意义的名称(如 AppResources)并按“确定” 。Choose a meaningful name such as AppResources and press OK.

添加资源文件Add Resource File

两个文件即会添加到项目中:Two files will be added to the project:

  • AppResources.resx 文件,其中以 XML 格式存储已翻译的字符串 。AppResources.resx file where translatable strings are stored in an XML format.
  • AppResources.designer.cs 文件,声明分部类,以包含对 RESX XML 文件中创建的所有元素的引用 。AppResources.designer.cs file that declares a partial class to contain references to all the elements created in the RESX XML file.

解决方案树将显示相关的文件。The solution tree will show the files as related. 应编辑 RESX 文件,以添加新的已翻译字符串;不应编辑 .designer.cs 文件 。The RESX file should be edited to add new translatable strings; the .designer.cs file should not be edited.

字符串可见性String Visibility

默认情况下,如果生成对字符串的强类型引用,则它们对程序集将为 internalBy default when strongly-typed references to strings are generated, they will be internal to the assembly. 这是因为 RESX 文件的默认生成工具将生成具有 internal 属性的 .designer.cs 文件 。This is because the default build tool for RESX files generates the .designer.cs file with internal properties.

选择 AppResources.resx 文件,并显示 Properties Pad,以查看配置此生成工具的位置 。Select the AppResources.resx file and show the Properties pad to see where this build tool is configure. 下面的截图显示“自定义工具: ResXFileCodeGenerator”。The screenshot below shows the Custom Tool: ResXFileCodeGenerator.

若要使强类型字符串属性为 public,必须手动将配置更改为“自定义工具: PublicResXFileCodeGenerator”,如以下屏幕截图中所示:To make the strongly-typed string properties public, you must manually change the configuration to Custom Tool: PublicResXFileCodeGenerator, as shown in the screenshot below:

此更改是可选的,并且仅在要在不同程序集中引用本地化字符串时必需(例如,如果要将不同程序集中的 RESX 文件放入你的代码)。This change is optional, and is only required if you wish to reference localized strings across different assemblies (for example, if you put the RESX files in a different assembly to your code). 此主题的示例使字符串保持 internal,因为它们在使用它们的相同 Xamarin.Forms.NET Standard 中定义。The sample for this topic leaves the strings internal because they are defined in the same Xamarin.Forms .NET Standard library assembly where they are used.

如上所示,只需在基础 RESX 文件上设置自定义工具即可;无需在以下各节中所述的特定语言 RESX 文件上设置任何生成工具 。You only need to set the custom tool on the base RESX file as shown above; you do not need to set any build tool on the language-specific RESX files discussed in the following sections.

编辑 RESX 文件Editing the RESX file

遗憾的是,Visual Studio for Mac 中没有内置 RESX 编辑器。Unfortunately there is no built-in RESX editor in Visual Studio for Mac. 添加新的可翻译字符串需要为每个字符串添加新的 XML data 元素。Adding new translatable strings requires the addition of a new XML data element for each string. 每个 data 元素均可包含以下内容:Each data element can contain the following:

  • name 属性(必需)是此可翻译字符串的关键值。name attribute (required) is the key for this translatable string. 它必须是有效的 C# 属性名称 - 因此不允许使用任何空格或特殊字符。It must be a valid C# property name - so no spaces or special characters are allowed.
  • value 元素(必需)是应用程序中显示的实际字符串。value element (required), which is the actual string that is displayed in the application.
  • comment 元素(可选)可以包含介绍如何使用此字符串的翻译工具的说明。comment element (optional) can contain instructions for the translator that explains how this string is used.
  • xml:space 属性(可选)用于控制如何保留字符串中的间距。xml:space attribute (optional) to control how spacing in the string is preserved.

部分示例 data 元素如下所示:Some example data elements are shown here:

<data name="NotesLabel" xml:space="preserve">
    <value>Notes:</value>
    <comment>label for input field</comment>
</data>
<data name="NotesPlaceholder" xml:space="preserve">
    <value>eg. buy milk</value>
    <comment>example input for notes field</comment>
</data>
<data name="AddButton" xml:space="preserve">
    <value>Add new item</value>
</data>

编写应用程序时,向用户显示的每一段文本都应添加到新 data 元素中的基础 RESX 资源文件。As the application is written, every piece of text displayed to the user should be added to the base RESX resources file in a new data element. 建议添加尽可能多的 comment,以确保高质量的翻译。It is recommended that you include comments as much as possible to ensure a high-quality translation.

备注

Visual Studio(包括免费的社区版本)包含基本的 RESX 编辑器。Visual Studio (including the free Community edition) contains a basic RESX editor. 如果你有权访问 Windows 计算机,则可以方便地在 RESX 文件中添加和编辑字符串。If you have access to a Windows computer, that can be a convenient way to add and edit strings in RESX files.

特定语言资源Language-Specific Resources

通常情况下,在编写了大型应用程序块之前,不会进行默认文本字符串的实际翻译(在这种情况下,默认 RESX 文件将包含大量字符串)。Typically, the actual translation of the default text strings won't happen until large chunks of the application have been written (in which case the default RESX file will contain lots of strings). 在开发周期早期添加特定语言资源仍然是个好办法,可以选择使用计算机翻译的文本来帮助测试本地化代码。It is still a good idea to add the language-specific resources early in the development cycle, optionally populating with machine-translated text to help test the localization code.

将为我们要支持的每种语言添加一个额外的 RESX 文件。One additional RESX file is added for each language we wish to support. 特定语言资源文件必须遵循特定的命名约定:使用相同的文件名作为基础资源文件(例如,Language-specific resource files must follow a specific naming convention: use the same filename as the base resources file (eg. AppResources),后面跟一个句点 (.) 和语言代码 。AppResources) followed by a period (.) and then the language code. 简单的示例包括:Simple examples include:

  • AppResources.fr.resx - 法语翻译。AppResources.fr.resx - French language translations.
  • AppResources.es.resx - 西班牙语翻译。AppResources.es.resx - Spanish language translations.
  • AppResources.de.resx - 德语翻译。AppResources.de.resx - German language translations.
  • AppResources.ja.resx - 日语翻译。AppResources.ja.resx - Japanese language translations.
  • AppResources.zh-Hans.resx - 中文(简体)翻译。AppResources.zh-Hans.resx - Chinese (Simplified) language translations.
  • AppResources.zh-Hant.resx - 中文(繁体)翻译。AppResources.zh-Hant.resx - Chinese (Traditional) language translations.
  • AppResources.pt.resx - 葡萄牙语翻译。AppResources.pt.resx - Portuguese language translations.
  • AppResources.pt-BR.resx - 葡萄牙语(巴西)翻译。AppResources.pt-BR.resx - Brazilian Portuguese language translations.

常规模式是使用两个字母的语言代码,但也有使用不同格式的示例(例如中文)以及需要 4 字符区域设置标识符的其他示例(例如葡萄牙语(巴西))。The general pattern is to use two-letter language codes, but there are some examples (such as Chinese) where a different format is used, and other examples (such as Brazilian Portuguese) where a four character locale identifier is required.

这些特定语言资源文件不 需要 .designer.cs 分部类,因此如果已设置“生成操作:EmbeddedResource”,则可添加为常规的 XML 文件 。These language-specific resources files do not require a .designer.cs partial class so they can be added as regular XML files, with the Build Action: EmbeddedResource set. 此屏幕截图显示了一个包含特定语言资源文件的解决方案:This screenshot shows a solution containing language-specific resource files:

开发应用程序且基础 RESX 文件已添加文本后,应将其发送给可翻译每个 data 元素的翻译器,并返回特定语言资源文件(使用如下所示命名约定),以包括在应用中。As an application is developed and the base RESX file has text added, you should send it out to translators who will translate each data element and return a language-specific resource file (using the naming convention shown) to be included in the app. 部分“计算机翻译”示例如下所示:Some 'machine translated' examples are shown below:

AppResources.es.resx(西班牙语)AppResources.es.resx (Spanish)

<data name="AddButton" xml:space="preserve">
    <value>Escribir un artículo</value>
    <comment>this string appears on a button to add a new item to the list</comment>
</data>

AppResources.ja.resx(日语)AppResources.ja.resx (Japanese)

<data name="AddButton" xml:space="preserve">
    <value>新しい項目を追加</value>
    <comment>this string appears on a button to add a new item to the list</comment>
</data>

AppResources.pt-BR.resx(巴西葡萄牙语)AppResources.pt-BR.resx (Brazilian Portuguese)

<data name="AddButton" xml:space="preserve">
    <value>adicionar novo item</value>
    <comment>this string appears on a button to add a new item to the list</comment>
</data>

翻译工具只需更新 value 元素 - 不打算翻译 commentOnly the value element needs to be updated by the translator - the comment is not intended to be translated. 请记住:编辑 XML 文件以转义保留字符(如 <>&)时,如果它们出现在 valuecomment 中,则使用 &lt;&gt;&amp;Remember: when editing XML files to escape reserved characters like <, >, & with &lt;, &gt;, and &amp; if they appear in the value or comment.

在 Code 中使用资源Using Resources in Code

RESX 资源文件中的字符串可用于使用 AppResources 类的用户界面代码。Strings in the RESX resource files will be available to use in your user interface code using the AppResources class. 分配给 RESX 文件中每个字符串的 name 将成为该类的属性,可在 Xamarin.Forms 代码中引用,如下所示:The name assigned to each string in the RESX file becomes a property on that class which can be referenced in Xamarin.Forms code as shown below:

var myLabel = new Label ();
var myEntry = new Entry ();
var myButton = new Button ();
// populate UI with translated text values from resources
myLabel.Text = AppResources.NotesLabel;
myEntry.Placeholder = AppResources.NotesPlaceholder;
myButton.Text = AppResources.AddButton;

iOS、Android 和通用 Windows 平台 (UWP) 版用户界面将按预期呈现,只不过现在可以将应用翻译为多种语言,因为要从资源中加载本部,而不是对其进行硬编码。The user interface on iOS, Android, and the Universal Windows Platform (UWP) renders as you'd expect, except now it's possible to translate the app into multiple languages because the text is being loaded from a resource rather than being hard-coded. 下面是翻译前,每个平台上的 UI 的屏幕截图:Here is a screenshot showing the UI on each platform prior to translation:

疑难解答Troubleshooting

测试特定语言Testing a Specific Language

将模拟器或设备切换为不同的语言可能很棘手,特别是开发过程中,你希望快速测试不同的区域性。It can be tricky to switch the simulator or a device to different languages, particularly during development when you want to test different cultures quickly.

可以通过设置 Culture,强制加载特定语言,如此代码片段中所示:You can force a specific language to be loaded by setting the Culture as shown in this code snippet:

// force a specific culture, useful for quick testing
AppResources.Culture =  new CultureInfo("fr-FR");

这种直接对 AppResources 类设置区域性的方法,也可用于实现在应用内实现语言选择器(而不是使用设备的区域设置)。This approach – setting the culture directly on the AppResources class – can also be used to implement a language-selector inside your app (rather than using the device's locale).

加载嵌入的资源Loading Embedded Resources

以下代码片段可用于尝试调试嵌入资源的问题(例如 RESX 文件)。The following code snippet is useful when trying to debug issues with embedded resources (such as RESX files). 将此代码添加到你的应用(应用程序生命周期早期),将会列出程序集中嵌入的所有资源,并显示完整的资源标识符:Add this code to your app (early in the app lifecycle) and it will list all the resources embedded in the assembly, showing the full resource identifier:

using System.Reflection;
// ...
// NOTE: use for debugging, not in released app code!
var assembly = typeof(EmbeddedImages).GetTypeInfo().Assembly; // "EmbeddedImages" should be a class in your app
foreach (var res in assembly.GetManifestResourceNames())
{
    System.Diagnostics.Debug.WriteLine("found resource: " + res);
}

在 AppResources.Designer.cs 文件中(约第 33 行),指定完整的资源管理器名称(例如, In the AppResources.Designer.cs file (around line 33), the full resource manager name is specified (eg. "UsingResxLocalization.Resx.AppResources"),与下面的代码类似:"UsingResxLocalization.Resx.AppResources") similar to the code below:

System.Resources.ResourceManager temp =
        new System.Resources.ResourceManager(
                "UsingResxLocalization.Resx.AppResources",
                typeof(AppResources).GetTypeInfo().Assembly);

检查“应用程序输出”,获取上述调试的结果,以确认已列出正确的资源(例如 "UsingResxLocalization.Resx.AppResources") 。Check the Application Output for the results of the debug code shown above, to confirm the correct resources are listed (ie. "UsingResxLocalization.Resx.AppResources").

如果没有,则 AppResources 类将无法加载其资源。If not, the AppResources class will be unable to load its resources. 检查以下项目,解决找不到资源的问题:Check the following to resolve issues where the resources cannot be found:

  • 项目的默认命名空间与 AppResources.Designer.cs 文件中的根命名空间匹配 。The default namespace for the project matches the root namespace in the AppResources.Designer.cs file.
  • 如果 AppResources.resx 文件位于子目录中,则子目录名称应为命名空间和资源标识符的一部分 。If the AppResources.resx file is located in a subdirectory, the subdirectory name should be part of the namespace and part of the resource identifier.
  • AppResources.resx 文件包含“生成操作: EmbeddedResource”。The AppResources.resx file has Build Action: EmbeddedResource.
  • 勾选了“项目选项”>“源代码”>“.NET 命名策略”>“使用 Visual Studio 样式的资源名称” 。The Project Options > Source Code > .NET Naming Policies > Use Visual Studio-style resources names is ticked. 如果愿意可以取消勾选,但是引用 RESX 资源时将使用命名空间,以更新整个应用。You can untick this if you prefer, however the namespaces used when referencing your RESX resources will need to updated throughout the app.

不能在调试模式下操作(仅限 Android)Doesn't work in DEBUG mode (Android only)

如果已翻译的字符串在发行 Android 内部版本中正常,但调试时不正常,请右键单击“Android 项目”并选择“选项”>“生成”>“Android 内部版本”,然后确保未勾选“快速程序集部署” 。If the translated strings are working in your RELEASE Android builds but not while debugging, right-click on the Android Project and select Options > Build > Android Build and ensure that the Fast assembly deployment is NOT ticked. 此选项会导致加载资源的问题,并且在测试本地化应用时序不应使用。This option causes problems with loading resources and should not be used if you are testing localized apps.

显示正确的语言Displaying the Correct Language

到目前为止,我们探讨了如何编写代码,以便可以提供翻译,而不是如何实际显示它们。So far we've examined how to write code so that translations can be provided, but not how to actually make them appear. Xamarin.Forms 代码可以利用 .NET 的资源加载正确的语言翻译,但我们需要查询每个平台上的操作系统,以确定用户所选的语言。Xamarin.Forms code can take advantage of .NET's resources to load the correct language translations, but we need to query the operating system on each platform to determine which language the user has selected.

由于获取用户的语言首选项需要某些平台特定代码,请使用依赖项服务来公开 Xamarin.Forms 应用中的信息,并针对每个平台实现它。Because some platform-specific code is required to obtain the user's language preference, use a dependency service to expose that information in the Xamarin.Forms app and implement it for each platform.

首先,定义一个界面来公开用户首选的区域性,类似于如下代码:First, define an interface to expose the user's preferred culture, similar to the code below:

public interface ILocalize
{
    CultureInfo GetCurrentCultureInfo ();
    void SetLocale (CultureInfo ci);
}

第二,使用 Xamarin.Forms 类中的 DependencyServiceApp,以调用界面并将 RESX 资源区域性设置为正确值。Second, use the DependencyService in the Xamarin.Forms App class to call the interface and set our RESX resources culture to the correct value. 请注意,我们无需为通用 Windows 平台设置此值,因为资源框架会自动识别这些平台上所选的语言。Notice that we don't need to manually set this value for the Universal Windows Platform, since the resources framework automatically recognizes the selected language on those platforms.

if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Android)
{
    var ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
    Resx.AppResources.Culture = ci; // set the RESX for resource localization
    DependencyService.Get<ILocalize>().SetLocale(ci); // set the Thread for locale-aware methods
}

首次加载应用程序时,需要设置资源 Culture,以便使用正确的语言字符串。The resource Culture needs to be set when the application first loads so that the correct language strings are used. 可选择根据特定平台事件更新此值,如果用户在应用运行时更新其语言首选项,则可能在 iOS 或 Android 上引发上述事件。You may optionally update this value according to platform-specific events that may be raised on iOS or Android if the user updates their language preferences while the app is running.

ILocalize 界面的实现在下面的特定平台代码部分中显示。The implementations for the ILocalize interface are shown in the Platform-specific Code section below. 这些实现充分利用此 PlatformCulture 帮助程序类:These implementations take advantage of this PlatformCulture helper class:

public class PlatformCulture
{
    public PlatformCulture (string platformCultureString)
    {
        if (String.IsNullOrEmpty(platformCultureString))
        {
            throw new ArgumentException("Expected culture identifier", "platformCultureString"); // in C# 6 use nameof(platformCultureString)
        }
        PlatformString = platformCultureString.Replace("_", "-"); // .NET expects dash, not underscore
        var dashIndex = PlatformString.IndexOf("-", StringComparison.Ordinal);
        if (dashIndex > 0)
        {
            var parts = PlatformString.Split('-');
            LanguageCode = parts[0];
            LocaleCode = parts[1];
        }
        else
        {
            LanguageCode = PlatformString;
            LocaleCode = "";
        }
    }
    public string PlatformString { get; private set; }
    public string LanguageCode { get; private set; }
    public string LocaleCode { get; private set; }
    public override string ToString()
    {
        return PlatformString;
    }
}

特定于平台的代码Platform-Specific Code

用于检测要显示语言的代码必须特定于平台,因为 iOS、Android 和 UWP 公开该信息的方式都有所不同。The code to detect which language to display must be platform-specific because iOS, Android, and UWP all expose this information in slightly different ways. 下面提供适用于每个平台的 ILocalize 依赖项服务的代码,以及其他平台特定要求,以确保本地化文本呈现正确。The code for the ILocalize dependency service is provided below for each platform, along with additional platform-specific requirements to ensure localized text is rendered correctly.

特定于平台的代码还必须处理操作系统允许用户配置 .NET 的 CultureInfo 类不支持的区域设置标识符的情况。The platform-specific code must also handle cases where the operating system allows the user to configure a locale identifier that is not supported by .NET's CultureInfo class. 在这些情况下必须编写自定义代码,以检测不受支持的区域设置并替换最佳的 .NET 兼容区域设置。In these cases custom code must be written to detect unsupported locales and substitute the best .NET-compatible locale.

iOS 应用程序项目iOS Application Project

iOS 用户将独立于日期和时间格式区域性选择首选语言。iOS users select their preferred language separately from date and time formatting culture. 要加载正确的资源本地化 Xamarin.Forms 应用,只需对第一个元素查询 NSLocale.PreferredLanguages 数组。To load the correct resources to localize a Xamarin.Forms app we just need to query the NSLocale.PreferredLanguages array for the first element.

以下 ILocalize 依赖项服务的实现应置于 iOS 应用程序项目中。The following implementation of the ILocalize dependency service should be placed in the iOS application project. 由于 iOS 使用下划线而不是短划线(这是 .NET 的标准表示方法),因此代码在实例化 CultureInfo 类之前将替换下划线:Because iOS uses underscores instead of dashes (which is the .NET standard representation) the code replaces the underscore before instantiating the CultureInfo class:

[assembly:Dependency(typeof(UsingResxLocalization.iOS.Localize))]

namespace UsingResxLocalization.iOS
{
    public class Localize : UsingResxLocalization.ILocalize
    {
        public void SetLocale (CultureInfo ci)
        {
            Thread.CurrentThread.CurrentCulture = ci;
            Thread.CurrentThread.CurrentUICulture = ci;
        }

        public CultureInfo GetCurrentCultureInfo ()
        {
            var netLanguage = "en";
            if (NSLocale.PreferredLanguages.Length > 0)
            {
                var pref = NSLocale.PreferredLanguages [0];
                netLanguage = iOSToDotnetLanguage(pref);
            }
            // this gets called a lot - try/catch can be expensive so consider caching or something
            System.Globalization.CultureInfo ci = null;
            try
            {
                ci = new System.Globalization.CultureInfo(netLanguage);
            }
            catch (CultureNotFoundException e1)
            {
                // iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
                // fallback to first characters, in this case "en"
                try
                {
                    var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
                    ci = new System.Globalization.CultureInfo(fallback);
                }
                catch (CultureNotFoundException e2)
                {
                    // iOS language not valid .NET culture, falling back to English
                    ci = new System.Globalization.CultureInfo("en");
                }
            }
            return ci;
        }

        string iOSToDotnetLanguage(string iOSLanguage)
        {
            // .NET cultures don't support underscores
            string netLanguage = iOSLanguage.Replace("_", "-");

            //certain languages need to be converted to CultureInfo equivalent
            switch (iOSLanguage)
            {
                case "ms-MY":   // "Malaysian (Malaysia)" not supported .NET culture
                case "ms-SG":    // "Malaysian (Singapore)" not supported .NET culture
                    netLanguage = "ms"; // closest supported
                    break;
                case "gsw-CH":  // "Schwiizerdüütsch (Swiss German)" not supported .NET culture
                    netLanguage = "de-CH"; // closest supported
                    break;
                // add more application-specific cases here (if required)
                // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }

        string ToDotnetFallbackLanguage (PlatformCulture platCulture)
        {
            var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
            switch (platCulture.LanguageCode)
            {
                case "pt":
                    netLanguage = "pt-PT"; // fallback to Portuguese (Portugal)
                    break;
                case "gsw":
                    netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
                    break;
                // add more application-specific cases here (if required)
                // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
    }
}

备注

GetCurrentCultureInfo 方法中的 try/catch 块可模拟经常与区域设置说明符一起使用的回退行为 - 如果找不到确切的匹配,则只需根据该语言寻找接近的匹配即可(区域设置中的第一个字符块)。The try/catch blocks in the GetCurrentCultureInfo method mimic the fallback behavior typically used with locale specifiers – if the exact match is not found, look for a close match based just on the language (first block of characters in the locale).

在 Xamarin.Forms 中,部分区域设置在 iOS 中有效,但不对应 .NET 中的有效 CultureInfo,且上述代码会尝试处理此情况。In the case of Xamarin.Forms, some locales are valid in iOS but do not correspond to a valid CultureInfo in .NET, and the code above attempts to handle this.

例如,iOS 的“设置”>“通用语言”&“区域”屏幕允许你将手机的“语言”设置为“英语”,但“地区”设置为“西班牙”,这将导致 "en-ES" 的区域设置字符串 。For example, the iOS Settings > General Language & Region screen allows you to set your phone Language to English but the Region to Spain, which results in a locale string of "en-ES". 如果 CultureInfo 创建失败,代码将回退到仅使用前两个字母,以选择显示语言。When the CultureInfo creation fails, the code falls back to using just the first two letters to select the display language.

开发人员应修改 iOSToDotnetLanguageToDotnetFallbackLanguage 方法,以处理其所支持语言需要的特定用例。Developers should modify the iOSToDotnetLanguage and ToDotnetFallbackLanguage methods to handle specific cases required for their supported languages.

存在 iOS 自动翻译的系统定义用户界面元素,例如 Picker 控件上的“完成”按钮 。There are some system-defined user-interface elements that are automatically translated by iOS, such as the Done button on the Picker control. 若要强制 iOS 翻译这些元素,需要在 Info.plist 文件中指示支持的语言 。To force iOS to translate these elements we need to indicate which languages we support in the Info.plist file. 可以通过 Info.plist >“源”添加这些值,如下所示 :You can add these values via Info.plist > Source as shown here:

Info.plist 中的本地化键Localization keys in Info.plist

或者,在 XML 编辑器中打开 Info.plist 文件并直接编辑值 :Alternatively, open the Info.plist file in an XML editor and edit the values directly:

<key>CFBundleLocalizations</key>
<array>
    <string>de</string>
    <string>es</string>
    <string>fr</string>
    <string>ja</string>
    <string>pt</string> <!-- Brazil -->
    <string>pt-PT</string> <!-- Portugal -->
    <string>ru</string>
    <string>zh-Hans</string>
    <string>zh-Hant</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>

实现依赖项服务并更新 Info.plist 后,iOS 应用将能够显示本地化文本 。Once you've implemented the dependency service and updated Info.plist, the iOS app will be able to display localized text.

备注

请注意,Apple 处理葡萄牙语的方式略有不同。Note that Apple treats Portuguese slightly differently than you'd expect. 来源于其文档:“使用 pt 作为葡萄牙语的语言 ID(因为在巴西使用),pt-PT 用作葡萄牙语的语言 ID(因为在葡萄牙使用)” 。From their docs: "use pt as the language ID for Portuguese as it is used in Brazil and pt-PT as the language ID for Portuguese as it is used in Portugal". 这意味着,如果在非标准区域设置中选择葡萄牙语,除非编写代码(如上述 ToDotnetFallbackLanguage)更改此行为,否则 iOS 上的回退语言为巴西葡萄牙语。This means when Portuguese language is chosen in a non-standard locale, the fallback language will be Brazilian Portuguese on iOS, unless code is written to change this behavior (such as the ToDotnetFallbackLanguage above).

有关 iOS 本地化的详细信息,请参阅 iOS 本地化For more information about iOS Localization, see iOS Localization.

Android 应用程序项目Android Application Project

Android 通过 Java.Util.Locale.Default 公开当前选定的区域设置,并且还使用下划分隔符而不是短横线(由以下代码替换)。Android exposes the currently selected locale via Java.Util.Locale.Default, and also uses an underscore separator instead of a dash (which is replaced by the following code). 将此依赖项服务实现添加到 Android 应用程序项目:Add this dependency service implementation to the Android application project:

[assembly:Dependency(typeof(UsingResxLocalization.Android.Localize))]

namespace UsingResxLocalization.Android
{
    public class Localize : UsingResxLocalization.ILocalize
    {
        public void SetLocale(CultureInfo ci)
        {
            Thread.CurrentThread.CurrentCulture = ci;
            Thread.CurrentThread.CurrentUICulture = ci;
        }
        public CultureInfo GetCurrentCultureInfo()
        {
            var netLanguage = "en";
            var androidLocale = Java.Util.Locale.Default;
            netLanguage = AndroidToDotnetLanguage(androidLocale.ToString().Replace("_", "-"));
            // this gets called a lot - try/catch can be expensive so consider caching or something
            System.Globalization.CultureInfo ci = null;
            try
            {
                ci = new System.Globalization.CultureInfo(netLanguage);
            }
            catch (CultureNotFoundException e1)
            {
                // iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
                // fallback to first characters, in this case "en"
                try
                {
                    var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
                    ci = new System.Globalization.CultureInfo(fallback);
                }
                catch (CultureNotFoundException e2)
                {
                    // Android language not valid .NET culture, falling back to English
                    ci = new System.Globalization.CultureInfo("en");
                }
            }
            return ci;
        }
        string AndroidToDotnetLanguage(string androidLanguage)
        {
            var netLanguage = androidLanguage;
            //certain languages need to be converted to CultureInfo equivalent
            switch (androidLanguage)
            {
                case "ms-BN":   // "Malaysian (Brunei)" not supported .NET culture
                case "ms-MY":   // "Malaysian (Malaysia)" not supported .NET culture
                case "ms-SG":   // "Malaysian (Singapore)" not supported .NET culture
                    netLanguage = "ms"; // closest supported
                    break;
                case "in-ID":  // "Indonesian (Indonesia)" has different code in  .NET
                    netLanguage = "id-ID"; // correct code for .NET
                    break;
                case "gsw-CH":  // "Schwiizerdüütsch (Swiss German)" not supported .NET culture
                    netLanguage = "de-CH"; // closest supported
                    break;
                    // add more application-specific cases here (if required)
                    // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
        string ToDotnetFallbackLanguage(PlatformCulture platCulture)
        {
            var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
            switch (platCulture.LanguageCode)
            {
                case "gsw":
                    netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
                    break;
                    // add more application-specific cases here (if required)
                    // ONLY use cultures that have been tested and known to work
            }
            return netLanguage;
        }
    }
}

备注

GetCurrentCultureInfo 方法中的 try/catch 块可模拟经常与区域设置说明符一起使用的回退行为 - 如果找不到确切的匹配,则只需根据该语言寻找接近的匹配即可(区域设置中的第一个字符块)。The try/catch blocks in the GetCurrentCultureInfo method mimic the fallback behavior typically used with locale specifiers – if the exact match is not found, look for a close match based just on the language (first block of characters in the locale).

在 Xamarin.Forms 中,部分区域设置在 Android 中有效,但不对应 .NET 中的有效 CultureInfo,且上述代码会尝试处理此情况。In the case of Xamarin.Forms, some locales are valid in Android but do not correspond to a valid CultureInfo in .NET, and the code above attempts to handle this.

开发人员应修改 iOSToDotnetLanguageToDotnetFallbackLanguage 方法,以处理其所支持语言需要的特定用例。Developers should modify the iOSToDotnetLanguage and ToDotnetFallbackLanguage methods to handle specific cases required for their supported languages.

此代码添加到 Android 应用程序项目后,它将能够自动显示已翻译的字符串。Once this code has been added to the Android application project, it will be able to automatically display translated strings.

警告

如果已翻译的字符串在发行 Android 内部版本中正常,但调试时不正常,请右键单击“Android 项目”并选择“选项”>“生成”>“Android 内部版本”,然后确保未勾选“快速程序集部署” 。If the translated strings are working in your RELEASE Android builds but not while debugging, right-click on the Android Project and select Options > Build > Android Build and ensure that the Fast assembly deployment is NOT ticked. 此选项会导致加载资源的问题,并且在测试本地化应用时序不应使用。This option causes problems with loading resources and should not be used if you are testing localized apps.

有关 Android 本地化的详细信息,请参阅 Android 本地化For more information about Android localization, see Android Localization.

通用 Windows 平台Universal Windows Platform

通用 Windows 平台 (UWP) 项目不需要依赖项服务。Universal Windows Platform (UWP) projects do not require the dependency service. 相反,此平台可自动将资源的区域性设置正确。Instead, this platform automatically sets the resource's culture correctly.

AssemblyInfo.csAssemblyInfo.cs

在 .NET Standard 库项目中展开“属性”节点,并双击“AssemblyInfo.cs”文件 。Expand the Properties node in the .NET Standard library project and double-click on the AssemblyInfo.cs file. 将以下行添加到文件,将非特定资源程序集语言设为英语:Add the following line to the file to set the neutral resources assembly language to English:

[assembly: NeutralResourcesLanguage("en")]

这会通知资源管理器应用的默认区域性,因此,请确保语言非特定 RESX 文件中定义的字符串 (AppResources.resx) 将在应用在其中一个英语区域设置中运行时显示 。This informs the resource manager of the app's default culture, therefore ensuring that the strings defined in the language neutral RESX file (AppResources.resx) will be displayed when the app is running in one the English locales.

示例Example

如上所述,更新平台特定项目并使用已翻译的 RESX 文件重新编译应用后,将在每个应用中提供更新后的翻译。After updating the platform-specific projects as shown above and recompiling the app with translated RESX files, updated translations will be available in each app. 下面是翻译为简体中文的代码示例的屏幕截图:Here is a screenshot from the sample code translated into Simplified Chinese:

有关 UWP 本地化的详细信息,请参阅 UWP 本地化For more information about UWP localization, see UWP Localization.

本地化 XAMLLocalizing XAML

采用 XAML 生成 Xamarin.Forms 用户界面时,标记将与此类似,并且字符串直接嵌入 XML:When building a Xamarin.Forms user interface in XAML the markup would look similar to this, with strings embedded directly in the XML:

<Label Text="Notes:" />
<Entry Placeholder="eg. buy milk" />
<Button Text="Add to list" />

理想情况下,我们可以直接在 XAML 中翻译用户界面控件,可通过创建标记扩展实现 。Ideally, we could translate user interface controls directly in the XAML, which we can do by creating a markup extension. 向 XAML 公开 RESX 资源的标记扩展代码如下所示。The code for a markup extension that exposes the RESX resources to XAML is shown below. 此类应添加到 Xamarin.Forms 通用代码(以及 XAML 页):This class should be added to the Xamarin.Forms common code (along with the XAML pages):

using System;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace UsingResxLocalization
{
    // You exclude the 'Extension' suffix when using in XAML
    [ContentProperty("Text")]
    public class TranslateExtension : IMarkupExtension
    {
        readonly CultureInfo ci = null;
        const string ResourceId = "UsingResxLocalization.Resx.AppResources";

        static readonly Lazy<ResourceManager> ResMgr = new Lazy<ResourceManager>(
            () => new ResourceManager(ResourceId, IntrospectionExtensions.GetTypeInfo(typeof(TranslateExtension)).Assembly));

        public string Text { get; set; }

        public TranslateExtension()
        {
            if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Android)
            {
                ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
            }
        }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Text == null)
                return string.Empty;

            var translation = ResMgr.Value.GetString(Text, ci);
            if (translation == null)
            {
#if DEBUG
                throw new ArgumentException(
                    string.Format("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, ci.Name),
                    "Text");
#else
                translation = Text; // HACK: returns the key, which GETS DISPLAYED TO THE USER
#endif
            }
            return translation;
        }
    }
}

以下项目符号说明了上述代码中的重要元素:The following bullets explain the important elements in the code above:

  • 该类名为 TranslateExtension,但根据约定,我们可在标记中翻译时引用 。The class is named TranslateExtension, but by convention we can refer to is as Translate in our markup.
  • 该类可实现 IMarkupExtension,这是 Xamarin.Forms 正常工作所必需。The class implements IMarkupExtension, which is required by Xamarin.Forms for it to work.
  • "UsingResxLocalization.Resx.AppResources" 是 RESX 资源的资源标识符。"UsingResxLocalization.Resx.AppResources" is the resource identifier for our RESX resources. 它由默认命名空间、资源文件所处文件夹和默认 RESX 文件名组成。It is comprised of our default namespace, the folder where the resource files are located and the default RESX filename.
  • 使用 IntrospectionExtensions.GetTypeInfo(typeof(TranslateExtension)).Assembly) 创建 ResourceManager 类,以确定要从其加载资源的当前程序集,并缓存在静态 ResMgr 字段中。The ResourceManager class is created using IntrospectionExtensions.GetTypeInfo(typeof(TranslateExtension)).Assembly) to determine the current assembly to load resources from, and cached in the static ResMgr field. 它将作为 Lazy 类型创建,因此创建操作将推迟到在 ProvideValue 方法中第一次使用它时发生。It's created as a Lazy type so that its creation is deferred until it's first used in the ProvideValue method.
  • ci 使用依赖项服务获取本机操作系统中的用户所选语言。ci uses the dependency service to get the user's chosen language from the native operating system.
  • GetString 是从资源文件中检索实际的已翻译字符串的方法。GetString is the method that retrieves the actual translated string from the resources files. 在通用 Windows 平台上,ci 将为 null,因为在这些平台上无法实现 ILocalize 界面。On the Universal Windows Platform, ci will be null because the ILocalize interface isn't implemented on those platforms. 这相当于仅使用第一个参数调用 GetString 方法。This is equivalent to calling the GetString method with only the first parameter. 相反,资源框架会自动识别区域设置,并从相应的 RESX 文件中检索已翻译的字符串。Instead, the resources framework will automatically recognize the locale and will retrieve the translated string from the appropriate RESX file.
  • 错误处理已包括在内,以通过引发异常帮助调试缺少的资源(仅限 DEBUG 模式下)。Error handling has been included to help debug missing resources by throwing an exception (in DEBUG mode only).

以下 XAML 代码片段演示如何使用标记扩展。The following XAML snippet shows how to use the markup extension. 可通过两个步骤实现:There are two steps to make it work:

  1. 在根节点中声明自定义 xmlns:i18n 命名空间。Declare the custom xmlns:i18n namespace in the root node. namespaceassembly 必须完全匹配项目设置 - 在此示例中,它们完全一样,但在你的项目中可能不同。The namespace and assembly must match the project settings exactly - in this example they are identical but could be different in your project.
  2. 对属性使用 {Binding} 通常包含调用 Translate 标记扩展的文本。Use {Binding} syntax on attributes that would normally contain text to call the Translate markup extension. 资源键是唯一的必需参数。The resource key is the only required parameter.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="UsingResxLocalization.FirstPageXaml"
        xmlns:i18n="clr-namespace:UsingResxLocalization;assembly=UsingResxLocalization">
    <StackLayout Padding="0, 20, 0, 0">
        <Label Text="{i18n:Translate NotesLabel}" />
        <Entry Placeholder="{i18n:Translate NotesPlaceholder}" />
        <Button Text="{i18n:Translate AddButton}" />
    </StackLayout>
</ContentPage>

以下更详细的语法也适用于标记扩展:The following more verbose syntax is also valid for the markup extension:

<Button Text="{i18n:TranslateExtension Text=AddButton}" />

本地化特定平台的元素Localizing Platform-Specific Elements

尽管我们可以在 Xamarin.Forms 代码中处理用户界面翻译,但在每个平台特定项目中,均存在必须完成的某些元素。Although we can handle the translation of the user interface in Xamarin.Forms code, there are some elements that must be done in each platform-specific project. 本部分将介绍如何本地化:This section will cover how to localize:

  • Application NameApplication Name
  • 映像Images

示例项目包括名为 flag.png 的示例项目,在 C# 中引用,如下所示 :The sample project includes a localized image called flag.png, which is referenced in C# as follows:

var flag = new Image();
switch (Device.RuntimePlatform)
{
    case Device.iOS:
    case Device.Android:
        flag.Source = ImageSource.FromFile("flag.png");
        break;
    case Device.UWP:
        flag.Source = ImageSource.FromFile("Assets/Images/flag.png");
        break;
}

标记图像也在 XAML 中引用,如下所示:The flag image is also referenced in the XAML like this:

<Image>
  <Image.Source>
    <OnPlatform x:TypeArguments="ImageSource">
        <On Platform="iOS, Android" Value="flag.png" />
        <On Platform="UWP" Value="Assets/Images/flag.png" />
    </OnPlatform>
  </Image.Source>
</Image>

只要实现如下所述的项目结构,所有平台就会自动将此类图像引用解析为图像的本地化版本。All platforms will automatically resolve image references like these to localized versions of the images, as long as the project structures explained below are implemented.

iOS 应用程序项目iOS Application Project

iOS 使用名为本地化项目或 .lproj 目录的命名标准,以包含图像和字符串资源 。iOS uses a naming standard called Localization Projects or .lproj directories to contain image and string resources. 这些目录可以包含应用中使用的图像的本地化版本以及可用于本地化应用名称的 InfoPlist.strings 文件 。These directories can contain localized versions of images used in the app, and also the InfoPlist.strings file that can be used to localize the app name. 有关 iOS 本地化的详细信息,请参阅 iOS 本地化For more information about iOS Localization, see iOS Localization.

映像Images

此屏幕截图显示了具有特定语言 .lproj 目录的 iOS 示例应用 。This screenshot shows the iOS sample app with language-specific .lproj directories. 称为 es.lproj 的西班牙语目录包含默认图像的本地化版本,以及 flag.png :The Spanish directory called es.lproj, contains localized versions of the default image, as well as flag.png:

每个语言目录都包含针对该语言本地化的 flag.png 副本 。Each language directory contains a copy of flag.png, localized for that language. 如果没有提供图像,则操作系统将默认使用默认语言目录中的图像。If no image is supplied, the operating system will default to the image in the default language directory. 有关完整的 Retina 支持,应提供每个图像的 @2x 和 @3x 副本 。For full Retina support, you should supply @2x and @3x copies of each image.

应用程序名称App Name

InfoPlist.strings 的内容只是一个键值,用于配置应用名称 :The contents of the InfoPlist.strings is just a single key-value to configure the app name:

"CFBundleDisplayName" = "ResxEspañol";

运行应用程序时,将同时本地化应用名称和图像:When the application is run, the app name and the image are both localized:

Android 应用程序项目Android Application Project

Android 遵循不同的方案用于存储本地化图像,使用具有语言代码后缀的可绘制资源和字符串目录 。Android follows a different scheme for storing localized images using different drawable and strings directories with a language code suffix. 如果需要四个字母的区域设置代码(例如,zh-TW 或 pt-BR),请注意,Android 在短划线/区域设置代码后需要一个额外的 r(例如, When a four-letter locale code is required (such as zh-TW or pt-BR), note that Android requires an additional r following the dash/preceding the locale code (eg. zh-rTW 或 pt-rBR)。zh-rTW or pt-rBR). 有关 Android 本地化的详细信息,请参阅 Android 本地化For more information about Android localization, see Android Localization.

映像Images

此屏幕截图显示具有部分本地化可绘制资源和字符串的 Android 示例:This screenshot shows the Android sample with a some localized drawables and strings:

请注意,Android 不使用 Zh-Hans 和 Zh-Hant 代码表示简体中文和繁体中文;相反,它仅支持特定于国家/地区的代码 zh-CN 和 zh-TW。Note that Android does not use zh-Hans and zh-Hant codes for Simplified and Traditional Chinese; instead, it only supports country-specific codes zh-CN and zh-TW.

若要支持适用于高密度屏幕的不同分辨率图像,请创建具有 -*dpi 后缀的其他语言文件夹,例如,drawables-es-mdpi、drawables-es-xdpi、drawables-es-xxdpi 等 。有关详细信息,请参阅提供替代的 Android 资源To support different resolution images for high-density screens, create additional language folders with -*dpi suffixes, such as drawables-es-mdpi, drawables-es-xdpi, drawables-es-xxdpi, etc. See Providing Alternative Android Resources for more information.

应用程序名称App Name

strings.xml 的内容只是一个键值,用于配置应用名称 :The contents of the strings.xml is just a single key-value to configure the app name:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ResxEspañol</string>
</resources>

更新 Android 应用中的 MainActivity.cs,使 Label 引用字符串 XML 。Update the MainActivity.cs in the Android app project so that the Label references the strings XML.

[Activity (Label = "@string/app_name", MainLauncher = true,
        ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]

现在,应用将本地化应用名称和图像。The app now localizes the app name and image. 下面是结果的屏幕截图(西班牙语):Here's a screenshot of the result (in Spanish):

通用 Windows 平台应用程序项目Universal Windows Platform Application Projects

通用 Windows 平台具有一个资源基础结构,可简化图像和应用程序名称的本地化。The Universal Windows Platform possesses a resource infrastructure that simplifies the localization of images and the app name. 有关 UWP 本地化的详细信息,请参阅 UWP 本地化For more information about UWP localization, see UWP Localization.

映像Images

可以通过将图像放在特定资源的文件夹中对其本地化,如以下屏幕截图中所示:Images can be localized by placing them in a resource-specific folder, as demonstrated in the following screenshot:

在运行时,Windows 资源基础结构将根据用户的区域设置选择相应的图像。At runtime the Windows resource infrastructure will select the appropriate image based on the user's locale.

总结Summary

可以使用 RESX 文件和 .NET 全球化类本地化 Xamarin.Forms 应用程序。Xamarin.Forms applications can be localized using RESX files and .NET globalization classes. 除少量平台特定代码用于检测用户首选语言外,大部分本地化工作均围绕通用代码进行。Apart from a small amount of platform-specific code to detect what language the user prefers, most of the localization effort is centralized in the common code.

通常以特定平台的方式处理图像,以充分利用 iOS 和 Android 中提供的多分辨率支持。Images are generally handled in a platform-specific way to take advantage of the multi-resolution support provided in both iOS and Android.