Microsoft Dataverse 支持多种语言。 如果您希望为包含不同基本语言的组织或设置多种语言的组织安装解决方案,在计划解决方案时,请将其考虑在内。 下表列出与要在支持多种语言的解决方案中包括的解决方案组件一同使用的策略。
策略 | 解决方案组件类型 |
---|---|
字符串(RESX)Web 资源 | Web 资源 |
嵌入式标签 | 应用程序导航(站点地图) 功能区 |
导出和导入翻译 | 属性 图表 仪表板 Entity 实体关系 表单 消息 选项集 视图 |
基本语言字符串中的本地化 | 合同模板 连接角色 流程(工作流) 安全角色 字段安全配置文件 |
不需要本地化 | SDK 消息处理步骤 服务端点 |
每种语言的单独组件 | 文章模板 电子邮件模板 邮件合并模板 报告 对话框 |
将 XML Web 资源用作语言资源 | 插件程序集 |
以下各节提供每个策略的其他详细信息。
字符串 (RESX) Web 资源
利用随 Dataverse 添加的字符串 (RESX) Web 资源,开发人员有了创建支持多种语言的 Web 资源的更强大的选项。 更多信息请参阅字符串 (RESX) Web 资源。
嵌入的标签
使用此策略的每个解决方案组件要求所有本地化文本包括在解决方案组件中。
功能区
当安装语言包时,应用程序功能区自动显示功能区中所有默认文本的本地化文本。 系统标签是在仅供内部使用的 ResourceId
属性值中定义的。
当添加自己的文本时,应使用 <LocLabels>
元素提供您支持的语言的本地化文本。 详细信息:对功能区使用本地化标签
SiteMap
当安装语言包时,应用程序导航栏中的默认文本自动显示本地化文本。
若要替代默认文本或提供您自己的文本,请使用 <Titles>
元素。
Titles
元素应包含 <Title>
元素,该元素包含您的解决方案支持的所有语言的本地化文本。
如果 Title
元素不可用于用户的首选语言,则显示与组织基本语言对应的标题。
<SubArea>
元素允许使用 userlcid
参数传递用户的语言首选项,以便作为 SubArea.Url
属性目标的内容可以了解用户的语言首选项并相应地调整。
详细信息:使用站点地图将参数传递给 URL
导出和导入翻译
可以导出下表中的解决方案组件的可本地化标签,以便于本地化。
实体 | 属性 | 关系 |
全局选项集 | 实体消息 | 实体窗体 |
实体视图 (SavedQuery) | 图表 | 仪表板 |
翻译标签和显示字符串
只能使用基础语言在应用程序中执行自定义。 因此,当要为这些自定义提供本地化标签和显示字符串时,必须导出标签的文本,以便它们可以针对为组织启用的任何其他语言本地化。 使用以下步骤:
确保您所在的组织已针对要为其提供翻译的语言安装了所有 MUI 包且设置了语言。
创建解决方案并修改组件
完成开发解决方案之后,请使用“导出翻译”功能。 这将生成一个 Office Excel 电子表格 (CrmTranslations.xml),其中包含所有需要翻译的标签。
在该电子表格上,提供了相应的翻译。
使用“导入翻译”功能将翻译导回到相同的 Dataverse 组织,然后发布您的更改。
下次导出解决方案时,它将会支持您提供的所有翻译。
当导入某个解决方案时,将会放弃目标系统中不可用的语言标签并且记录警告。
如果解决方案包中未提供目标系统基本语言的标签,则会改为使用源系统基本语言的标签。 例如,如果您导入的解决方案包含英语和法语的标签并且以英语作为基本语言,但是目标系统具有日语和法语并且以日语作为基本语言,则将会使用英语标签而不是日语标签。 基本语言标签不能是 null 值或为空。
正在导出翻译
在导出翻译之前,必须先安装语言包并设置要本地化的所有语言。 可以在 Web 应用程序中或使用 ExportTranslationRequest 消息导出翻译。 有关详细信息,请参阅导出翻译的自定义实体和字段文本。
正在翻译文本
当您在 Office Excel 中打开 CrmTranslations.xml 文件时,将显示下表中列出的三个工作表。
工作表 | 说明 |
---|---|
信息 | 显示有关标签和字符串从中导出的组织和解决方案的信息。 |
显示字符串 | 显示代表与元数据组件关联的所有消息文本的字符串。 此表包含用于系统功能区元素的错误消息和字符串。 |
本地化标签 | 显示所有元数据组件标签的所有文本。 |
可将此文件发送给语言专家、翻译机构或本地化公司。 他们需要为所有空单元格提供本地化字符串。
备注
对于自定义实体,有一些与系统实体共享的常用标签,如创建日期或创建者。 由于您已安装和设置语言,如果导出默认解决方案的语言,您能够将自定义实体中的某些标签与其他实体使用的相同标签的本地化文本相匹配。 这可以减少本地化成本和提高一致性。
本地化工作表中的文本后,将 CrmTranslations.xml 和 [Content_Types].xml 文件添加到一个压缩 .zip 文件中。 您现在可以导入此文件。
如果喜欢以编程方式将导出文件处理为 XML 文档,请参阅 Word、Excel 和 PowerPoint 标准支持,了解有关这些文件使用的架构的信息。
正在导入已翻译的文本
重要提示
您只能将已翻译文本重新导入回导出它的同一组织。
在导出自定义实体或属性文本并翻译后,您可以使用 ImportTranslationRequest 消息在 Web 应用程序中导入翻译完的文本字符串。 您导入的文件必须是在根目录中包含 CrmTranslations.xml 和 [Content_Types].xml 文件的压缩文件。 有关详细信息,请参阅导入翻译的实体和字段文本。
导入已完成的翻译后,会向使用自定义文本目标翻译语言的用户显示该文本。
备注
Dataverse 无法导入长度超过 500 个字符的已翻译文本。 如果翻译文件中的任何一项的长度超过 500 个字符,则导入过程失败。 如果导入过程失败,请检查文件中导致失败的行并减少该行的字符数,然后重新尝试导入。
因为只支持使用基本语言进行自定义,所以您只能在基本语言设置为语言首选项的 Dataverse 中进行自定义。 若要验证已翻译的文本是否已显示,必须更改 Dataverse 用户界面的语言首选项。 若要执行其他自定义工作,必须将语言首选项重新更改为基本语言。
基本语言字符串中的本地化
某些解决方案组件不支持多种语言。 这些组件包括只能在特定语言中有意义的名称或文本。 如果创建特定语言的解决方案,请为所需组织基本语言定义这些解决方案组件。
如果需要支持多种语言,一种策略是在基本语言字符串中包括本地化。 例如,如果您有一个名为“朋友”的连接角色,而您需要支持英语、西班牙语和德语,您可以使用文本““Friend (Amigo / Freund)”作为连接角色的名称。 由于文本长度的问题,对于使用此策略支持的语言数量有限制。
此组中的某些解决方案组件仅显示给管理员。 由于系统的自定义只能在组织基本语言中完成,因此需要提供多个语言版本。 Security Roles 和 Field Security Profile 组件属于此组。
Contract Templates 提供一种服务合同类型的描述。 它们需要名称和缩写字段的文本。 您应当考虑使用对组织中的所有用户唯一且适当的名称和缩写。
连接角色 依赖于选择描述性连接角色类别和名称的人员。 由于它们可能相当短,因此建议您在基本语言字符串中包括本地化。
为事件启动的流程(工作流) 可以正常工作,只要它们不需要使用要本地化的文本更新记录。 可以使用工作流程序集,以便应用于本地化文本的逻辑可以使用与插件程序集相同的策略(使用 XML Web 资源作为语言资源)。
按需工作流 需要一个名称,以便用户可以选择它们。 除了在按需工作流的名称中包括本地化之外,另一个策略是创建多个具有本地化名称的工作流(每个工作流调用相同的子流程)。 不过,所有用户将看到按需工作流的完整列表,而不仅是采用其首选用户界面语言的列表。
不需要本地化
SDK 消息处理步骤 和服务 终结点 解决方案组件不会向用户公开可本地化的文本。 如果这些组件具有对应于组织基本语言的名称和描述是非常重要的,您可以创建和导出具有该语言的名称和说明的托管解决方案。
每种语言的单独组件
以下每个解决方案组件都可能包含很大数量的要本地化的文本:
文章模板
电子邮件模板
邮件合并模板
报表
对话框
对于这些解决方案组件类型,推荐的策略是为每种语言创建单独的组件。 这意味着通常应创建一个基本托管解决方案,其中包含您的核心解决方案组件,然后创建单独的托管解决方案,其中包含每种语言的这些解决方案组件。 客户安装基本解决方案之后,他们可以安装为组织设置的语言的托管解决方案。
与流程(工作流)不同,您可以创建将反映用户当前语言首选项设置的对话并只将这些对话显示给使用该语言的用户。
创建本地化对话
安装适当的语言包并设置语言。
有关详细信息,请参阅语言包安装说明。
更改个人选项以指定对话所需语言的用户界面语言。
导航到设置并,在流程中心组中,选择流程。
单击新建,以您指定的语言创建对话。
创建完对话后,更改个人选项以指定组织基本语言。
当使用组织基本语言时,您可以导航到设置中的解决方案区域,将本地化对话作为解决方案的一部分添加进来。
以另一种语言创建的对话将只显示给使用该语言查看 Dataverse 的用户。
使用 XML Web 资源作为语言资源
插件程序集解决方案组件可以通过引发 InvalidPluginExecutionException 以及创建和更新记录来向最终用户发送消息。 与 Silverlight Web 资源不同,插件无法使用资源文件。
当插件需要本地化文本时,您可以使用 XML Web 资源存储本地化字符串,因此插件可以在需要时访问它们。 XML 结构是您的选项,但是您可能想要按照 ASP.NET 资源 (.resx) 文件来为每种语言创建单独的 XML Web 资源。 例如,下面是遵循 .resx 文件使用的模式的名为 localizedString.en_US 的 XML Web 资源 。
<root>
<data name="ErrorMessage">
<value>There was an error completing this action. Please try again.</value>
</data>
<data name="Welcome">
<value>Welcome</value>
</data>
</root>
下面的代码演示如何将本地化的消息传递回插件以将消息显示给用户。 它是 Account
实体的 Delete
事件的前期验证阶段:
protected void ExecutePreValidateAccountDelete(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
int OrgLanguage = RetrieveOrganizationBaseLanguageCode(localContext.OrganizationService);
int UserLanguage = RetrieveUserUILanguageCode(localContext.OrganizationService,
localContext.PluginExecutionContext.InitiatingUserId);
String fallBackResourceFile = "";
switch (OrgLanguage)
{
case 1033:
fallBackResourceFile = "new_localizedStrings.en_US";
break;
case 1041:
fallBackResourceFile = "new_localizedStrings.ja_JP";
break;
case 1031:
fallBackResourceFile = "new_localizedStrings.de_DE";
break;
case 1036:
fallBackResourceFile = "new_localizedStrings.fr_FR";
break;
case 1034:
fallBackResourceFile = "new_localizedStrings.es_ES";
break;
case 1049:
fallBackResourceFile = "new_localizedStrings.ru_RU";
break;
default:
fallBackResourceFile = "new_localizedStrings.en_US";
break;
}
String ResourceFile = "";
switch (UserLanguage)
{
case 1033:
ResourceFile = "new_localizedStrings.en_US";
break;
case 1041:
ResourceFile = "new_localizedStrings.ja_JP";
break;
case 1031:
ResourceFile = "new_localizedStrings.de_DE";
break;
case 1036:
ResourceFile = "new_localizedStrings.fr_FR";
break;
case 1034:
ResourceFile = "new_localizedStrings.es_ES";
break;
case 1049:
ResourceFile = "new_localizedStrings.ru_RU";
break;
default:
ResourceFile = fallBackResourceFile;
break;
}
XmlDocument messages = RetrieveXmlWebResourceByName(localContext, ResourceFile);
String message = RetrieveLocalizedStringFromWebResource(localContext, messages, "ErrorMessage");
throw new InvalidPluginExecutionException(message);
}
protected static int RetrieveOrganizationBaseLanguageCode(IOrganizationService service)
{
QueryExpression organizationEntityQuery = new QueryExpression("organization");
organizationEntityQuery.ColumnSet.AddColumn("languagecode");
EntityCollection organizationEntities = service.RetrieveMultiple(organizationEntityQuery);
return (int)organizationEntities[0].Attributes["languagecode"];
}
protected static int RetrieveUserUILanguageCode(IOrganizationService service, Guid userId)
{
QueryExpression userSettingsQuery = new QueryExpression("usersettings");
userSettingsQuery.ColumnSet.AddColumns("uilanguageid", "systemuserid");
userSettingsQuery.Criteria.AddCondition("systemuserid", ConditionOperator.Equal, userId);
EntityCollection userSettings = service.RetrieveMultiple(userSettingsQuery);
if (userSettings.Entities.Count > 0)
{
return (int)userSettings.Entities[0]["uilanguageid"];
}
return 0;
}
protected static XmlDocument RetrieveXmlWebResourceByName(LocalPluginContext context, string webresourceSchemaName)
{
context.TracingService.Trace("Begin:RetrieveXmlWebResourceByName, webresourceSchemaName={0}", webresourceSchemaName);
QueryExpression webresourceQuery = new QueryExpression("webresource");
webresourceQuery.ColumnSet.AddColumn("content");
webresourceQuery.Criteria.AddCondition("name", ConditionOperator.Equal, webresourceSchemaName);
EntityCollection webresources = context.OrganizationService.RetrieveMultiple(webresourceQuery);
context.TracingService.Trace("Webresources Returned from server. Count={0}", webresources.Entities.Count);
if (webresources.Entities.Count > 0)
{
byte[] bytes = Convert.FromBase64String((string)webresources.Entities[0]["content"]);
// The bytes would contain the ByteOrderMask. Encoding.UTF8.GetString() does not remove the BOM.
// Stream Reader auto detects the BOM and removes it on the text
XmlDocument document = new XmlDocument();
document.XmlResolver = null;
using (MemoryStream ms = new MemoryStream(bytes))
{
using (StreamReader sr = new StreamReader(ms))
{
document.Load(sr);
}
}
context.TracingService.Trace("End:RetrieveXmlWebResourceByName , webresourceSchemaName={0}", webresourceSchemaName);
return document;
}
else
{
context.TracingService.Trace("{0} Webresource missing. Reinstall the solution", webresourceSchemaName);
throw new InvalidPluginExecutionException(String.Format("Unable to locate the web resource {0}.", webresourceSchemaName));
return null;
// This line never reached
}
}
protected static string RetrieveLocalizedStringFromWebResource(LocalPluginContext context, XmlDocument resource, string resourceId)
{
XmlNode valueNode = resource.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, "./root/data[@name='{0}']/value", resourceId));
if (valueNode != null)
{
return valueNode.InnerText;
}
else
{
context.TracingService.Trace("No Node Found for {0} ", resourceId);
throw new InvalidPluginExecutionException(String.Format("ResourceID {0} was not found.", resourceId));
}
}