dojo dragon main logo

国际化 Dojo 应用程序

配置应用程序支持的区域

一个国际化的应用程序,应该在它的构建配置文件 .dojorc 中指定支持的区域。应该将其中一个区域指定为应用程序的主(默认)区域,其余受支持的区域可作为辅助选项,可在需要时激活。这是通过 build-app 中的 locale 属性和 supportedLocales 列表实现的。

注意:因为大量的格式化和解析器都依赖于特定语言环境的 CLDR 数据,所以为了能正常运行,@dojo/framework/i18n 提供的大部分功能都要求在 .dojorc 中设置至少一个 locale。比如,如果没有指定默认的 locale,则只返回默认包中的消息,并且将禁用 ICU 消息格式化功能。

  • locale: string
    • 应用程序支持的主区域。即,如果没有指定覆盖的区域,将使用此作为默认语言。
  • supportedLocales: string[]
    • 应用程序支持的其他区域列表。这些区域激活后可覆盖默认的 locale,当运行在客户端时可通过应用程序中用户的语言设置来隐式激活,运行在服务器端时通过进程或主机的语言设置隐式激活,或者在应用程序中显式激活

例如,使用以下配置,应用程序的默认语言环境为英语(en),也支持西班牙语(es)和法语(fr)。

.dojorc

{
    "build-app": {
        "locale": "en",
        "supportedLocales": ["es", "fr"]
    }
}

创建支持 i18n 的部件

使用 @dojo/framework/core/middleware/i18n 中的 i18n 中间件,可国际化单个部件。使用中间件向部件的属性接口添加额外的 i18n 相关属性。i18n 中间件的 API 包含一个方法 localize(bundle),用于获取消息包中的本地化消息文本;以及两外两个方法,用于获取和设置应用程序的区域详情。

i18n 部件属性

  • locale?: string
  • rtl?: boolean
    • 一个可选标记,指定部件的文本方向。如果值为 true,则部件底层 DOM 节点的 dir 属性设置为 "rtl"。如果值为 false,则 dir 属性设置为 "ltr"。否则,不设置该属性。
  • i18nBundle?: Bundle<Messages> | Map<Bundle<Messages>, Bundle<Messages>>
    • 一个可选的覆写,传给 localizeBundle 方法的默认语言包。如果覆写包含 messages 对象,那么它将完全替换部件使用的默认语言包。如果覆写仅包含一个 locales 对象,则将使用覆盖中指定的其他本地化加载器来加载新创建的包。

i18nlocalize() 方法

部件可以将默认语言包传给 localize 方法,以便根据部件的 locale 属性获取对应的本地化包。

如果包支持部件当前设置的区域,但尚未加载这些特定区域的消息,则会返回一个消息都为空字符串的包。或者,为 localize 方法传入第二个布尔类型的参数,当值为 true 时,则会返回默认的消息,而不返回空包。当特定语言的消息加载完成,部件会失效,然后使用本地化的消息内容重新渲染。

localize 返回的对象包含以下属性和方法:

  • messages
    • 一个对象,包含本地化消息的键值对。如果此消息尚未加载,则 messages 的值是空包或者默认的消息,这取决于调用 localize 的方式。
  • isPlaceholder
    • 一个布尔属性,指出返回的消息是实际的特定区域的消息(false),还是只是等待本地化消息完成加载时使用的占位符(true)。如果本地化消息尚未加载,则要避免渲染部件。
  • format(key: string, replacements: { [key: string]: string })
    • 一个方法,第一个参数是消息的 key,第二个参数是用于替换的值。例如,如果包中包含 greeting: 'Hello, {name}!',则调用 format('greeting', { name: 'World'}) 将返回 'Hello, World!'

以下示例使用了 localize 返回值的所有功能:

nls/en/MyI18nWidget.ts

export default {
    messages: {
        hello: 'Welcome to the shop',
        purchaseItems: 'Please confirm your purchase',
        itemCount: 'Purchase {count} items'
    }
};

widgets/MyI18nWidget.tsx

import { create, tsx } from '@dojo/framework/core/vdom';
import i18n from '@dojo/framework/core/middleware/i18n';
import Label from '@dojo/widgets/label';
import Button from '@dojo/widgets/button';

import greetingsBundle from '../nls/en/MyI18nWidget';

const factory = create({ i18n });

export default factory(function MyI18nWidget({ middleware: { i18n } }) {
    // Load the "greetings" messages for the current locale. If the locale-specific
    // messages have not been loaded yet, then the default messages are returned,
    // and the widget will be invalidated once the locale-specific messages have
    // loaded.
    const { format, isPlaceholder, messages } = i18n.localize(greetingsBundle);

    // In many cases it makes sense to postpone rendering until the locale-specific messages have loaded,
    // which can be accomplished by returning early if `isPlaceholder` is `true`.
    if (isPlaceholder) {
        return;
    }

    return v('div', { title: messages.hello }, [
        w(Label, {}, [
            // Passing a message string to a child widget.
            messages.purchaseItems
        ]),
        w(Button, {}, [
            // Passing a formatted message string to a child widget.
            format('itemCount', { count: 2 })
        ])
    ]);
});

注意,这种模式支持部件从多个包中获取消息。但是,简洁起见,推荐每个部件单独使用各自的消息包。

基于类的部件使用 I18nMixin

通过混入 @dojo/framework/core/mixins/I18n 中的 I18nMixin,可国际化单个基于类的部件。这个 Mixin 将与 i18n 中间件相同的 i18n 相关属性添加到部件属性中,并且提供了一个 localizeBundle 方法,将导入的消息包与部件当前设置的区域关联。

localizeBundle() 方法

部件可以将默认语言包传给 localizeBundle 方法,以便根据部件的 locale 属性获取对应的本地化包。

如果包支持部件当前设置的区域,但尚未加载这些特定区域的消息,则会返回一个消息都为空字符串的包。或者,为 localizeBundle 方法传入第二个布尔类型的参数,当值为 true 时,则会返回默认的消息,而不返回空包。当特定语言的消息加载完成,部件会失效,然后使用本地化的消息内容重新渲染。

localizeBundle 返回的对象包含以下属性和方法:

  • messages
    • 一个对象,包含本地化消息的键值对。如果此消息尚未加载,则 messages 的值是空包或者默认的消息,这取决于调用 localizeBundle 的方式。
  • isPlaceholder
    • 一个布尔属性,指出返回的消息是实际的特定区域的消息(false),或者只是等待本地化消息完成加载时使用的占位符(true)。如果本地化消息尚未加载,则要避免渲染部件。
  • format(key: string, replacements: { [key: string]: string })
    • 一个方法,第一个参数是消息的 key,第二个参数是用于替换的值。例如,如果包中包含 greeting: 'Hello, {name}!',则调用 format('greeting', { name: 'World'}) 将返回 'Hello, World!'

以下示例使用了 localizeBundle 返回值的所有功能:

nls/en/MyI18nWidget.ts

export default {
    messages: {
        hello: 'Welcome to the shop',
        purchaseItems: 'Please confirm your purchase',
        itemCount: 'Purchase {count} items'
    }
};

widgets/MyI18nWidget.ts

import WidgetBase from '@dojo/framework/core/WidgetBase';
import { v, w } from '@dojo/framework/core/vdom';
import I18nMixin from '@dojo/framework/core/mixins/I18n';
import Label from '@dojo/widgets/label';
import Button from '@dojo/widgets/button';

import greetingsBundle from '../nls/en/MyI18nWidget';

export default class MyI18nWidget extends I18nMixin(WidgetBase) {
    render() {
        // Load the "greetings" messages for the current locale. If the locale-specific
        // messages have not been loaded yet, then the default messages are returned,
        // and the widget will be invalidated once the locale-specific messages have
        // loaded.
        const { format, isPlaceholder, messages } = this.localizeBundle(greetingsBundle);

        // In many cases it makes sense to postpone rendering until the locale-specific messages have loaded,
        // which can be accomplished by returning early if `isPlaceholder` is `true`.
        if (isPlaceholder) {
            return;
        }

        return v('div', { title: messages.hello }, [
            w(Label, {}, [
                // Passing a message string to a child widget.
                messages.purchaseItems
            ]),
            w(Button, {}, [
                // Passing a formatted message string to a child widget.
                format('itemCount', { count: 2 })
            ])
        ]);
    }
}

为支持 i18n 的部件提供本地化数据

当应用程序使用支持 i18n 的基于类的部件时(专指使用 I18nMixin 的部件),还需要通过 Dojo 注册表管理区域设置的详情。这适用于应用程序内部包含此类部件,或者外部依赖中包含此类部件,包括 Dojo 的 @dojo/widgets 套件中的部件。通过部件的注册系统,将本地化数据注入到所有此类部件中;当应用程序的区域设置变化时,这些部件将会失效并使用更新后的本地化数据重新渲染。

这个机制是通过 @dojo/framework/core/mixins/I18n 提供的工具方法 registerI18nInjector 实现的。调用此方法会将 i18n 注入器注册到指定的 registry 实例中。通常这是在应用程序引导阶段完成的,而 i18n 注入器是通过将全局的 registry 传给 renderer.mount() 方法完成注册的。

main.ts

import renderer from '@dojo/framework/core/vdom';
import { w } from '@dojo/framework/core/vdom';
import Registry from '@dojo/framework/core/Registry';
import { registerI18nInjector } from '@dojo/framework/core/mixins/I18n';

import App from './App';

const registry = new Registry();
registerI18nInjector({ locale: 'us', rtl: false }, registry);

const r = renderer(() => w(App, {}));
r.mount({ registry });

更改区域设置

i18n 中间件可用于更改应用程序的区域设置。调用 i18n.set({ locale: string, rtl: boolean }); 方法会将新的区域设置广播给所有使用 i18n 中间件以及使用 I18nMixin 的部件(前提是先前已在应用程序中设置 registerI18nInjector)。

用法示例

下面的示例展示了一个支持 i18n 的部件,它会渲染两个按钮,用于在英语和法语之间切换应用程序的区域。

import { create, tsx } from '@dojo/framework/core/vdom';
import i18n from '@dojo/framework/core/middleware/i18n';

import nlsBundle from '../nls/main';

const factory = create({ i18n });

export default factory(function LocaleChanger({ middleware: { i18n } }) {
    const { messages } = localize(nlsBundle);
    return (
        <div>
            <button
                onclick={() => {
                    i18n.set({ locale: 'en' });
                }}
            >
                English
            </button>
            <button
                onclick={() => {
                    i18n.set({ locale: 'fr' });
                }}
            >
                French
            </button>
            <div>{messages.greetings}</div>
        </div>
    );
});

覆写每个部件的区域设置和包

使用 i18n 中间件或 I18nMixin 的部件都拥有 i18n 部件属性,可以在父部件中实例化时覆写。当需要在一个应用程序中渲染使用不同区域设置的多个部件时(即在一个应用程序中使用多个区域设置),以及覆写第三方部件正在使用的消息集以确保在应用程序上下文中保持一致时,这是非常有用的功能。

每个支持 i18n 的部件都可以通过设置部件的 locale 属性来拥有自身的区域设置。如果没有设置 locale 属性,则假定使用默认的区域设置

也可以通过传入部件的 i18nBundle 属性来替换部件的默认包。Dojo 建议不要在同一个部件中使用多个包,但有时应用程序需要使用的第三方部件却使用多个包。因此,i18nBundle 也可以是一个 Map 对象,用于关联默认包与覆写包。

在子部件中覆写包的示例:

import { Bundle } from '@dojo/framework/i18n/i18n';

// A complete bundle to replace WidgetA's message bundle
import overrideBundleForWidgetA from './nls/widgetA';

// Bundles for WidgetB
import widgetB1 from 'third-party/nls/widgetB1';
import overrideBundleForWidgetB from './nls/widgetB';

// WidgetB uses multiple bundles, but only `thirdy-party/nls/widgetB1` needs to be overridden
const overrideMapForWidgetB = new Map<Bundle<any>, Bundle<any>>();
map.set(widgetB1, overrideBundleForWidgetB);

export class MyWidget extends WidgetBase {
    protected render() {
        return [
            w(WidgetA, {
                i18nBundle: overrideBundleForWidgetA
            }),
            w(WidgetB, {
                i18nBundle: overrideMapForWidgetB
            })
        ];
    }
}

默认区域设置

支持 i18n 部件的区域设置按照如下顺序确定,直到找到值为止,而这取决于应用程序使用的 i18n 功能:

顺序 I18n 功能 区域设置
1 I18nMixini18n 中间件 使用部件的 locale 属性 显式覆写的区域
2 I18nMixini18n 中间件以及 i18n 注入器 应用程序中选择或更改的区域
3 I18nMixini18n 中间件以及 i18n 注入器 初始注册 i18n 注入器时设置的默认区域
4 .dojorc 用户当前的区域,例如浏览器中的语言设置,同时该区域也存在于应用程序的 build-app.supportedLocales 列表中
5 .dojorc 在应用程序的 build-app.locale 中指定的默认区域
6 @dojo/framework/i18n 通过 Dojo i18n 中的 switchLocale 方法显式设置的区域
7 @dojo/framework/i18n 为当前执行环境设置的 systemLocale