简介
我们的世界是一个整体,作为一个开发人员,我们不能将目光局限于一个国家或地区,而应该放眼全球。我们的应用程序要让全世界所有文化的人都能使用,才能称得上是一个成功的程序。但是,这个世界上应该没有哪个人懂得所有文明的语言,因此,我们必须把应用程序本地化的工作交给当地那些懂得该文明语言和其它风俗习惯的人进行。但是,语言文化专家往往不是程序开发人员,所以我们不能够要求他们修改我们的源代码来进行翻译。我们必须提供一个更方便的工具,帮助语言文化工作人员。这就是作为一个开发人员所需要做的工作。
本文将描述一下Windows Phone上应用程序本地化的基本方式。我们不会涉及到各国的语言学习,因为那是语言文化工作人员的事。我们会站在一个开发人员的角度,看看从程序本身能做到什么来方便语言文化工作人员进行本地化。
你可以自File:WPLocalization.zip下载本文附带的源代码。注意若是你的显卡不支持DirectX 10的特性,你不能在模拟器上运行示例中的XNA项目(但可以运行Silverlight项目),而必须将程序部署到真实的手机上运行。
本地化的主要工作
作为一个开发人员,我们为本地化做的工作主要有以下这些:
- 寻找程序中需要翻译的部分,例如文字,部分包含文字的图片,语音,等等。
- 将这些需要翻译的部分提取出来,放到资源中,而不要写死在代码中。
- 定制程序的布局,使得界面上有足够的空间显示文本。例如,英文通常会比中文更长一些,因此需要占据更多的空间。可能有些语言会更长。
完成了以上这几步,语言文化工作人员所需要做的事就仅仅只是翻译,而不需要考虑任何和代码有关的事了。
Windows Phone程序的本地化
接下来我们就具体看看如何为我们的程序提供本地化支持。在Windows Phone中,有两个地方需要分别进行本地化。一个是程序在手机主页面上以及程序列表中显示的标题,这对于Silverlight和XNA是通用的。另一个是进入程序之后的显示,在这里Silverlight和XNA将会有一点点不同,但是大致的过程还是通用的。
程序标题的本地化
程序在手机主页面上以及程序列表中显示的标题并不是我们自己的程序负责控制的,而是由手机的操作系统统一管理的。因此这一块的本地化和程序内部的本地化有所区别。具体步骤如下:
创建一个C++ Win32 dll项目。创建这个项目时请勾上Empty Project。尽管项目类型是C++,但是我们不需要任何C++代码,只需要一个资源文件。注意这个项目的名字必须叫做AppResLib。
然后在这个C++项目的属性页面中选择Linker=>Advanced,并且将No Entry Point设置成Yes,这意味着我们的项目不需要任何代码。
接下来,为C++项目添加一个Resource(在右键的Add菜单中有一项名叫Resource,就在Class下面),resource的类型选择String Table,并且点New。
接下来你会看到一个设计器,有点像一个DataGrid。在这里你可以添加或修改项目。请添加两项:
- ID: AppTitle, Value: 100, Caption: 你的程序名称。
- ID: AppTileString, Value: 200, Caption: 你的程序名称。
请不要搞错了,第一个ID中的是Title,第二个ID中的是Tile。
这两项分别用于程序列表以及手机的开始界面。通常你会选择使用相同的值,代表程序的名称。在这边你可以使用一个大家都看得懂的语言,例如英文。现在你写的这些文字只是给语言文化工作人员看的,不是你的程序真正显示的文字,所以请不要太在意文字的细节。
现在编译你的程序,会得到一个dll。将这个dll作为一个existing item添加到你的Windows Phone项目中(不管是Silverlight还是XNA)。
下一步就是修改Windows Phone项目让它使用dll中的资源了。为此首先打开AssemblyInfo.cs(在Properties文件夹下),在这里你会看到一行AssemblyTitle,例如:
[assembly: AssemblyTitle("SilverlightLocalization")]
请将它改成:
[assembly: AssemblyTitle("@AppResLib.dll,-100")]
这边的100是上面添加的第一项资源的Value,若你之前并未使用100,请自行修改数值。
然后打开WMAppManifest.xml文件(同样在Properties文件夹下),并且将App节点的Title属性的值改成:
@AppResLib.dll,-100
继续往下找到PrimaryToken节点,下面有一个Title子节点,我们需要将它的值改成:
@AppResLib.dll,-200
最后,针对XNA还需要做一件额外的事,打开XNA项目属性,在第一个XNA Game Studio页面上,有一个叫Tile title的文本框,将它的值也改成上面的@AppResLib.dll,-200:
至此,作为一个开发人员,你的工作就完成了。
接下来我们不妨看看一个语言文化工作人员会如何接手,开发人员不需要了解具体某个单词如何翻译成另外一种语言,但是对这个过程有一个大致的了解还是很有帮助的。作为一个语言文字工作人员,如果要本地化一个应用程序,还是需要有一定的电脑知识的,但是他们不需要懂得编程。
首先,他们需要打开你的C++项目,打开其中的resource文件以及其中的string table。然后他们会将其中的每一项进行翻译。他们不会修改ID和Value,只会修改Caption的值。随后,他们会修改项目属性,在Resources=>General页面中,他们会将Culture的值改成对应的文化,例如:
随后他们会编译这个C++项目,获取一个dll。他们会将这个dll重命名一下,在后面添加.文化代码.mui。例如:
- 英文:AppResLib.dll.0409.mui
- 中文:AppResLib.dll.0804.mui
你可能不知道应该使用什么文化代码,但是你不用在乎也不用去记忆。作为语言文化工作人员,他们心里自然清楚。
然后,他们会将这个文件作为existing item添加到你的Windows Phone项目中,并且将Build Action设置成Content。
最后编译程序,这个mui文件中的资源就会代替你原来的dll中的资源了。
例如,下面是一个英文版的程序:
下面是同一个程序的中文版:
可以看到,虽然语言文化工作人员还是不可避免地要涉及到一些和程序相关的内容,但是至少他们不需要修改任何代码文件。他们的主要工作是翻译资源文件。而作为开发人员,你不需要懂任何当地语言,你只需要提供好一个机制,让懂当地语言的语言文化工作人员去进行翻译即可。这样一来,开发人员和语言文化工作人员的分工就很清晰了。
程序内部的本地化
接下来我们看看程序内部如何进行本地化。这一点对于Silverlight和XNA是有所通用,但也有所不同的。
在Silverlight和XNA项目中,我们全都通过resx文件存储资源。请在你的Windows Phone项目(注意不是C++项目)中添加一个资源文件(选择Resource Files模板),命名为AppResources.resx。在这里你可以像之前一样添加一堆数据到string table中。这边的每一项都有三列:
- Name: 相当于之前的resx中的ID。
- Value: 文本内容,在这里你输入的是默认语言的文本(默认是英文,但是你可以通过项目属性修改),语言文化工作者会将它替换成真正的特定于某文化的文本。
- Comment:这个可填可不填。它的作用是给语言文化工作者更多的提示,告诉他们这段文字的上下文,程序本身不会使用它。
此外,你需要在工具栏上将Access Modifier设置成Public:
现在,你就可以在代码中使用这些资源了。例如:
MessageBox.Show(AppResources.MessageBoxText);
Silverlight
以上的工作内容对于Silverlight和XNA是通用的,接下来我么看看一些不同点。
若是你希望在XAML中使用这些资源,需要创建一个类,这边我们把它喊做LocalizedStrings。这个类的实现很简单:
public class LocalizedStrings
{
public LocalizedStrings()
{
}
private static AppResources localizedResources = new AppResources();
public AppResources LocalizedResources { get { return localizedResources; } }
}
有了这个类,我们就可以在XAML中使用本地化字符串了。为此我们在App.xaml中定义这个类作为一个application级别的资源,从而可以在整个application范围内使用。
首先确保添加了namespace引用:
xmlns:local="clr-namespace:SilverlightLocalization"
然后将LocalizedStrings作为资源添加进来:
<Application.Resources>
<local:LocalizedStrings x:Key="localizedStrings"/>
</Application.Resources>
现在在某个XAML页面中,可以通过数据绑定的形式显示文本:
<TextBlock Text="{Binding LocalizedResources.TitleText, Source={StaticResource localizedStrings}}"/>
最后,还有一个比较特别的地方是ApplicationBar。ApplicationBar并不是Silverlight的对象,所以你不能在XAML中使用数据绑定对它进行本地化。你必须使用代码。具体方法和之前在代码中使用本地化资源是一样的:
到了这里,作为开发人员,你的工作就全部完成了。接下来我们还是来看一看语言文化工作人员会怎么做吧。为了更好的和他们合作,也是有必要了解一下他们的工作方式的。
首先,他们会创建一个新的资源文件,取名为AppResources.文化缩写.resx,例如中文是AppResources.zh-CN.resx。
在这里他们会将你默认的AppResources.resx文件中所有的内容复制过来,并且对每一项进行翻译。
然后,他们会修改你的csproj文件,并且添加所支持的语言:
<SupportedCultures> zh-CN; </SupportedCultures>
随后重新编译,这就是他们所需要做的全部工作了,不会涉及到任何代码相关的内容。
以下是一个英文版的程序:
以下是同一个程序的中文版:
注意到一些系统组件,例如MessageBox,本身就做好了本地化,你不需要做任何事情。
XNA
最后我们看看在XNA中进行本地化的注意点。其实在XNA中进行本地化和Silverlight基本上是一样的。当然你不需要考虑XAML,因此你只需要一个AppResources.resx文件,而不需要一个类来将它暴露给XAML。之后在代码中使用资源和Silverlight中是一样的。
此外,别忘了XNA不能够直接使用系统字体,你必须将需要的字体以spritefont格式定义,并且添加到程序中。为了在界面上绘制文字,必须使用字体文件。这就产生了一个问题:你怎么知道应该使用哪个字体文件?为了解决这个问题,我们不能使用XNA默认的content pipeline,而必须自己写一个。
请创建一个项目,选择“Content Pipeline Library Extension”作为模板。
在这个项目中我们需要两个类,第一个是继承自FontDescription的类,它会解析spritefont文件。你也许知道其实spritefont文件是一个xml文件。默认的FontDescription会去解析全部的xml tag,但是当他读到和本地化相关的tag时,并不会做任何事情。所以我们需要在自己的FontDescription中添加这一功能。
class LocalizedFontDescription : FontDescription
{
public LocalizedFontDescription()
: base("Arial", 14, 0)
{
}
[ContentSerializer(Optional = true, CollectionItemName = "Resx")]
public List<string> ResourceFiles
{
get { return resourceFiles; }
}
List<string> resourceFiles = new List<string>();
}
上面的代码意味着若是读到了Resx这个节点,就将内容反序列化到一个List<string>中间。至于构造函数,只是提供一些默认值而已,具体数值content pipeline会自动解析spritefont文件获得。
第二个类继承自ContentProcessor<TInput, TOutput>,你可以直接将项目模板生成的ContentProcessor1重命名一下。既然现在我们是要使用之前创建的LocalizedFontDescription并且输出一个SpriteFontContent,那么就可以将TInput和TOutput定死:
[ContentProcessor(DisplayName = "LocalizationContentPipelineExtension.LocalizedFontProcessor")]
class LocalizedFontProcessor : ContentProcessor<LocalizedFontDescription, SpriteFontContent>
{
public override SpriteFontContent Process(LocalizedFontDescription input, ContentProcessorContext context)
{
foreach (string resourceFile in input.ResourceFiles)
{
string absolutePath = Path.GetFullPath(resourceFile);
// Make sure the .resx file really does exist.
if (!File.Exists(absolutePath))
{
throw new InvalidContentException("Can't find " + absolutePath);
}
// Load the .resx data.
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(absolutePath);
// Scan each string from the .resx file.
foreach (XmlNode xmlNode in xmlDocument.SelectNodes("root/data/value"))
{
string resourceString = xmlNode.InnerText;
// Scan each character of the string.
foreach (char usedCharacter in resourceString)
{
input.Characters.Add(usedCharacter);
}
}
context.AddDependency(absolutePath);
}
return context.Convert<FontDescription, SpriteFontContent>(input, "FontDescriptionProcessor");
}
}
这边的主要工作是从之前的LocalizedFontDescription的ResourceFiles集合中读取每一个resx文件,从中读取所有的文字。
接下来要做的事情就是修改spritefont文件,在Asset节点内部的最下方添加我们自定义ResourceFiles的节点:
<ResourceFiles>
<Resx>..\XNALocalization\AppResources.resx</Resx>
<Resx>..\XNALocalization\AppResources.zh-CN.resx</Resx>
</ResourceFiles>
</Asset>
当然,最后这一步通常是语言文化工作人员做的若是你想要进一步方便他们的工作,可以开发一个工具来帮助他们修改spritefont文件。
有关更详细的信息,请参考http://msdn.microsoft.com/en-us/library/ff966426.aspx。
由于我现在用的这台机体显卡太旧,无法正常在模拟器上运行XNA程序,因此无法截图。你可以运行本文附带的示例看到效果。注意若是你的显卡不支持DirectX 10的特性,你不能在模拟器上运行,而必须将程序部署到真实的手机上运行。
本地化非文本资源
除了文本之外,还有一部分资源可能需要本地化,例如图片,电影,等等。对于开发人员而言,你需要做的事情是给出一张清单,告诉语言文化工作人员哪些内容需要本地化,随后他们会自行将资源文件替换掉。这一部分工作通常不会涉及到代码。
总结
本文让大家看到了如何为Windows Phone程序进行本地化。本地化是非常重要的一个过程,请不要忽视它。或许你的程序目前仅仅针对中国市场开放,但是说不定将来你的业务会扩展到全球市场。现在做好本地化的框架,今后就可以完全将本地化工作交给语言文化工作人员,而不需要你亲自进行了。