WPF 内联本地化 - 新方法






4.42/5 (6投票s)
使用内联文本而不是资源文件本地化 WPF 应用程序。
引言
本地化 WPF 应用程序可能非常具有挑战性,有时甚至令人沮丧。
有几种解决方案可以解决这个问题。大多数解决方案都侧重于使用资源文件或其他集中式存储来存放本地化文本。这在许多项目中都有效,但在某些情况下,其开销实在太大了。
在本文中,您将学习一种方法,该方法允许您将本地化文本直接放在需要它们的地方,即
- 在您的 XAML 中
- 在您的 C# 代码中
背景
多年来,我开发了许多 WPF 应用程序。几乎所有这些应用程序都需要某种本地化机制。通常我使用资源文件、XML 文件或其他类型的中央位置来存储翻译文本。有很多优秀的库可以做到这一点,例如 WPF Localization Extension(http://wpflocalizeextension.codeplex.com/)。
但是,使用集中的翻译位置也有其缺点。
特别是对于小型应用程序,这样做会产生过多的开销,从而显著减慢开发进程。因此,我一直在寻找另一种方法来解决这个问题。
一种解决方案是将本地化文本直接放在代码(C# 和 XAML)中,而不是将其存储在中央位置。这种技术就是本文所述的内容。
那么,让我们直接开始吧……
准备工作
要使用行内本地化,您需要定义两个类:一个用于 XAML 本地化的标记扩展,以及一个用于代码本地化的类。
标记扩展类
public class LocTextExtension : LocalizationMarkupExtensionBase
{
public String En { get; set; }
public String De { get; set; }
}
当您在 XAML 中指定本地化文本时,将使用标记扩展类。
正如您所见,定义了两个属性(En
和 De
)。
这些属性将存储英语和德语的本地化文本。您可以通过简单地添加其他属性来使用其他语言/文化。有关如何命名属性的详细信息,请参阅下文“翻译属性命名”部分。
代码翻译类
public class LocString : LocalizedStringBase
{
public String En { get; set; }
public String De { get; set; }
}
当您指定要在 C# 代码中使用的本地化文本时,将使用代码翻译类。
在这里,我们也有用于存储本地化文本的属性。
翻译属性命名
在标记扩展类(LocTextExtension
)和代码翻译类(LocString
)中定义的属性必须遵循一个约定,该约定由它们应包含翻译的文化名称指导。属性名称必须以文化名称(CultureInfo.Name
)命名,但要去除连字符,并且语言的首字母和区域代码的首字母必须大写。
以下是一些示例
属性名称 | 语言 | CultureInfo.Name |
En | 英语(中性) | en |
De | 德语(中性) | de |
EnUs | 英语(美国) | en-US |
FrCa | 法语(加拿大) | fr-CA |
Using the Code
定义了标记扩展类和代码翻译类后,您可以直接在 XAML 和代码中定义本地化文本,如下所示:
XAML 中的本地化
首先,在 XAML 文件顶部添加标记扩展的 XML 命名空间声明,如下所示:
<Window x:Class="WpfInlineLocalization.Example.TestWindow"
xmlns:loc="clr-namespace:WpfInlineLocalization.Example"
...
然后,您可以在 XAML 中的任何位置使用此标记扩展,如下所示:
<Button Content="{loc:LocText En=Open Entry, De=Eintrag öffnen}" />
代码中的本地化
要本地化在代码中使用的文本,只需创建一个 LocText
类的实例,并在通常使用 string
的地方使用它,如下所示:
MessageBox.Show(new LocString {En = "Do you really want to delete this item?",
De = "Wollen Sie den Eintrag wirklich löschen?"});
技巧
在 XAML 中使用多行文本
如果您想在 XAML 中使用多行文本,可以这样做:
<TextBlock>
<TextBlock.Text>
<loc:LocTextExtension xml:space="preserve">
<loc:LocTextExtension.En>Line 1
Line 2</loc:LocTextExtension.En>
<loc:LocTextExtension.De>Zeile 1
Zeile 2</loc:LocTextExtension.De>
</loc:LocTextExtension>
</TextBlock.Text>
</TextBlock>
在 XAML 中转义字符
在 XAML 中,您无法使用逗号(,)和单引号(')等某些字符。
要克服此限制,您有两个选择:转义字符或使用 XML 元素而不是属性。
要转义字符,只需在其前面加上反斜杠(\),如下所示:
<TextBlock Text="Hello\, how are you" />
这是一个使用 XML 元素而不是属性的示例:
<TextBlock> <TextBlock.Text> <loc:LocTextExtension xml:space="preserve"> <loc:LocTextExtension.En>Hello, how are you</loc:LocTextExtension.En> </loc:LocTextExtension> </TextBlock.Text> </TextBlock>
ReSharper Live Templates
在下载部分,您将找到 WpfInlineLocalizationTemplates.zip 文件,其中包含可以导入到 ReSharper 的 Live Templates。其中包含两个 Live Templates:一个用于在 C# 代码中插入 LocString
,另一个用于在 XAML 中插入 LocText
(标记扩展),使您能够更轻松地使用此方法。只需键入 locs
即可在代码中插入 LocString
,键入 loct
即可插入用于本地化文本的标记扩展。
关注点
设计时支持
标记扩展在设计模式下(例如,在 Expression Blend 或 Visual Studio WPF 编辑器中打开 XAML 文件时)也能正常工作。设计模式下使用的语言通过 LocalizationManager.DesignTimeCulture
属性指定。这样,您就可以在设计模式下预览本地化 UI。
标记扩展
标记扩展类根据当前文化(Thread.CurrentThread.CurrentUICulture
)从翻译属性中拾取翻译。它通过重写 MarkupExtension
类的 ProvideValue
方法来实现这一点。
public abstract class LocalizationMarkupExtensionBase : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this.GetLocalizedValue();
}
...
}
LocalizationMarkupExtensionBase.GetLocalizedValue
方法只是尝试查找与当前文化名称匹配的属性,并使用该属性的值作为翻译。它的工作原理如下:
- 尝试查找一个名称与当前文化匹配的属性。
- 如果找不到该属性,则尝试查找一个名称与当前文化的通用版本匹配的属性。
- 如果仍然找不到属性,则尝试查找名称为回退文化(
LocalizationManager.FallbackCulture
)的属性。 - 如果仍然找不到任何属性,或者属性返回
null
,则返回LocalizationManager.MissingLocalizationText
(以指示翻译缺失)。
代码本地化
对于代码中的本地化,使用了一个简单的技巧。LocalizedStringBase
类有一个隐式转换运算符(转换为 string
)的重载,这使得您可以在任何地方使用此类(及其子类)以及通常使用 string
的地方。
public abstract class LocalizedStringBase { public static implicit operator String(LocalizedStringBase localizedString) { return localizedString.GetLocalizedValue(); } ... }
这里使用了 LocalizedStringBase
类的 GetLocalizedValue
方法来获取翻译文本。它的工作原理与 LocalizationMarkupExtensionBase
类的 GetLocalizedValue
方法完全相同。
您也可以将代码本地化与 String.Format
结合使用,如下所示:
var articleId = 101; MessageBox.Show(String.Format(new LocString {En = "Do you really want to delete the article '{0}' ?", De = "Wollen Sie wirklich den Artikel '{0}' löschen?"}, articleId));
性能
代码使用反射来查找和检索本地化属性的值。
但是反射可能有点慢。因此,使用了 ReflectionHelper
类。每当调用 ReflectionHelper.GetPropertyValue
来检索属性值时,它都会创建一个委托,用于访问属性并缓存该委托。下次访问同一属性时,将使用缓存的委托。这加快了属性访问速度(根据我的测试,速度提高了约 30% 到 50%)。
使用此方法的优缺点
虽然这种方法可以为许多项目节省大量开发时间,但它并非对所有项目都完美无缺。
它有很多优点,但也有一些您必须考虑的缺点。
优点
- 本地化文本与它们的使用位置完全一致,这使得翻译它们更加容易,因为您始终可以看到它们使用的上下文。
- 您无需像使用资源文件那样为翻译定义键。
这尤其方便,因为有时 UI 中会有大量(解释性)文本(如工具提示),而为这些文本找到有用的键名既困难又烦人。 - 每个文本的翻译都近乎集中,而不是例如不得不不断地在多个资源文件之间切换。
- 您不必处理资源文件、XML 文件等。
- 这是一种非常简单的方法,因此易于理解,特别是对于刚接触您项目的新开发人员。
- 实现起来非常简单快捷。
- 它具有最小的运行时开销,因此是速度最快的本地化技术之一。
- 当需要本地化一个以前未本地化(使用静态文本)的应用程序时,使用起来要容易和快速得多。
- 由于涉及的代码量非常少,因此极不可能抛出异常,这使得您甚至可以在处理异常的 UI 部分(例如错误对话框)中使用本地化。
通常,本地化库相当复杂,并且在之前抛出异常时可能不起作用。
缺点
- 翻译分散在整个代码中,使得为已本地化的应用程序添加新语言更加困难。
- 对于非开发人员进行的翻译,效果不佳。
例如,翻译不能轻易地发送给其他人进行翻译(就像使用资源文件或 Excel 表格一样)。 - 翻译不能像使用其他方法那样轻松地重用。
- 无法在运行时切换语言。这样做只会影响在更改语言后创建的控件。但是,我很少看到真正需要运行时 UI 语言切换支持的项目。只有极少数情况需要这样做。
话虽如此,我建议此方法适用于符合以下标准的项目:
- 这是一个相对较小的项目。
- 开发人员也是进行本地化的人。
- 该应用程序仅支持少数语言(例如 2 到 5 种语言)。
有任何问题吗?
如果您对本文和代码有任何疑问,或者需要帮助使用它,请随时与我联系。您可以通过 info@rent-a-developer.de 联系我。
历史
- 2013 年 7 月 18 日:初始版本
- 2013 年 7 月 19 日:为 ReSharper 添加了 Live Templates