Drupal专业开发指南 第18章 本地化和翻译

老葛的Drupal培训班 Think in Drupal

本地化就是将用户界面的字符串替换为适合用户本地的翻译了的字符串。Drupal的开发和使用都是由一个国际化社区完成的。因此,它默认支持本地化,还对从右向左的语言,比如阿拉伯语、以色列语,提供了主题化支持。在本章中,你将看到如何启用本地化,如何使用用户界面翻译将Drupal内置的字符串替换为你自己的。接着,我们将看一个完整的翻译,并学习如何创建、导入、导出它们。最后,我们将检查一下Drupal在多个语言下显示相同内容的能力(比如,一个加拿大的网站,它使用英语和法语来显示内容),并学习Drupal是如何为页面的显示选择合适的语言的。
 

Drupal版本:

Drupal专业开发指南 第18章 启用本地模块(Locale Module)

 

    本地模块,为Drupal提供了语言处理功能和用户界面翻译,在安装Drupal时并未启用它。这与Drupal的哲理是一致的,那就是仅在需要的时候启用相应的功能。你可以在“管理➤站点构建 ➤模块”中启用本地模块。如果安装Drupal时,选用的语言不是英语,而是其它的一个语言翻译,那么本地模块将作为安装流程的一部分被启用。本章中的例子都假定启用了本地模块。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 用户界面翻译

 

Drupal的界面是由字儿、短语、和句子组成的,用来向用户传递信息。在接下来的部分中,你将看到如何修改它们。我们的例子将集中于字符串替换,同时我们需要理解----翻译就是建立在字符串替换的基础之上的。
 
字符串
    从一个编程的观点来看,一个字符串就是一系列的字符,比如5位字符的字符串Hello。在Drupal中,字符串的翻译构成了用户界面翻译的基础。当Drupal准备一个用来输出的字符串时,它将检查这个字符串是否需要被翻译一下,如果启用了英语,那么将显示“Hello”;如果启用了法语,那么将显示“Bonjour”。让我们看一下这是怎么实现的。
 
使用t()翻译字符串
    在Drupal中显示给终端用户的所有字符串,都须经过t()函数的处理;这个就是Drupal的翻译函数,由于经常使用,为了方便起见,所以将它简写为“t”。
 
注意Drupal中,有些地方隐含的使用了t(),比如传递给watchdog()的字符串,或者菜单钩子中的标题和描述。复数形式使用format_plural()来翻译,该函数负责调用t()(参看http://api.drupal.org/api/function/format_plural/6)。
 
    t()函数中特定于本地的部分,如下所示:
 
function t($string, $args = array(), $langcode = NULL) {
    global $language;
    static $custom_strings;
 
    $langcode = isset($langcode) ? $langcode : $language->language;
 
    // First, check for an array of customized strings. If present, use the array
    // *instead of* database lookups. This is a high performance way to provide a
    // handful of string replacements. See settings.php for examples.
    // Cache the $custom_strings variable to improve performance.
    if (!isset($custom_strings[$langcode])) {
        $custom_strings[$langcode] = variable_get('locale_custom_strings_'.
            $langcode, array());
    }
    // Custom strings work for English too, even if locale module is disabled.
    if (isset($custom_strings[$langcode][$string])) {
        $string = $custom_strings[$langcode][$string];
    }
    // Translate with locale module if enabled.
    elseif (function_exists('locale') && $langcode != 'en') {
        $string = locale($string, $langcode);
    }
    if (empty($args)) {
        return $string;
    }
    ...
}
 
    除了负责翻译以外,t()函数还负责向字符串中的占位符插入相应的值。这些值通常是用户提供的输入,它们在显示以前,必须经过文本转换处理。
 
t('Hello, my name is %name.', array('%name' => 'John');
Hello, my name is John.
 
    文本中待插入的位置是由占位符指示的,而待插入的文本是一个键值数组。这个文本转换处理对Drupal的安全性非常重要(更多信息,可参看第20章)。图18-1向你展示了t()函数是如何处理翻译的;图20-1向你展示了t()函数是如何处理占位符的。
18-1. t()函数是如何处理翻译和占位符插入的,这里假定当前语言为法语。
 
使用自定义字符串替换内置的字符串
    用户界面的翻译,主要就是将一个字符串替换为另一个。让我们从简单的开始,仅仅选择修改几个字符串。对于翻译问题,有多种解决方案。我们将从最简单的入手,然后逐步深入。第一种方案涉及到编辑你的设置文件,而第2种方案则涉及到本地模块。让我们首先在面包屑中实现一个简单的字符串替换,然后再将Blog 替换为Journal
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 在settings.php中覆写字符串

老葛的Drupal培训班 Think in Drupal

找到你的settings.php文件(一般位于sites/default/settings.php)。在进行修改以前,你可能首先需要修改这个文件的权限,将其改为可写的,因为Drupal总是尽可能的将其保持为只读的。滚读到settings.php的结尾处。我们将添加下面的自定义字符串数组:
 
/**
 * String overrides:
 *
 * To override specific strings on your site with or without enabling locale
 * module, add an entry to this list. This functionality allows you to change
 * a small number of your site's default English language interface strings.
 *
 * Remove the leading hash signs to enable.
 */
# $conf['locale_custom_strings_en'] = array(
#  forum' => 'Discussion board',
#  @count min' => '@count minutes',
# );
 
$conf['locale_custom_strings_en'] = array(
    Home' => 'Sweet Home',
);
 
    如果你访问你的站点,你将会注意到,在面包屑中,“Home”(首页)已被修改为了“Sweet Home”(美丽家园),如图18-2所示。
    现在你应该知道如何覆写字符串了,让我们继续前进,把单词“Blog”替换为“Journal”:
 
$conf['locale_custom_strings_en'] = array(
    Blog' => 'Journal',
);
 
    接着,导航到“管理➤站点构建➤模块”,启用博客模块。然后访问“创建内容➤博客条目”,你将看到如图18-3所示的一幕。
 
18-2.面包屑中的字符串“Home”被替换为了“Sweet Home”。
 
18-3.字符串“Blog entry”未被替换为“Journal entry”。
 
    哪里出错了?为什么你自定义的字符串替换数组不起作用呢?这是因为字符串“Blog entry”与字符串“Blog”不完全相同。你不能只挑选子字符串进行替换;你必须匹配整个字符串。
    如何才能找到包含单词“Blog”的所有字符串?这样你就可以使用它的“Journal”等价字符串逐一进行替换了。本地模块可以帮你实现这一点。
 
提示 由于不需要调用数据库,所以在settings.php文件中覆写字符串是很高效的(仅用于小量的字符串集);而字符串的替换只需简单的查找一个数组就可以了。对于字符串的覆写,你不需要为其启用本地模块。另外还有一个第3方的字符串覆写模块,地址为http://drupal.org/project/stringoverrides
 

Drupal版本:

Drupal专业开发指南 第18章 使用本地模块替换字符串

 

我们在前面讲述了,通过在settings.php文件中定义一列自定义的字符串替换,来实现字符串的替换,除了这种方式以外,我们还可以使用本地模块,使用它来查找要被替换的字符串,并定义替换为什么。一个语言翻译就是Drupal的自定义字符串替换集。当Drupal准备显示一个字符串时,它将使用t()函数对字符串进行处理,正如前面所述的那样。如果它在当前语言翻译中找到了一个替换,那么它将使用该替换;如果未找到,那么它将简单的使用原始的字符串。图18-4以一种简单的形式给出了这一流程,这也正是locale()函数的所要做的。使用本地模块的方法,就是创建一个语言代码为en-US的语言,让其仅包含我们想要替换的字符串。
 
18-4.如果本地模块没有在当前语言翻译中找到一个替换,那么它将会退回来使用原始的字符串。
 
    好的,让我们现在开始,将任何包含“blog”的字符串修改为包含“journal”的字符串。因为,如果找不到翻译的话,那么Drupal将会退回来使用原始的字符串,所以我们只需要提供我们想要修改的字符串。我们可以把这些字符串放在一个自定义的语言中,对于我们没有提供的字符串,让Drupal回退到原始的字符串上去就可以了。首先,添加一个自定义语言,来保存我们的自定义字符串。添加语言的界面如图18-5所示。我们把它叫做“English-custom”,并使用“en-US”作为语言代码和路径前缀。
 
18-5.为字符串翻译添加一个自定义语言
 老葛的Drupal培训班 Think in Drupal
    现在,启用你的新语言,并将其设为默认语言,如图18-6所示。点击“保存配置”,取消对英语的选中,并再次点击“保存配置”,如图18-7所示。这样就只启用了一个语言,当用户编辑它们的帐号时,就看不到让人困惑的如图18-8所示的“语言设置”选项了。
18-6. 启用新语言,并将它设为默认语言
 
18-7.禁用英语,这样English-custom就是唯一启用了的语言。
 
18-8. “我的帐号”页面的用户界面,用户可以为站点发送的电子邮件选择一个偏爱语言。
 
    好的,你启用了名为English-custom的单个语言翻译。因为我们还没有添加任何字符串替换,所以它当前还是空的。对于每个字符串,Drupal都将使用如图18-4所示的流程对其进行处理,由于在English-custom中找不到字符串替换,所以最终会回退到原始的英文字符串上。让我们设立一些字符串替换。导航到管理界面“管理➤站点构建➤翻译界面”,如图18-9所示。
18-9. “翻译界面”的概览页面
 
    Drupal使用的是即时翻译。当一个页面被加载时,里面的每个字符串将经过t()函数和locale()函数的处理,在这里,如果发现字符串还没有出现在locales_source和locales_target数据库表中,那么它将被添加到这些表中。所以,在图18-9中“内置界面”一列的值,表示有301个字符串经过了t()的处理并可被翻译。继续前进,在Drupal中随便浏览一些其它页面,然后再回到刚才的页面。你会发现字符串的数量增加了,为什么呢?这是因为Drupal遇到了更多的需要翻译的界面。我们将使用本地模块的web界面来翻译一些字符串。
    在点击了“搜索”标签以后,我们将看到一个搜索界面,它允许我们查找需要翻译的字符串。让我们对目前可用的这301个或者更多的字符串进行搜索。搜索界面如图18-10所示。
 
18-10.用来显示可翻译字符串的搜索界面
 
    选择我们的语言(English-custom),对所有的字符串进行搜索,把搜索框置为空将显示所有的可翻译的字符串。在每个字符串的后面都紧挨着一个“编辑”链接。在字符串列表的下面,也就是页面的底部,将会再次显示搜索界面。由于字符串列表太长了,让我们限定一下它的范围,改为包含单词“Search”的字符串。在“字符串包括”字段中输入单词“Search”,并点击搜索按钮。结果将会是一列包含单词“Search”的字符串,如图18-11所示。现在让我们把字符串“Search”修改为“Search now”,点击“编辑”链接。
 
18-11.一列包含单词“search”的可翻译字符串,以及它们的状态。
 
    当你编辑了这个字符串以后,你返回到了搜索标签页面。但是,等一下!它现在变成了“Search now”标签!而搜索表单底部的按钮,也由“Search”改为了“Search now”,如图18-12所示。实际上,在单词“Search”单独出现的每个地方,它都被替换为了“Search now”。
18-12.字符串“Search”现在被替换为了字符串“Search now”
 
    继续前进,再次搜索字符串“Search”,在字符串的结果列表中,你将会看到,对于该条目,其语言列的删除线不见了,这表示该字符串已经被翻译了,如图18-13所示。
 
18-13.编辑完“Search”后的可翻译字符串列表
 
    注意,这里显示的是原始的字符串。如果你返回到概览标签,你将看到English-custom现在有了一个可用的替换字符串。
    现在你学会了如何修改字符串,我们将继续前进,把所有出现“blog”的地方改为“journal”。在启用了博客模块以后,访问博客相关的页面(比如/node/add/blog和blog/1),我们将看到等待我们翻译的字符串。在“管理➤站点构建➤翻译界面”中的搜索,是区分大小写的,所以我们需要搜索两次,一次“blog”,一次“Blog”,这样就得到所有的情况了;然后使用我们喜欢的单词“journal”和“Journal”,将它们分别修改为等价的替换字符串。
 
警告 我们这里介绍的方法,是用来对Drupal站点进行润色的,它仅针对特定界面元素的字符串替换,而不是全部的。例如,如果一个模块包含了单词“blog”,但是还没有启用,那么我们就没有翻译该模块的字符串。在本章的“开始一个新的翻译”一节中,将会介绍一个更彻底的方法。
 
    该改的都改了,看起来也不错,但是还有一点,那就是创建一个新的journal条目时所用的URL还是http://example.com/?q=node/add/blog,这一点还是有点麻烦的;能不能把它替换为http://example.com/?q=node/add/journal?当然可以。启用路径模块,添加一个别名,把node/add/blog作为已有的系统路径,把node/add/journal作为对应的别名,这样我们就可以快速的解决这个问题了。一转眼!所有的“blog”都消失了,这样在使用站点时,你再也看不到单词“blog”了,这样就顺心了。
 
提示  有个第3方模块,它使得字符串的翻译更加方便,这就是本地化客户端模块,下载地址为http://drupal.org/project/l10n_client。这个模块提供了一个在线的本地化编辑器界面,并且大量的使用了AJAX。
 

Drupal版本:

Drupal专业开发指南 第18章 导出你的翻译

 

在你选择了并翻译了你想要修改的字符串以后,当你设立一个新的Drupal站点时,如果还需要再次选择和翻译,这样不断的重复已有的工作,那么也就太丢人了。通过使用“管理➤站点构建➤翻译界面”中的导出标签,你可以把翻译保存在一个特定的PO文件中(便携式对象文件)。这个文件将包含Drupal传递给t()的所有字符串,还有你已经定义的任何替换字符串。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 便携式对象文件

 

导出我们的English-custom翻译所生成的文件,它的前面几行如下所示:
 
# English-custom translation of Drupal 6
# Copyright (c) 2007 drupalusername <me@example.com>
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"POT-Creation-Date: 2008-05-09 12:46-0500\n"
"PO-Revision-Date: 2008-05-09 12:46-0500\n"
"Last-Translator: drupalusername <me@example.com>\n"
"Language-Team: English-custom <me@example.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
 
#: /example.com/?q=admin/build/translate/search
msgid "Search"
msgstr "Search now"
 
#: /example.com/?q=node/add/blog
msgid "blog"
msgstr "journal"
 
#: /example.com/?q=admin/build/modules/list/confirm
msgid "Blog entry"
msgstr "Journal entry"
 
#: /example.com/?q=admin/build/translate/search
msgid ""
"A <em>blog entry</em> is a single post to an online journal, or "
"<em>blog</em>."
msgstr "A <em>journal entry</em> is a single post to an online journal."
...
 
    .po文件包含两部分,头部为一些元数据,后面跟着翻译了的字符串。每个字符串包含3部分:1个注释,说明了字符串第一次出现的位置;一个msgid,表示原始字符串;一个msgstr,表示翻译了的字符串。关于.po文件格式的更全面的描述,参看http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
    使用“管理➤站点构建➤翻译界面”中的导入标签,现在就可以将en-US.po文件导入到另一个Drupal站点中了(该站点也启用了本地模块)。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 便携式对象模板

 

    一个翻译包含了一些元数据和大量的原始的和已翻译的字符串;而一个便携式对象模板文件(后缀为.pot)则包含了所有的待翻译的原始字符串,而不包含任何翻译了的字符串。.pot文件在一些场合是非常有用的,比如当你从头开始一门语言的翻译时,或者在修改你的站点以前,想判定从最终版本到现在为止,是否向Drupal中添加了一些新字符串时(找出这些字符串的另一种方式是,复制你的Drupal站点,并对其进行更新,搜索未翻译的字符串,如本章“使用自定义字符串替换内置的字符串”一节所讲)。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 开始一个新的翻译

 

Drupal的用户界面已被翻译成了许多语言。如果你想成为一名志愿者来帮助翻译的话,社区是非常欢迎你的。每个已有的语言翻译,都有一个项目页面用来追踪其发展。例如,法语翻译就位于http://drupal.org/project/fr。在翻译的论坛里面可以找到与翻译有关的一般帮助http://drupal.org/forum/30
 
注意 一些专业的翻译者,在使用英语以外的语言时,不会使用本章前面介绍的字符串替换方法。他们习惯使用.po和.pot文件,并通常使用一些特殊的软件来帮助他们管理翻译(参看http://drupal.org/node/11131)。还可参看另一个项目,一个基于web的翻译工具,地址为:http://drupal.org/project/l10n_server
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 用户界面翻译

 

Drupal的界面是由字儿、短语、和句子组成的,用来向用户传递信息。在接下来的部分中,你将看到如何修改它们。我们的例子将集中于字符串替换,同时我们需要理解----翻译就是建立在字符串替换的基础之上的。
 
字符串
    从一个编程的观点来看,一个字符串就是一系列的字符,比如5位字符的字符串Hello。在Drupal中,字符串的翻译构成了用户界面翻译的基础。当Drupal准备一个用来输出的字符串时,它将检查这个字符串是否需要被翻译一下,如果启用了英语,那么将显示“Hello”;如果启用了法语,那么将显示“Bonjour”。让我们看一下这是怎么实现的。
 
使用t()翻译字符串
    在Drupal中显示给终端用户的所有字符串,都须经过t()函数的处理;这个就是Drupal的翻译函数,由于经常使用,为了方便起见,所以将它简写为“t”。
 
注意Drupal中,有些地方隐含的使用了t(),比如传递给watchdog()的字符串,或者菜单钩子中的标题和描述。复数形式使用format_plural()来翻译,该函数负责调用t()(参看http://api.drupal.org/api/function/format_plural/6)。
 
    t()函数中特定于本地的部分,如下所示:
 
function t($string, $args = array(), $langcode = NULL) {
    global $language;
    static $custom_strings;
 
    $langcode = isset($langcode) ? $langcode : $language->language;
 
    // First, check for an array of customized strings. If present, use the array
    // *instead of* database lookups. This is a high performance way to provide a
    // handful of string replacements. See settings.php for examples.
    // Cache the $custom_strings variable to improve performance.
    if (!isset($custom_strings[$langcode])) {
        $custom_strings[$langcode] = variable_get('locale_custom_strings_'.
            $langcode, array());
    }
    // Custom strings work for English too, even if locale module is disabled.
    if (isset($custom_strings[$langcode][$string])) {
        $string = $custom_strings[$langcode][$string];
    }
    // Translate with locale module if enabled.
    elseif (function_exists('locale') && $langcode != 'en') {
        $string = locale($string, $langcode);
    }
    if (empty($args)) {
        return $string;
    }
    ...
}
 
    除了负责翻译以外,t()函数还负责向字符串中的占位符插入相应的值。这些值通常是用户提供的输入,它们在显示以前,必须经过文本转换处理。
 
t('Hello, my name is %name.', array('%name' => 'John');
Hello, my name is John.
 
    文本中待插入的位置是由占位符指示的,而待插入的文本是一个键值数组。这个文本转换处理对Drupal的安全性非常重要(更多信息,可参看第20章)。图18-1向你展示了t()函数是如何处理翻译的;图20-1向你展示了t()函数是如何处理占位符的。
18-1. t()函数是如何处理翻译和占位符插入的,这里假定当前语言为法语。
 
使用自定义字符串替换内置的字符串
    用户界面的翻译,主要就是将一个字符串替换为另一个。让我们从简单的开始,仅仅选择修改几个字符串。对于翻译问题,有多种解决方案。我们将从最简单的入手,然后逐步深入。第一种方案涉及到编辑你的设置文件,而第2种方案则涉及到本地模块。让我们首先在面包屑中实现一个简单的字符串替换,然后再将Blog 替换为Journal
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 获取Drupal的.pot文件

 

Drupal最终的.pot文件,可以在http://drupal.org/project/drupal-pot下载。下载你感兴趣的Drupal分支的.tar.gz文件,并将其进行解压缩,这样你将得到一个目录,里面包含了对应于Drupal文件的.pot文件。例如aggregator-module.pot包含了Drupal的聚合器模块中的可翻译的字符串。
 
$ gunzip drupal-pot-6.x-1.0.tar.gz
$ tar -xf drupal-pot-6.x-1.0.tar
$ ls drupal-pot
LICENSE.txt             modules-dblog.pot       modules-statistics.pot
README.txt              modules-filter.pot      modules-syslog.pot
general.pot             modules-forum.pot       modules-system.pot
includes.pot            modules-help.pot        modules-taxonomy.pot
installer.pot           modules-locale.pot      modules-throttle.pot
misc.pot                modules-menu.pot        modules-tracker.pot
modules-aggregator.pot modules-node.pot        modules-translation.pot
modules-block.pot       modules-openid.pot      modules-trigger.pot
modules-blog.pot        modules-path.pot        modules-update.pot
modules-blogapi.pot     modules-php.pot         modules-upload.pot
modules-book.pot        modules-ping.pot        modules-user.pot
modules-color.pot       modules-poll.pot        themes-chameleon.pot
modules-comment.pot     modules-profile.pot     themes-garland.pot
modules-contact.pot     modules-search.pot      themes-pushbutton.pot
 
    在这个目录中,你还将注意到一些其它文件。有一个用来提供信息的README.txt文件(读它一遍!),一个名为general.pot的文件,一个名为installer.pot的文件。general.pot文件包含了多次重复出现的字符串,翻译时一般从它开始。installer.pot包含的是安装器界面的字符串,如果你需要为安装器创建一个翻译,那么必须翻译该文件所包含的字符串。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 使用翻译模板提取器生成.pot文件

 

3方的翻译模板提取器模块(参看http://drupal.org/project/potx),可以用来为你生成.pot文件。如果你编写了一个自己的模块,或者下载了一个第3方的模块,当需要为其创建一个翻译时,该模块会非常有用。翻译模板提取器模块包含了两个提取器:一个是命令行的版本,一个是基于web的版本。如果你熟悉Unix下的xgettext程序,你可以把这个模块看成是该程序的Drupal版本。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 为你的模块创建一个.pot文件

 

我们在第2章中创建了一个注释模块,现在让我们为该模块生成一个.pot文件。首先,我们需要下载翻译模板提取器模块,下载地址http://drupal.org/project/potx,解压该模块的压缩包,将加解压后的文件放在sites/all/modules/potx。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 使用命令行

 

potx.inc和potx-cli.php拷贝到注释模块的目录sites/all/modules/custom/annotate下。接着,我们需要运行提取器,这样它就可以创建.pot文件了。
 
警告  你向你的Drupal站点添加了一个可执行的PHP脚本,它需要对它运行在的目录具有写权限(这样它就可以写入.pot文件了)。千万不要在一个在线的站点上执行模板提取,你需要把网站拷贝一份,放在你开发用的机器上,然后再执行模板提取。
 
    下面是运行提取器时返回的结果:
 
$ cd sites/all/modules/custom/annotate
$ php potx-cli.php
Processing annotate.admin.inc...
Processing annotate.module...
Processing annotate.install...
Processing annotate.info...
Done.
 
    让我们看一下所生成的文件:
annotate.admin.inc      general.pot
annotate.info           potx-cli.php
annotate.install        potx.inc
annotate.module
 
    运行提取器脚本后将生成一个新的文件general.pot,它包含了来自于annotate.module, annotate.info, 和annotate.install的字符串。该脚本在默认的情况下把所有的字符串都放在了general.pot中,但是,如果你喜欢的话,它也可以生成单独的文件。运行
 
$ php potx-cli.php –-help
 
    来查看提取器脚本提供的各种选项。在当前的这种情况下,把所有的字符串都放在一个文件中,这会比较方便。如果我们需要把这个翻译模板分享给他人,那么我们可以在annotate目录下创建一个translations子目录,把general.pot移到translations目录下,并将其重命名为annotate.pot。接着,我们可以打开这个合成的.pot文件,将其翻译成法语,然后将其保存为fr.po,这样我们模块的目录就变成了这样:
 
annotate.admin.inc
annotate.info
annotate.install
annotate.module
translations/
    annotate.pot
    fr.po
老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 使用基于web的提取器

 

除了前面介绍的命令行方式以外,翻译模板提取器模块还提供了一个基于web的用户界面,你可以使用它来从你的模块中提取字符串。确保你已经下载了该模块,并把它放在了sites/all/modules/potx下,这和前面所述的一样,接下来,导航到“管理➤站点构建➤模块”,启用注释模块和翻译模板提取器模块。接着,导航到“管理➤站点构建➤翻译界面”,你会看到新的提取标签。点击这个标签,在标签页面选择“语言独立模板”,并点击提取按钮,这样你就可以生成一个.pot文件了,如图18-14所示。通过你的web浏览器,将.pot文件下载到本地,接着你可以把它放在sites/all/custom/annotate/translations中,此时又和使用命令行提取器时一样了。
 
18-14.使用翻译模板提取器模块的基于web的用户界面,为注释模块提取一个.pot文件。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 为整个站点创建.pot文件

 

如果你想为你站点中的所有可翻译的字符串创建.pot文件,那么你需要把potx.inc和potx-cli.php文件放在你站点的根部,确保你对当前目录具有写权限,然后运行potx-cli.php。如果你想让生成的.pot文件,与http://drupal.org/project/Translations中.pot文件的布局保持一致,那么在使用命令行运行该脚本时,你需要将mode参数设置为core:
 
$ php potx-cli.php --mode=core
 
    脚本总是把.pot文件输出到它所在的目录中;例如, modules-aggregator.pot文件将会创建在你站点的根目录下,而不是在modules/aggregator/下面。.pot文件的名字反映了其对应模块所在的位置。所以在前面的例子中,将会生成一个sites-all-modules-custom-annotate.pot文件。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 安装一个语言翻译

 你可以在安装Drupal时选择一个英语以外的语言,也可以在安装后添加一个语言翻译。我们将讲解这两种可能。

 
在安装时设立一个翻译
    Drupal的安装器使用st()函数来识别安装器翻译,而不是使用t(),这是因为在安装器运行时,Drupal还没有完成安装,所以此时不能使用t()。安装器翻译是基于installer.pot文件的(参看“获取Drupal的.pot文件”一节),在安装期间,它将作为一个选择出现。
    为了实际的查看安装器的翻译能力,让我们从http://drupal.org/project/Translations下载Drupal的法语翻译。这将得到一个fr-6.x-1.x.tar.gz文件。根据这个文件名的后缀,我们可以看出,它是一个使用GZIP压缩了的.tar文件。提取该文件的一种方式就是使用Unix的tar命令:
 
$ tar -xzvf fr-6.x-1.x.tar.gz
 
警告 该文件包含的目录结构与Drupal的目录结构一致。当提取它时,如果你的提取方法是在提取的同时,完成与Drupal已有目录的合并,那么你需要小心了。在Mac OS X中,默认的提取器无法实现这一点。如果你在提取以后,得到了一个fr-6.x-1.x-dev文件夹,那么这就表示没有进行合并。参看http://www.lullabot.com/videocast/installing-drupal-translation,它使用图文的方式说明了执行提取的正确方式。译者注:分两步就可以了,解压缩该文件,然后拷贝到Drupal目录中。
 
    在成功的提取了翻译以后,在你的Drupal目录中,你会找到许多额外的名为translations的文件夹。例如,在profiles/default文件夹下(Drupal的默认安装轮廓所在的地方),就有了一个translations子文件夹,里面包含了一个fr.po文件。这就是安装器的法语翻译,当Drupal的安装器运行时,你可以看到一个新的选择出现了,如图18-15所示。
    如果你选择了法语,那么将使用法语进行安装,并且站点的默认语言也将被设置为法语。
 
18-15. 当在安装轮廓的translations子目录下存在一个.po文件时,Drupal的安装器将允许你为它选择一个语言。
 

老葛的Drupal培训班 Think in Drupal

 

Drupal版本:

Drupal专业开发指南 第18章 在已有站点上安装一个翻译

 

为了在一个已有的站点上安装一个语言翻译,你需要像前面一节所讲的那样,采用相同的提取步骤。当成功的提取了翻译文件以后(你可以检查translations管理➤站点配置➤语言”,点击“添加语言”标签,你就可以添加语言了。接着,找出对应于语言翻译文件的语言,简单的选择它,并点击“添加语言”按钮,如图18-16所示。如果你已经正确的提取了翻译文件,那么Drupal将会显示一个进度条,表示它正在安装翻译。导航到“管理➤站点配置➤语言”,你就可以看到新装的语言了。子目录是否已经存在了),导航到“
(译者注:这里的“提取”,指的是将语言包中的po文件提取到Drupal站点目录中)
 
18-16.安装一个语言。(在点击添加语言按钮时,必须已经正确提取了翻译文件。)
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 支持从右到左的语言

 

语言的方向性,显示在已添加到Drupal中的语言翻译的列表中,如图18-17所示。
18-17. 从右到左的语言可以用语言表格中的“读写方向”列标识出来。
 
    Drupal对从右到左的语言的支持,比如以色列语,是在主题层实现的。当Drupal为当前页面加载一个样式表时,如果当前语言的读写方向为从右到左,那么Drupal将检查是否存在一个以-rtl.css结尾的对应的样式表。如果该样式表也存在,那么它也将和被请求的样式表一起被加载进来。图18-18给出了这一逻辑。因此,对于支持从右到左的语言的主题,一般把样式定义在主样式表中,而把覆写的CSS定义在对应的从右到左的样式表中。
    例如,如果当前语言为以色列语,主题为Bluemarine,当Drupal添加样式表themes/bluemarine/style.css时,文件themes/bluemarine/style-rtl.css也将被包含进来。在Drupal的默认主题中,检查从右到左的样式表,来看看都有哪些CSS元素被覆写了。
    导航到“管理➤站点配置➤语言”,点击相应语言的“编辑”链接,在编辑页面,你可以修改语言的读写方向。
    如果想测试当前语言的方向性,那么可以使用下面的代码:
 
if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
    // Do something.
}
 
    由于常量LANGUAGE_RTL是由本地模块定义的,所以如果没有加载本地模块,那么也就无法支持从右到左的语言,因此上面的代码是有效的。
18-18.如果当前语言的读写方向为从右到左,如果附加的样式表存在的话,那么它将会被包含进来。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 语言协定

 

Drupal实现了用来判定用户语言的大多数常见方式,这样当一个Drupal站点启用了多个语言时,那么将会使用用户首选的语言。在接下来的部分中,我们将假定安装了Drupal的法语翻译,正如前面所述的那样。导航到“管理➤站点配置➤语言”,点击配置标签,在这里就可以对Drupal判定语言设置的方式进行配置了。相应的用户界面如图18-19所示。让我们检查一下每个选项。
 
18-19.语言协定的可能设置
 
一个也没有
    这是默认选项,也是最简单的一个。在显示页面时,将为所有的用户使用站点的默认语言。指定默认语言的用户界面,可参看图18-17。

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 用户首选语言

 

如果启用了多个语言,当用户编辑他们的“我的帐号”页面时,他们将看到如图18-20所示的字段集。
18-20.为电子邮件消息选择一个特定于用户的语言。
 
    可以按照下面的方式取回用户所选的语言:
 
// Retrieve user 3's preferred language.
$account = user_load(array('uid' => 3));
$language = user_preferred_language($account);
 
    如果用户还没有设置一个首选的语言,那么将会返回站点的默认语言。结果将会是一个语言对象(关于语言对象的更多详细,参看下一节)。如果“语言协定”设置被设置为了“一个也没有”,那么只有在站点发送电子邮件时,才会使用用户的首选语言。在其它情况下,用户的首选语言都不起作用,比如显示页面时就不会使用用户首选的语言。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 全局$language对象

 

通过查看全局变量$language(一个对象),你可以通过编程的方式判定当前的语言。在引导指令的DRUPAL_BOOTSTRAP_LANGUAGE部分,该变量被初始化。通过执行一个var_dump(),你可以查看该变量的详细:
 
global $language;
var_dump($language);
 
结果如下所示:
object(stdClass) (11) {
    ["language"]    => string(2) "fr"
    ["name"]        => string(6) "French"
    ["native"]      => string(9) "Français"
    ["direction"]  => string(1) "0"
    ["enabled"]     => int(1)
    ["plurals"]     => string(1) "2"
    ["formula"]     => string(6) "($n>1)"
    ["domain"]      => string(0) ""
    ["prefix"]      => string(2) "fr"
    ["weight"]      => string(1) "0"
    ["javascript"] => string(0) ""
}
 
    通过$language对象的language属性,可以取回RFC 4646语言标识符(比如,前面例子中的fr):
 
global $language;
$lang = $language->language;
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 仅使用路径前缀

 

当语言协定被设置为了“仅使用路径前缀”时,这里只有两种可能性。要么在路径中找到了语言路径前缀,要么使用默认语言。假定你创建了一个站点,同时能够支持英语和法语。英语是站点的默认语言,但是法语翻译也被安装和启用了。导航到“管理➤站点配置➤语言”,点击法语旁边的“编辑”链接,这样你将看到一个如图18-21所示的用户界面。注意,“路径前缀”字段被设置为了fr。这个值可被修改为任意的字符串。
    由于路径前缀设置为了fr,所以Drupal就可以通过查看被请求的URL来判定当前语言。流程如图18-22所示。
18-21.编辑语言的用户界面,显示了“路径前缀”字段
 
18-22.使用法语的路径前缀来判定语言
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 带有语言回退的路径前缀

 

当把“语言协定”设为这个设置时,Drupal将首先考虑路径前缀。如果一个匹配的也没有,那么将使用$user->language来检查用户首选的语言。如果用户没有选择首选语言,Drupal接着考虑浏览器的HTTP请求中Accept-language HTTP头部,来尝试着判定用户的首选语言。如果浏览器也没有指定一个首选语言,那么将使用站点的默认语言。假定英语是站点的默认语言,同时还启用了法语和以色列语,语言判定的流程如图18-23所示。
18-23.使用“带有语言回退的路径前缀”来判定语言
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 仅使用域名

 

当把“语言协定”设为这个设置时,Drupal将通过尝试对当前URL的域和语言域进行匹配,来判定当前语言,这里的语言域,指的是一个语言的“编辑语言”页面中“语言域”字段所指定的语言域(如图18-21所示)。例如,把英语作为默认语言,把http://fr.example.com指定为法语的语言域,那么当用户访问http://fr.example.com/?q=node/2时,当前语言将被设置为法语;当用户访问http://example.com/?q=node/2时,当前语言将被设置为英语。
 
注意 在“语言协定”被设置为“仅使用域名”时,来自“我的帐号”页面的用户首选语言和客户端浏览器的设置将被忽略。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 内容翻译

 到目前为止,我们所讲的主要是Drupal用户界面的翻译。但是,内容该怎么办呢?一旦判定了当前语言设置,那么用户极有可能想查看该语言下的站点内容!现在,让我们学习一下内容翻译的工作原理。

 
介绍内容翻译模块
    Drupal内置了一种方式,用来管理内容翻译,这就是内容翻译模块。这个模块向Drupal的内容类型添加了附加的多语言支持和翻译管理选项。
 
多语言支持
    导航到“管理➤站点构建➤模块”,启用本地和内容翻译模块,“多语言支持”选项将会出现在每个内容类型的“工作流设置”字段集中。为了查看这些设置,导航到“管理➤内容管理➤内容类型”,点击Page内容类型的“编辑”链接,展开“工作流设置”字段集,这样你将会看到“多语言支持”这一新设置,如图18-24所示。本地模块提供了“已禁用”和“已启用”设置,而内容翻译模块提供了“已启用,带有翻译”设置。
 
18-24.一个内容类型的多语言设置
 
    现在,如果你导航到“创建内容➤Page”,那么你将在内容创建表单上看到一个新的下拉选择框,它允许你选择内容所在的语言,或者内容是“语言中立的”。图18-25给出了该字段。
 
18-25.内容创建表单上的语言选择字段
 
    使用不同的语言创建了一些页面以后,在内容的管理界面“管理➤内容管理➤内容”,你可以看到新增了“语言”一列。另外,还多了一个语言选项,用来过滤内容,如图18-26所示。
 
18-26.启用了多语言支持的内容管理界面

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 带有翻译的多语言支持

 能够使用多个语言创建内容固然不错。然而,大多数的站点,都不会有一篇内容使用英语,另外一篇内容使用法语,两者没有任何关系。相反,法语内容通常是英语内容的一个翻译(或者反之亦然)。当把内容类型的“多语言支持”设为“已启用,带有翻译”(参看图18-24)时,那就变成可能的了。它涉及到以下步骤:

 
1. 使用一个语言创建一篇文章。这就是源文章。
2. 创建文章的翻译。
 
    让我们使用一个例子,来逐步的实现这些任务。首先,确保Page内容类型的“多语言支持”设置已被设为 “已启用,带有翻译”。接着,我们使用英语创建一个简单的页面。导航到“创建内容➤Page”,为标题输入Hello,为正文输入Have a nice day.。将语言设为英语,并点击保存按钮。除了通常的查看和编辑标签以外,你现在应该还会看到一个翻译标签(参看图18-27)。
 
18-27.节点现在有了一个翻译标签。
 
    点击翻译标签,将会显示该文章的翻译状态的总结。如图18-28所示,在英语中已有了一篇源文章,但是现在也只有这些了。通过点击“添加翻译”链接,让我们创建一个法语翻译。
18-28.点击翻译标签,显示了翻译状态的总结。
 
    点击“添加翻译”链接,将再次来到节点编辑表单页面,但是这次,语言被设置为了法语。为标题输入Bonjour,为正文输入Ayez un beau jour.。点击保存按钮,这样就新增了一个节点。Drupal将自动的在源节点和翻译之间创建链接,并使用语言名字作为链接的文本。图18-29给出了源节点的法语翻译的外观,在这里,源节点使用的是英语,同时还有一个使用以色列语的附加翻译。
18-29.源节点的法语翻译包含了指向英语和以色列语版本的链接。
 
    通过实现modules/translation/translation.module中的hook_link(),来构建这些链接:
 
/**
 * Implementation of hook_link().
 *
 * Display translation links with native language names, if this node
 * is part of a translation set.
 */
function translation_link($type, $node = NULL, $teaser = FALSE) {
    $links = array();
    if ($type == 'node' && ($node->tnid) &&
    $translations = translation_node_get_translations($node->tnid)) {
        // Do not show link to the same node.
        unset($translations[$node->language]);
        $languages = language_list();
        foreach ($translations as $language => $translation) {
            $links["node_translation_$language"] = array(
                'title'      => $languages[$language]->native,
                'href'       => "node/$translation->nid",
                'language'  => $languages[$language],
                'attributes' => array(
                    'title' => $translation->title,
                    'class' => 'translation-link'
                )
            );
        }
    }
    return $links;
}
 
    除了内容翻译模块生成的链接以外,本地模块还提供了一个语言转换区块,你可以在“管理➤站点构建➤区块”中启用它。只有当启用了多个语言,并且“语言协定”设置没有被设为“一个也没有”时,语言转换区块才会显示出来。语言转换区块如图18-30所示。
 
18-30. 语言转换区块
 
    让我们回到正题,继续讨论源节点和它们的翻译。如果一个节点是一个源节点,那么编辑它时将会在节点编辑表单中显示一个附加的名为“翻译设置”的字段集。这个字段集包含了单个复选框:“把翻译标记为过时的”,如图18-31所示。
 
18-31.一个源节点的节点编辑表单中的“翻译设置”字段集
 
    复选框是用来指示是否需要重新翻译的,如果你对源节点作了比较大的更改,也就是说原有的翻译已不再适用,那么就需要重新翻译了。选中复选框,将把翻译标记为过时的,这样在查看节点的翻译状态时,就会显示一个红色的提示“过时的”,对比图18-28和图18-32。
 
18-32.源节点已被编辑,而翻译的文章也被标记为过时的。
 
    源节点和它的翻译都有单独的节点ID,事实上,在数据库中它们就是完全独立的节点。它们通过使用node表的tnid列关联起来,该列包含的值,就是源节点的节点ID。假定英语版本就是源节点,并且是站点的第一个节点,而法语和以色列语翻译是紧接着添加的两个节点,那么node表将如图18-33所示。
 
18-33. tnid列用来追踪源节点和它们的翻译之间的关系
 
    注意translate列中的1,用来指示一个过时的翻译。
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 本地化和翻译相关的文件

 有时,想知道是由Drupal中的哪一部分负责某一本地化或翻译功能的,这是非常困难的。表18-1给出了这些文件和它们的职责。

 
18-1. Drupal内部与本地化和翻译相关的文件

 

文件
职责
includes/bootstrap.inc
运行DRUPAL_BOOTSTRAP_LANGUAGE阶段,判定当前语言
includes/language.inc
如果启用了多语言,那么它将被引导指令包含进来。可用来选择语言和将内部URL重写为特定于语言的
includes/common.inc
t()所在的地方,另外还有drupal_add_css(),后者支持从右到左的语言。
includes/locale.inc
包含了用户界面和管理语言翻译的函数。
modules/locale/locale.module
当安装或启用模块/主题时,它用来提供字符串替换和翻译导入。它向路经、节点、节点类型的表单中添加语言设置界面。
modules/translation/translation.module
管理源节点和对应的翻译
modules/translation/translation.admin.inc
当点击翻译标签时,它提供翻译概览页面(参看图18-31)。

 

 
附加资源
    国际化支持对于Drupal项目非常重要。为了追踪国际化的最新进展或者想参与进来,可参看http://groups.drupal.org/i18n
 

老葛的Drupal培训班 Think in Drupal

Drupal版本:

Drupal专业开发指南 第18章 总结

 

在本章中,你学到了以下知识:
    • t()函数的工作原理
    • 如何定制内置的Drupal字符串
    • 如何导出你定制的翻译
    • 什么是便携式对象文件和便携式对象模板文件
    • 如何下载便携式对象模板文件和生成你自己的
    • 如何导入一个已有的Drupal翻译
    • 如何使用样式表来支持从右到左的语言
    • 语言协定设置是如何影响Drupal的
    • 内容翻译的工作原理

老葛的Drupal培训班 Think in Drupal

Drupal版本: