在国际化应用程序中的复数和性别规则

简介

当创建应用程序的时候,开发者们经常会面临这样的情况:他/她必须在单数或复数版本中显示消息的时间,取决于他/她所处理的数字。 例句可能是:"有件夹中是 1 "和"有 4 个文件在此文件夹中"。 常见的解决方案使用间消息,像:"有 4 文件在此文件夹中"。 它是相当不错,但不是适合现代的应用程序。 当我们考虑到其他语言如波兰、 俄罗斯或阿拉伯语,那里有两个以上的规则的时候,情况会变得更加糟糕。 例如,在波兰语中有四个复数形式,这里与数量相关联的词可以表现出许多不同的方式。 幸运的是每个语言的规则都已经进行明确分类。 所有复数的规则的列表可以在这里找到: http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html。 接下来的问题是性别规则,在本文后面讨论。

这篇文章描述当创建 Tizen 应用程序并且想要提供最好的用户体验的时候,如何使用复数和性别规则。 它对Tizen SDK 2.2.0 附带的示例应用程序进行了测试。 这个示例应用程序使用三个库: MessageFormat,Handlebars和 jQuery 2.0.3。

复数的规则

每种语言有其自身的复数形式。 复数的可用的规则名称: 零,一、 二、 几个,多和其他。 每个规则都有它的定义,它可以有所不同语言。 例如,"少"在兰语中作为定义的规则: 任何数字对10取模都是2 至 4,而且它对 100 取模的结果不是 12 和 14 之间。 然而"几个"规则在斯洛伐克语中被定义为: 2 和 4 之间的一个数字。

既然有这些规则的定义,我们可以创建一个函数,获取数值作为参数,并决定我们必须使用哪个复数的规则。 波兰语复数规则函数就像这个样子:

function (n) {
    var int,
        mod10,
        mod100;

    if (n === 1) {
        return 'one';
    }

    int    = n % 1 === 0;
    mod10  = n % 10;
    mod100 = n % 100;

    if (int && ((mod10 >= 2 && mod10 <= 4) && (mod100 < 12 || mod100 > 14))) {
        return 'few';
    }

    if (int && ((mod10 === 0 || mod10 === 1) || (mod10 >= 5 && mod10 <= 9 || mod100 >= 12 && mod100 <= 14))) {
        return 'many';
    }

    return 'other';
};

英语复数规则函数是简单的:

function (n) {
    if (n === 1) {
        return 'one';
    }

    return 'other';
};

既然具有这些复数规则功能,我们应该为每条规则的每种语言准备我们的消息。 它可能看起来像这样:

英语:

  • 一 - "在夹中有 1 个文件"
  • 其他 - "文件夹中有 # 文件"

波兰语:

  • 一 - "W 特姆 folderze玩笑 1 plik"
  • 很少 - "W 特 folderze są # pliki"
  • 很多 - "W 特 folderze 笑话 # plików"
  • 其他 - "W 特 folderze 笑话 # pliku"

在这篇文章后面,我将会描述如何在一个字符串中编写这些不同的消息版本,并且当整个句子只有很少的几个单词不同的时候,避免一遍一遍地去重复。

性别规则

当我们可以有不同的消息版本的时候,另一种情况就是当我们处理性别问题的时候。 让我们将下面的息作为例:“Caroline将个朋友添到她的朋友列表中“,”Thaddeus将两个朋友添加到他的朋友列表中“。

在英语语言中情况是相当简单的。 在此示例中,消息的名称和代词有所不同。 在其他语言中,情况可能更复杂。 例如在波兰语中,一个动词的过去时态会根据性别而变化。

性别规则很简单。 当我们处理男性时,规则是男性。 当我们处理女性时,规则是女性。 当我们不能确定性时,规则是其他。

在这篇文章后面,我将描述如何在一个字符串中为不同的性别编写不同的消息,以及如何根据复数规则来综合一个消息的各种版本。

messageformat.js 库

幸运的是,你不必编写所有复数的规则和它背后的逻辑。 这里也有一个免费的库,它可以做最难的部分,你只需要完成为不同的规则编写不同句子的任务。 你可以在这里找到这个库和它的文档 https://github.com/SlexAxton/messageformat.js

您必须在您的应用程序的文件头部分添加库:

<script type="text/javascript" src="js/messageformat.js"></script>

下面是一个示例字符串,包含我们的消息,考虑了复数和性别规则。 在本节的后面,我将描述它如何构成。 现在,让我们专注于如何使用它。

{NAME} added {FRIENDS_NUM, plural,
                 one {1 friend}
               other {# friends}
             } to {GENDER, select,
                 male {his}
               female {her}
                other {their}
             } friends list

使用它最简单的方法是创建一个 MessageFormat 对象,并将语言背景作为第一个参数传递。 然后我们必须编译我们的消息字符串。 它返回一个函数,它可以用来设置我们的消息的格式。

var message = '{NAME} added {FRIENDS_NUM, plural, one {1 friend} other {# friends}} to {GENDER, select, male {his} female {her} other {their}} friends list';

var mf = new MessageFormat('en');
var compiled = mf.compile(message);

compiled({
    NAME: 'Caroline',
    FRIENDS_NUM: 2,
    GENDER: 'female'
});

我们传递一个对象,包含正确地格式化字符串所需的所有数据 compiled() 函数。 如果其中一个参数省略了,则会抛出异常。

每次需要消息时都要编译它,这不是一个好习惯,所以最好是缓存消息的已编译版本。 你可以在编译时这么做,或者在第一次编译完成后将它保存在缓存中,到后面再重新使用它。

那么,如何撰写这样的消息呢?规则的定义如下所示:

{VARIABLE_NAME, rule_type, options}

VARIABLE_NAME对象的属性名称,它会被传递给已编译的消息函数。 Rule_type 及到您想要使用的规则。 对于复数规则,你须使用复数词,对于性别规则,使用选择的单词。 下一个参是选项列表,取决于所使用的规则。 对于这两个规则,语法都是相同的:

rule_name_1 {rule_message_1} rule_name_2 {rule_message_2} rule_name_3 {rule_message_3}

对于复规则,rule_name 可以是六条复数规则之一:零,一,两个、 很少、 很多,其他。

对于性规则,rule_name 可以是三种性别规则之一: 男性、 女性、 其他。

Rule_message 实际的消息,或者是对应于给定规则的一部分消息。 当为性别规则编写一个特定消息时,你可以使用哈希(#)在句子的合适地方插入数字。 让我们考虑以下这个例子:

You have {NUM, plural,
    one {1 message}
  other {# messages}
}

如果你将1作为值传递给NUM变量,则出将会是:你有 1 条消息。 如果你传递 5,你会得到: 你有 5 条信息。 现在,让我们试着编写带有性别规则的消息:

That's {GENDER, select,
           male {his}
         female {her}
          other {their}
       } picture.

如果你男性作为值传递给GENDER变量,则输出将是: 这就是他的照片。 你很容易可以推断出其他案件的结果。

你也可以通过键入不任何类型或选项的 {VARIABLE_NAME} 来单独插入变量。

{NAME} joined the conversation

现在比如当你Lukas作为NAME变量的值进行传递时,你会得到以下输出:Lukas加入了对话。

如果需要更复杂的行为,你也可以将一种规则嵌套在另一个里面。

{GENDER1, select,
    male {}
  female {}
   other {{NUM, plural,
              one {1}
            other {{GENDER2, select,
                       male {}
                     female {}
                      other {}
                  }}
         }}
}

示例应用程序

示例应用程序演示如何实际地使用 MessageFormat.js 库。 你可以从下面图片的应用程序中看到屏幕截图。

消息格式的屏幕截图

应用程序的屏幕截图

应用程序以三种语言显示一些消息: 英语、 俄语、 波兰语。 它分为两个主要领域。 上面一个是窗体,您可以在这里设置您的姓名和性别。 你还可以定义通知的数量,以及以何种语言来显示消息。

底部区域会显示实际的消息。 它显示多少个应用程序都安装在设备上,有多少联系人都在通讯簿,打了多少个电话,最后一个只是简单地一条消息,它从Notifications计数区域获取数字。

一些要显示的数据需在config.xml文件中具有特殊权限。

<tizen:privilege name="http://tizen.org/privilege/contact.read"/>
<tizen:privilege name="http://tizen.org/privilege/callhistory.read"/>

除了 MessageFormat.js 以外,应用程序使用两个 JavaScript 库。 他们是 jQuery v。 2.0.3Handlebars.js。 Handlebars 库用来显示该模板,将表示层从逻辑层分离出来。 它被描述这篇文章。 jQuery 仅被用于处理事件并做一些基本的 DOM 操作。 MessageFormat.js 库需要多个文件来进行工作。 除了主要的 js/lib/messageformat.js 文件,它需要位于 js/lib/locales/ 目录下的性别规则定义。 对于三种语言有三个文件: 英语 (en.js)、 波兰 (pl.js) 和俄罗斯 (ru.js)。

应用程序逻辑驻留在 js/main.js 文件中。 让我们讨论一下这个文件的一些更重要的部分。 开始时,我们声明默认区域设置 (语言) 和创建的实例 MessageFormat,其以区域设置作为一个参数。 我们也声明并用一个对象填写默认值,这个对象将会被送给模板使用。 我们将在本节的后面部分详细讨论它。

locale = 'pl';
mf = new MessageFormat(locale);
data = {
    name: 'Noname',
    gender: 'male',
    appsCount: 0,
    contactsCount: 0,
    callsCount: 0,
    notificationsCount: 0
};

在这个文件后面,我们有了词典声明 (我已经修整过翻译,要看整部词典请看main.js文件)。 在实际的应用程序中,制作一本词典的最好方法,是将它放在一个外部文件中,在应用程序初始化的时候加载它。 你也可以编译它,并将其数据保存在文件中,以避免运行时编译。

dictionary = {
    en: {
        "Tizen found APPS_COUNT applications.":                      "...",
        "NAME added CONTACTS_COUNT contacts to their address book.": "...",
        "You've made CALLS_COUNT calls.":                            "...",
        "You have NOTIFICATIONS_COUNT notifications.":               "..."
    },
    pl: {
        "Tizen found APPS_COUNT applications.":                      "...",
        "NAME added CONTACTS_COUNT contacts to their address book.": "...",
        "You've made CALLS_COUNT calls.":                            "...",
        "You have NOTIFICATIONS_COUNT notifications.":               "..."
    },
    ru: {
        "Tizen found APPS_COUNT applications.":                      "...",
        "NAME added CONTACTS_COUNT contacts to their address book.": "...",
        "You've made CALLS_COUNT calls.":                            "...",
        "You have NOTIFICATIONS_COUNT notifications.":               "..."
    }
};

在任务对象有三个函数,他们会从设备中获得一些数据: installedApps(), addedContacts(), callsCount(). render() 函数会获得模板数据,进行编译,并用数据填充该模板。 呈现的模板放在 DIV 元素中,其 ID 属性等于"stats"。

render = function () {
    var source;

    if (template === undefined) {
        source = $('#template').html();
        template = Handlebars.compile(source);
    }

    $('#stats').html(template(data));
};

每次表单中的数据发生更改时,该模板会重新呈现。 呈现模板的关键部分是Handlebars helper,称为 i18n,它允许在Handlebars 模板内使用 MessageFormat 库。

Handlebars.registerHelper('i18n', function (text) {
    var options,
        compiledText;

    options  = arguments[arguments.length - 1];

    if (compiled[locale].hasOwnProperty(text)) {
        compiledText = compiled[locale][text];
    } else {
        compiledText = mf.compile(dictionary[locale][text]);
        compiled[locale][text] = compiledText;
    }

    return compiledText(options.hash);
});

这个helper实际上做了什么呢?它以下面的句法介绍了架构:

{{i18n "message_name" VARIABLE1=value1 VARIABLE2=value2}}

从我们的字典获取名称为 message_name 的消息,并将一些变量传递给它。 此helper获取我们的 MessageFormat 对象 (为给定的区域设置),并且如果它之前没有编译过的话,就编译消息。 这个已编译的消息用来显示带格式的文本。

Handlebars.registerHelper('i18n', function (text) {
    var options,
        compiledText;

    options  = arguments[arguments.length - 1];

    if (compiled[locale].hasOwnProperty(text)) {
        compiledText = compiled[locale][text];
    } else {
        compiledText = mf.compile(dictionary[locale][text]);
        compiled[locale][text] = compiledText;
    }

    return compiledText(options.hash);
});

当然你也可以直接使用 MessageFormat 库,但这不是一个好的习惯,这将表示层与逻辑层混合在一起了。

摘要

我希望这篇文章能帮助你了解什么是复数和性别规则。 得益于此,你应该能够创建更好的应用程序,显示更具人性化的消息。

文件附件: