CUBA 应用程序中的本地化

CUBA 应用程序对于本地化语言消息具有内建支持。语言翻译保存在属性文件中,应用程序代码和语言翻译之间清楚地分离开。

将要构建的内容

本指南对 CUBA 宠物诊所示例进行了增强,以演示如何在 CUBA 应用程序中定义和使用本地化消息。

  • 使应用程序支持德语

  • 对登记宠物就诊时自动生成的描述信息进行本地化

开发环境要求

你的开发环境需要满足以下条件:

下载 并解压本指南的源码,或者使用 git 克隆下来:

示例: CUBA 宠物诊所

这个示例是以 CUBA 宠物诊所项目为基础,而这个项目的基础是众所周知的 Spring 宠物诊所项目。CUBA 宠物诊所应用程序涉及到了宠物诊所的领域模型及与管理一家宠物诊所相关的业务流程。

这个应用程序的领域模型看起来像这样:

领域模型

主要实体是 PetVisit。 一个 Pet 到一家诊所就诊,在诊疗过程(Vist) 会有一名兽医(Vet)负责照顾它。每个宠物都有主人,一个主人可以有多个宠物。一个诊疗过程(Vist)即是一个宠物在主人的帮助下到诊所就诊的活动。

CUBA 应用程序中本地化翻译的保存位置

CUBA 遵循 Java 应用程序处理本地化翻译的通用模式。不同的语言或本地化消息存放在与源码相同的包下的 .properties 文件中。

每个Java包下对于每种语言都有一个属性文件,这些文件包含了将特定文本翻译成目标语言的信息。语言文件的后缀语言的本地代码对应,比如 俄语是 _ru ,西班牙语是 _es

另外有一个在文件名中没有指定本地化代码的默认文件 message.properties 。这个文件在目标语言不可用或特定的翻译不可用时起到了一个“失败恢复”的作用。

在 Studio 中使用本地化对话框时,Studio 会把选中的第一个本地化代码的消息放到默认文件中,其它的放到带有后缀的文件中。

如果应用程序是国际化的,将英语或主要受众的语言作为默认语言文件的语言是比较合理的方式,这样当需要的语言缺失时, 可以从默认文件获取消息显示给用户。

在宠物诊所示例中,文件结构看起来是这样:

com.haulmont.sample.petclinic.entity
|
├── owner
│   ├── Owner.java
│   └── messages.properties
├── pet
│   ├── Pet.java
│   ├── PetType.java
│   ├── messages.properties
│   └── messages_de.properties
├── vet
│   ├── Specialty.java
│   ├── Vet.java
│   ├── messages.properties
│   └── messages_de.properties
└── visit
    ├── Visit.java
    ├── VisitType.java
    ├── messages.properties
    └── messages_de.properties

属性文件由键值对组成,键名是要本地化的消息的标识,值是对应的语言翻译。键名在所有语言文件中都是相同的,只是值不同。在宠物诊所示例中,本地化实体和实体属性名看起来是这样:

messages.properties
Pet.birthDate = Birth date
Pet = Pet
Pet.type = Type
Pet.owner = Owner
Pet.identificationNumber = Identification Number
PetType = Pet Type
messages_de.properties
Pet.birthDate = Geburtstag
Pet = Haustier
Pet.type = Typ
Pet.owner = Tierhalter
Pet.identificationNumber = Haustier-Identifikationsnummer
PetType = Tierart

在宠物诊所示例中,实体属性的翻译通过在项目中创建 messages_de.properties 文件实现。

CUBA Studio 为每个 Java 包提供了一个资源包查看器,通过它可以查看选中包的所有翻译。

cuba studio resource bundle screen

由于资源包编辑器使用了两边对应的布局,右边显示各种语言的翻译,创建翻译的操作比较方便。在左边显示出所有消息键名,如果某些消息缺少特定的翻译,键名会高亮显示,这样可以很容易地发现它们。

关于本地化消息存储结构的详细信息请参考文档:https://doc.cuba-platform.cn/manual-{cuba_version}-chs/localization.html[本地化] 。

在应用程序中注册可用的语言

要激活允许用户选择语言的功能,需要在应用程序中配置两个主要属性。

第一个属性是激活登录界面的语言下拉框:cuba.localeSelectVisible ,这个属性应该设置为 true

第二个属性是可选的语言/本地化代码: cuba.availableLocales = English|en;German|de

Both configurations should be placed in the web-app.properties. After this configuration is in place, the Login screen displays the language selection box at login:

两个属性都要放到 web-app.properties 文件。在这些配置完成后,登录界面会显示一个语言选择框:

login screen with activated language selection

当用户使用一个指定语言登录后,应用程序会显示正确的翻译。如果所有 UI 组件需要的翻译文件都准备就绪,框架会自动应用这些翻译。 <form> 组件以正确的语言显示属性标题:

localized editor

引用本地化消息

对于 <groupTable /><form /> ,会自动使用相关的实体属性的翻译。 对于没有直接与实体属性绑定的UI组件,有两种方式与 CUBA 的本地化机制交互: 在界面 XML 描述中定义或在Java代码中使用编程方式。

要显示正确的本地化消息,需要在代码以中显式地引用消息键名。

在 XML 描述中引用消息

第一个选项是在 XML UI 描述中引用特定的消息键名。在宠物诊所示例中,"regular checkup" 按钮标题应该以本地化语言显示,这里通过 msg:// 后面跟上消息键名来实现。

visit-browse.xml
<groupTable id="visitsTable"
            dataContainer="visitsDc"
            width="100%">
    <actions>

        <action id="createRegularCheckup"
                caption="msg://createRegularCheckup" (1)
                icon="font-icon:BRIEFCASE" />

    </actions>
    <buttonsPanel id="buttonsPanel"
                  alwaysVisible="true">
        <button id="createForPetBtn" action="visitsTable.createRegularCheckup"/>
    </buttonsPanel>
</groupTable>
1 msg://createRegularCheckup 引用了 XML 描述所在包下的 createRegularCheckup 消息

编程式消息本地化

与本地化机制交互的第二种方式是通过 Java 代码。在宠物诊所示例中,"regular checkup" 功能应该以编程方式显示两种动态消息 :

  1. 当用户打开新建常规检查的界面,就诊实体(visit)的discription属性应该自动填写一个特定的本地化消息

  2. 一旦常规检查记录被创建,应该显示一个本地化的通知消息, 消息中包含宠物的名称。

可以使用 Messages 接口以编程的方式获取消息。它提供了一个通过 CUBA 的本地化消息机制获取翻译的字符串的 API,本地化语言是根据当前登录用户的本地化设置确定的。

在示例代码中没有使用 Messages 接口,而是使用了 MessageBundle 接口。它是 CUBA 7.0 新引入的快捷机制,它从正在调用的 Java 类的消息包中获取特定消息。这种方式省略了使用翻译 API 时指定消息包的步骤。

第一个预填写的 description 属性的本地化消息这样创建:

VisitBrowse.java
public class VisitBrowse extends StandardLookup<Visit> {

    @Inject
    private Notifications notifications;

    @Inject
    private MessageBundle messageBundle;
    // ...
    private void createVisitForPet(Pet pet) {
        screenBuilders.editor(visitsTable)
                .newEntity()
                .withInitializer(visit -> {
                    visit.setDescription(
                        regularCheckupDescriptionContent(pet) (1)
                    );
                    visit.setPet(pet);
                })
                .withScreenClass(RegularCheckup.class)
                .withLaunchMode(OpenMode.DIALOG)
                .withAfterCloseListener(event -> {
                    if (event.getCloseAction().equals(WINDOW_COMMIT_AND_CLOSE_ACTION)) {
                        showRegularCheckupCreatedNotification(pet);
                    }
                })
                .build()
                .show();
    }

    private String regularCheckupDescriptionContent(Pet pet) {
        return messageBundle.formatMessage( (2)
                "regularCheckupContent", (3)
                pet.getName(), (4)
                pet.getIdentificationNumber()
        );
    }
1 将在实体创建时设置就诊(visit)实体的 description 属性。
2 messageBundle Bean 用于获取翻译的消息
3 regularCheckupContent 是要翻译的消息的键
4 参数 pet.getName()pet.getIdentificationNumber() 作为动态部分传递给翻译

对于消息的动态部分,应该像下面的翻译一样包使用占位符:%s

messages_de.properties
regularCheckupContent=Kontrolluntersuchung für %s (%s)\n\n

Bestandteile: \n
- Krallen-Kontrolle\n
- Temperatur messen\n
- Zahn-Check

这段代码会产生如下对常规检测的描述值:

editor with programmatic created i10n messages

常规检查(Regular Checkup )创建成功后应该以通知的方式显示第二条消息。Visit 浏览界面控制器中的 showRegularCheckupCreatedNotification 方法在实例创建成功后会被调用。

VisitBrowse.java
public class VisitBrowse extends StandardLookup<Visit> {
    // ...
    @Inject
    private MetadataTools metadataTools;

    @Inject
    private MessageBundle messageBundle;

    @Inject
    private Notifications notifications;

    private void showRegularCheckupCreatedNotification(Pet pet) {

        String petName = metadataTools.getInstanceName(pet); (1)

        String regularCheckupCreatedMessage = messageBundle
            .formatMessage("regularCheckupCreated", petName); (2)

        notifications.create(Notifications.NotificationType.TRAY)
                .withCaption(regularCheckupCreatedMessage) (3)
                .show();
    }
1 metadataTools 在 CUBA 7.0+ 中用于获取实体实例
2 又一次使用 messageBundle 获取特定的参数化消息
3 本地化消息被传递给 notifications bean 来显示一个托盘消息

对已有 UI 界面或功能的翻译

由于 CUBA 内置了一些 UI 组件和多个开箱即用的 UI 界面,现有的功能了需要进行本地化。对于这个问题, 有两种主要的解决方案:

  1. 社区语言包

  2. 手动提供的翻译

社区语言包

首选且最简单的方案是使用社区已经提供的语言包。当前有12个可用的翻译: 西班牙语、俄语、法语、简体中文、德语及其它。

社区翻译包由社区提供,因此在准确性及与平台的同步方面是有区别的,社区只能尽力做到最好。

要将这些翻译加入应用程序,可以从 Github 仓库上的翻译 来手工获取。有些翻译可以以应用程序组件的方式使用,可以在这里查找可用的翻译: CUBA 市场, 这种方式最简捷。

在宠物诊所示例中,由于目标语言是德语,已经有现成的应用程序组件可用:https://www.cuba-platform.com/marketplace/german-localization/[德语本地化] 。

在 CUBA Studio 中应用程序组件可以通过下列菜单安装:

CUBA > Project Properties > Custom Components.

cuba studio add translation component

然后,唯一需要做的事情是将新语言手工加到应用程序属性:cuba.availableLocales 。 也可以在 CUBA Studio 通过项目属性界面来添加。

手动提供语言

在社区没有提供目标语言或者提供的语言不完整或有错误,可以在应用程序中直接提供这些翻译。

CUBA 本身为界面提供的翻译在平台的源码中,以常规的 *.properties 属性文件方式提供。比如用户浏览(User Browser)界面,在 `com.haulmont.cuba.gui.app.security.user.browse`包中包含了 messages.properties 文件,在这个文件中包含有界面的翻译。

要为这个界面提供或覆盖翻译,需要完成两件事:

必要时创建 GUI 模块

放置翻译的模块在应用程序中必须可用。 大多数平台界面是在 GUI 模块,但是此模块在 CUBA 7.0+ 的应用程序中是默认不启用的。如果目标应用程序没有 GUI 模块,就需要添加一个 GUI 模块来放置翻译文件。

在 CUBA Studio 中通过以下菜单添加 GUI 模块:

CUBA > Advanced > Manage Modules > Create 'gui' module.

在应用程序中创建包镜像(Package-Mirror)

确认了目标模块在应用程序中可用后,原始翻译文件所在的包必须在应用程序中再创建出来。在这些包目录中存放添加或替换的消息文件。 CUBA 将读取这些文件来显示正确的界面翻译。

在宠物诊所示例中,已经为用户浏览界面创建了消息文件:https://github.com/cuba-guides/cuba-petclinic-i18n-messages/blob/master/modules/gui/src/com/haulmont/cuba/gui/app/security/user/browse/messages_de.properties[messages_de.properties] , 用于为这个界面提供德语翻译。

最终使用了德语本地化设置的用户浏览界面看起来是这样:

localized platform screen

总结

CUBA 支持开箱即用的应用程序本地化以支持。它提供了一个机制,允许应用程序的构建者为应用程序提供多语言/本地化支持。每个用户都可以选择所需的语言。

在应用程序代码中可以使用消息键来引用本地化消息,消息是在运行时被转换成目标语言。有几种方式可以与本地化机制交互。这里展示了基于 XML 声明式引用和使用 MessageBundle 接口的编程式引用。

有一些由社区提供的对平台本身的翻译,这些翻译涵盖了平台功能和界面(比如管理员界面)。