高可用性与脚本国际化:原理、实践与挑战
高可用性与脚本保障
在当今数字化的时代,企业应用的高可用性至关重要。高可用性是一个复杂的领域,通常需要一定的资金投入。如果应用程序的不可用会给企业带来损失,那么投入资金来降低这种风险是合理的。通过投入,我们可以构建一个更完善的系统,它能够监控存储和网络资源,重启应用程序,并且在节点之间进行故障转移,以应对硬件的全面故障。
然而,在实际情况中,我们可能会遇到一些不可靠的应用程序,这些程序需要在一天中的任何时间都保持运行,手动重启既不现实也不高效。这时,一个合适的脚本就能发挥重要作用。它会合理地尝试让应用程序持续运行,并处理可能阻碍其运行的基本问题,比如高可用性脚本本身的故障。而且,这种脚本易于理解和使用,无需进行长达一周的培训就能掌握其核心功能。
脚本国际化的必要性与挑战
脚本国际化常常被认为只能通过过度设计的编程环境中的复杂功能来实现,但事实并非如此。实际上,编写国际化的 shell 脚本并不困难。不过,以编程方式处理人类语言本身就具有挑战性,同时处理多种不同语言则更加复杂。其中有两个关键要点:一是要将 shell 语法与消息字符串分开;二是要注意每一个复数形式的情况。例如,在英语脚本中,“I can see 1 aircraft” 和 “I can see 2 aircraft” 可以正常使用,但在其他语言中,“aircraft” 的复数形式可能会不同。
大多数 shell 脚本是用美式英语编写的,而且绝大多数脚本从未被翻译成其他语言。同样,有些脚本是用本地语言编写的,也从未进行过翻译。在某些情况下,这可能没有问题,比如为单语言员工编写的内部脚本,不需要支持其他语言。但在其他情况下,如果脚本不进行翻译,可能会遇到各种问题。显然,如果用户不理解脚本的内容,就无法使用它。如果脚本能够用用户的母语进行交流,用户对信息的理解会更加清晰。此外,在法律或合同方面,可能也要求支持某些特定的语言。
国际化的技术与概念
在实现脚本国际化的过程中,会涉及到一些关键的技术和概念:
-
国际化(i18n)
:这是将脚本准备好供全球使用的过程,也就是让脚本具有国际通用性。具体来说,需要标记所有需要翻译的字符串,对于使用 gettext 的 shell 脚本,要确保编码标准足够简单,以便 gettext 能够识别字符串中的变量并进行适当的翻译。
-
本地化(L10n)
:这是翻译过程的最后一步,发生在用户的机器上。当脚本执行时,控制权会交给 gettext,由它获取并显示与用户当前语言环境相关的本地化文本。在图形用户界面(GUI)系统中,这意味着菜单、对话框和弹出消息等将以适当的语言显示;对于 shell 脚本,就是文本以正确的语言输出。
-
gettext
:这是实现国际化的核心工具,它可以帮助我们处理字符串的翻译。不过,gettext 有一些特点需要注意,比如它不会像 echo 那样在输出末尾添加换行符,直接用 gettext 替换 echo 语句可能无法得到相同的结果。在这种情况下,根据具体情况,可以在 gettext 后面单独使用 echo 来插入换行符,或者使用更灵活的输出工具,如 printf,这样可以使代码更简洁易读。
-
eval_gettext
:它是 gettext 的扩展,能够计算简单的变量语法,使变量的值可以包含在可翻译的字符串中。但它只能处理简单的变量,如 $a 或 ${a},对于更复杂的结构,如 ${a:-1} 或
pwd
则无法处理。因此,在使用 eval_gettext 时,需要将这些复杂操作放在外部进行,这虽然给开发者增加了一些小负担,但也让非技术翻译人员更容易进行翻译。
-
eval_ngettext
:用于处理复数形式的问题。不同语言对于复数的处理方式不同,有些语言的单数和复数形式相同,而有些语言则有完全不同的语法规则。eval_ngettext 可以处理这种差异,确保脚本在不同语言环境下都能正确显示复数形式。
-
xgettext
:它可以从脚本中提取所有可翻译的字符串,并创建一个 messages.po 文件。这个文件包含一个模板,其中包含脚本中找到的所有字符串,供翻译人员进行翻译。
-
msgfmt
:用于将翻译好的文本文件编译成二进制的 .mo 文件,这些文件可以被 gettext 使用。编译后的 .mo 文件可以放在 $TEXTDOMAINDIR 目录下的特定语言子目录中,根据当前的语言环境设置使用相应的文件。
翻译流程
翻译过程通常分为四个步骤:
1.
国际化
:标记需要翻译的字符串,确保编码标准符合 gettext 的要求。
2.
翻译
:将脚本中已有的字符串翻译成不同的人类语言。如果开发者能够熟练地将脚本翻译成另一种语言,那么测试工作可以快速完成。否则,可能需要花费更多的时间和精力来重写代码以适应 gettext、获取字符串翻译、编译翻译结果并进行测试运行,理想情况下还需要翻译人员进行检查,以确保最终结果符合预期。为了方便测试,一种有效的方法是创建一个虚拟语言,例如 Red Hat Linux 安装程序曾经提供的 “Redneck” 语言,这为北美开发者提供了一种无需精通外语就能进行测试的方式。但需要注意的是,在进行这种跨语言、跨文化的翻译时,要避免出现政治不正确的文本,始终保持尊重。
3.
编译
:使用 msgfmt 工具将翻译好的文本文件编译成二进制的 .mo 文件。这些文件将被放在特定的目录中,以便 gettext 根据当前的语言环境设置使用相应的翻译。
4.
本地化
:在用户的机器上,当脚本执行时,gettext 会根据用户的语言环境自动获取并显示相应的本地化文本。
下面是一个简单的 mermaid 流程图,展示了这个翻译过程:
graph LR
A[国际化] --> B[翻译]
B --> C[编译]
C --> D[本地化]
潜在的陷阱与应对策略
在脚本国际化的过程中,存在一些潜在的陷阱需要我们注意:
1.
脚本更新带来的翻译问题
:随着脚本的不断发展,每次产生新的或不同的输出时,都需要重新审视翻译内容。这可能会对已经完成翻译的语言产生重大影响,一个简单的脚本更改可能会破坏原本 100% 翻译完成的语言,甚至会使部分支持的语言情况变得更糟。在商业项目中,如果支持五种语言,那么可能需要重新聘请五名翻译人员来翻译新文本,与他们协商的时间和成本可能会很高。在社区项目中,也需要同样的努力,但可能会遇到部分翻译人员不再愿意维护脚本的情况,这时就需要寻找新的志愿者,否则该语言的翻译将不完整。如果支持 50 种语言,问题会更加严重。
2.
技术层面的问题
:
-
输出格式差异
:如前面提到的,echo 和 gettext 在输出格式上存在差异,直接替换可能导致结果不一致。可以根据具体情况选择合适的解决方法,如使用 echo 插入换行符或使用 printf 等工具。
-
变量处理限制
:gettext 不了解 shell 可以使用的结构,虽然这对翻译人员来说是好事,但开发者需要注意在使用 eval_gettext 时,只能处理简单的变量,复杂的操作需要放在外部进行。
-
复数形式处理
:不同语言的复数形式规则不同,需要使用 eval_ngettext 来处理这种差异。
为了更直观地展示这些技术的使用,下面给出一个简单的 33 行 shell 脚本示例,它将帮助我们更好地理解脚本国际化的实际操作:
#!/bin/bash
. gettext.sh
export TEXTDOMAIN=mynicescript
cd `dirname $0`
export TEXTDOMAINDIR=`pwd`/po
savedir=`gettext “savedfiles”`
mkdir ~/.$savedir
gettext “Hello, world!”
echo
gettext “Welcome to the script.”
echo
###i18n: Thank you for translating this script!
###i18n: Please leave $RANDOM intact :-)
eval_gettext “Here’s a random number: \$RANDOM”
echo
eval_gettext “Here’s another: \$RANDOM”
echo
echo
for i in 2 4 6
do
ans=`expr $i \* 2`
eval_gettext “Twice \$i is \$ans”
echo
done
这个脚本的主要功能包括创建一个备份目录
~/.$savedfiles
,欢迎用户使用脚本,输出两个随机数,将 2、4 和 6 分别乘以 2 并输出结果,最后声称拥有 1、2 或 3 个孩子。虽然这些功能本身并不复杂,但它足以展示脚本国际化的关键步骤。
接下来,我们将详细介绍如何使用这个脚本进行国际化的具体操作。
脚本国际化的具体操作步骤
提取可翻译字符串
首先,我们使用
xgettext
工具从脚本中提取所有可翻译的字符串,并创建
messages.po
文件。以下是具体的命令:
steve@goldie:~/script$ xgettext --add-comments=’##i18n’ script.sh
执行该命令后,我们可以查看
messages.po
文件的内容:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE’S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid “”
msgstr “”
“Project-Id-Version: PACKAGE VERSION\n”
“Report-Msgid-Bugs-To: \n”
“POT-Creation-Date: 2011-04-06 19:47+0100\n”
“PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n”
“Last-Translator: FULL NAME <EMAIL@ADDRESS>\n”
“Language-Team: LANGUAGE <LL@li.org>\n”
“Language: \n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=CHARSET\n”
“Content-Transfer-Encoding: 8bit\n”
#: script.sh:7
msgid “savedfiles”
msgstr “”
#: script.sh:10
msgid “Hello, world!”
msgstr “”
#: script.sh:12
msgid “Welcome to the script.”
msgstr “”
#. ##i18n: Thank you for translating this script!
#. ##i18n: Please leave $RANDOM intact :-)
#: script.sh:17
#, sh-format
msgid “Here’s a random number: $RANDOM”
msgstr “”
#: script.sh:19
#, sh-format
msgid “Here’s another: $RANDOM”
msgstr “”
#: script.sh:25
#, sh-format
msgid “Twice $i is $ans”
msgstr “”
这个文件包含了一个需要由开发者和翻译人员共同完成的头部信息,以及脚本中所有可翻译字符串的模板。
为不同语言创建翻译文件
接下来,我们为每种需要翻译的语言创建一个
script.po
文件。这里以德语(DE)和法语(FR)为例:
德语翻译
steve@goldie:~/script$ mkdir -p po/de/LC_MESSAGES
steve@goldie:~/script$ cp messages.po po/de/script.po
steve@goldie:~/script$ vi po/de/script.po
编辑后的
po/de/script.po
文件内容如下:
# My Nifty Script.
# Copyright (C) 2011 Steve Parker
# This file is distributed under the same license as the PACKAGE package.
# Steve Parker <steve@steve-parker.org>, 2011
#
#, fuzzy
msgid “”
msgstr “”
“Project-Id-Version: 1.0\n”
“Report-Msgid-Bugs-To: i18n@example.com\n”
“POT-Creation-Date: 2011-04-06 19:47+0100\n”
“PO-Revision-Date: 2011-05-11 12:32+0100\n”
“Last-Translator: FULL NAME <EMAIL@ADDRESS>\n”
“Language-Team: German Translator <de@example.org>\n”
“Language: de\n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=iso-8859-1\n”
“Content-Transfer-Encoding: 8bit\n”
#: script.sh:7
msgid “savedfiles”
msgstr “gespeichertendateien”
#: script.sh:12
msgid “Hello, world!”
msgstr “Hallo Welt!”
#: script.sh:13
msgid “Welcome to the script.”
msgstr “willkommen, um das Skript”
#. ##i18n: Thank you for translating this script!
#. ##i18n: Please leave $RANDOM intact :-)
#: script.sh:16
#, sh-format
msgid “Here’s a random number: $RANDOM”
msgstr “Hier ist eine Zufallszahl: $RANDOM”
#: script.sh:17
#, sh-format
msgid “Here’s another: $RANDOM”
msgstr “Hier ist eine andere: $RANDOM”
#: script.sh:22
#, sh-format
msgid “Twice $i is $ans”
msgstr “zweimal $i ist $ans”
#: script.sh:31
#, sh-format
msgid “I have $i child.”
msgid_plural “I have $i children.”
msgstr[0] “Ich habe $i Kind.”
msgstr[1] “Ich habe $i Kinder.”
然后,使用
msgfmt
工具将德语翻译文件编译成二进制的
.mo
文件:
steve@goldie:~/script$ msgfmt -o po/de/LC_MESSAGES/mynicescript.mo po/de/script.po
法语翻译
steve@goldie:~/script$ mkdir -p po/fr/LC_MESSAGES
steve@goldie:~/script$ cp messages.po po/fr/script.po
steve@goldie:~/script$ vi po/fr/script.po
编辑后的
po/fr/script.po
文件内容如下:
# My Nifty Script.
# Copyright (C) 2011 Steve Parker
# This file is distributed under the same license as the PACKAGE package.
# Steve Parker <steve@steve-parker.org>, 2011
#
#, fuzzy
msgid “”
msgstr “”
“Project-Id-Version: 1.0\n”
“Report-Msgid-Bugs-To: i18n@example.com\n”
“POT-Creation-Date: 2011-04-06 19:47+0100\n”
“PO-Revision-Date: 2011-07-01 16:21+0100\n”
“Last-Translator: FULL NAME <EMAIL@ADDRESS>\n”
“Language-Team: French Translator <fr@example.org>\n”
“Language: fr\n”
“MIME-Version: 1.0\n”
“Content-Type: text/plain; charset=iso-8859-1\n”
“Content-Transfer-Encoding: 8bit\n”
#: script.sh:7
msgid “savedfiles”
msgstr “fichiersenregistrés”
#: script.sh:12
msgid “Hello, world!”
msgstr “Bonjour tout le monde!”
#: script.sh:13
msgid “Welcome to the script.”
msgstr “Bienvenue sur le script.”
#. ##i18n: Thank you for translating this script!
#. ##i18n: Please leave $RANDOM intact :-)
#: script.sh:16
#, sh-format
msgid “Here’s a random number: $RANDOM”
msgstr “voici un nombre aléatoire: $RANDOM”
#: script.sh:17
#, sh-format
msgid “Here’s another: $RANDOM”
msgstr “voici un autre: $RANDOM”
#: script.sh:22
#, sh-format
msgid “Twice $i is $ans”
msgstr “deux fois $i est de $ans”
#: script.sh:31
#, sh-format
msgid “I have $i child.”
msgid_plural “I have $i children.”
msgstr[0] “J’ai $i enfant.”
msgstr[1] “J’ai $i enfants.”
同样,使用
msgfmt
工具将法语翻译文件编译成二进制的
.mo
文件:
steve@goldie:~/script$ msgfmt -o po/fr/LC_MESSAGES/mynicescript.mo po/fr/script.po
总结与注意事项
通过以上步骤,我们完成了脚本的国际化和本地化过程。以下是一个简单的表格总结了这些步骤:
| 步骤 | 操作 | 命令示例 |
| ---- | ---- | ---- |
| 提取字符串 | 使用
xgettext
从脚本中提取可翻译字符串 |
xgettext --add-comments=’##i18n’ script.sh
|
| 创建德语翻译文件 | 复制
messages.po
并编辑 |
cp messages.po po/de/script.po; vi po/de/script.po
|
| 编译德语翻译文件 | 使用
msgfmt
编译成
.mo
文件 |
msgfmt -o po/de/LC_MESSAGES/mynicescript.mo po/de/script.po
|
| 创建法语翻译文件 | 复制
messages.po
并编辑 |
cp messages.po po/fr/script.po; vi po/fr/script.po
|
| 编译法语翻译文件 | 使用
msgfmt
编译成
.mo
文件 |
msgfmt -o po/fr/LC_MESSAGES/mynicescript.mo po/fr/script.po
|
在进行脚本国际化时,我们需要时刻注意前面提到的潜在陷阱,特别是脚本更新对翻译的影响以及技术层面的问题。同时,要选择合适的工具和方法来确保翻译的准确性和完整性。通过遵循这些步骤和注意事项,我们可以让脚本在不同的语言环境下都能正常运行,为全球用户提供更好的使用体验。
脚本国际化的深入探讨与优化建议
文本域的重要性
在脚本国际化过程中,文本域(text domain)起着关键作用。它告诉 gettext 应该使用哪一组翻译。例如,当系统中安装了两个不同的应用程序,它们都对英文短语 “No Change” 有各自的翻译。自动售货机应用程序中,这个短语可能意味着 “如果你没有提供所需的准确金额,将不会得到找零”;而在状态跟踪应用程序中,它可能意味着 “这个事物的状态与上次检查时相同”。文本域确保了在不同的应用场景中能使用到相关的正确翻译。
通常,文本域一般是项目的名称,而单个脚本可能只是项目的一小部分。在我们之前的示例中,文本域为
mynicescript
,脚本名为
script.sh
,这里通过设置不同的名称来突出它们之间的区别,但实际上它们也可以使用相同的名称。
复数形式处理的细节
复数形式的处理是脚本国际化中一个比较复杂但又必须解决的问题。不同语言对于复数的规则差异很大,有些语言的单数和复数形式相同,而有些语言的复数形式变化规则非常复杂。
在我们的示例脚本中,使用了
eval_ngettext
来处理复数形式。例如:
#: script.sh:31
#, sh-format
msgid “I have $i child.”
msgid_plural “I have $i children.”
msgstr[0] “Ich habe $i Kind.”
msgstr[1] “Ich habe $i Kinder.”
在德语翻译中,当数量为 1 时,使用 “Ich habe $i Kind.”;当数量大于 1 时,使用 “Ich habe $i Kinder.”。这体现了
eval_ngettext
能够根据不同的数量情况选择合适的复数形式进行显示。
以下是一个 mermaid 流程图,展示复数形式处理的逻辑:
graph LR
A[判断数量] -->|数量为 1| B[显示单数形式]
A -->|数量大于 1| C[显示复数形式]
代码优化与可维护性
为了提高脚本的可维护性和代码的可读性,我们可以对之前的示例脚本进行一些优化。例如,将一些重复的操作封装成函数,减少代码的冗余。
以下是优化后的脚本示例:
#!/bin/bash
. gettext.sh
export TEXTDOMAIN=mynicescript
cd `dirname $0`
export TEXTDOMAINDIR=`pwd`/po
# 函数:创建备份目录
create_backup_dir() {
savedir=`gettext “savedfiles”`
mkdir ~/.$savedir
}
# 函数:输出欢迎信息
print_welcome() {
gettext “Hello, world!”
echo
gettext “Welcome to the script.”
echo
}
# 函数:输出随机数
print_random_numbers() {
###i18n: Thank you for translating this script!
###i18n: Please leave $RANDOM intact :-)
eval_gettext “Here’s a random number: \$RANDOM”
echo
eval_gettext “Here’s another: \$RANDOM”
echo
}
# 函数:计算并输出倍数结果
print_multiples() {
echo
for i in 2 4 6
do
ans=`expr $i \* 2`
eval_gettext “Twice \$i is \$ans”
echo
done
}
# 主程序
main() {
create_backup_dir
print_welcome
print_random_numbers
print_multiples
}
main
通过这种方式,将不同的功能封装成函数,使得脚本的结构更加清晰,便于后续的维护和扩展。
应对脚本更新的策略
如前面所述,脚本的更新可能会对翻译产生重大影响。为了应对这个问题,我们可以采取以下策略:
1.
版本控制
:使用版本控制系统(如 Git)来管理脚本和翻译文件。这样可以方便地跟踪脚本的更改历史,以及翻译文件的相应更新。当脚本发生变化时,可以通过版本控制工具查看哪些字符串被修改或新增,从而有针对性地进行翻译更新。
2.
自动化工具
:可以编写一些自动化脚本,定期检查脚本的更改,并自动提取新的可翻译字符串,生成新的
messages.po
文件。这样可以减少手动操作的工作量,提高效率。
3.
翻译管理平台
:使用专业的翻译管理平台,如 Transifex 或 POEditor。这些平台可以帮助管理翻译项目,协调翻译人员的工作,并且能够方便地跟踪翻译进度和更新情况。
总结
脚本国际化是一个复杂但又非常重要的过程,它能够让我们的脚本在全球范围内得到更广泛的应用。通过合理使用如 gettext、eval_gettext、eval_ngettext 等工具,遵循国际化和本地化的流程,我们可以实现脚本的多语言支持。
在实际操作中,我们需要注意以下几点:
1. 处理好 shell 语法与消息字符串的分离,以及复数形式的问题。
2. 关注脚本更新对翻译的影响,采取有效的应对策略。
3. 优化代码结构,提高脚本的可维护性和可读性。
以下是一个简单的列表总结脚本国际化的关键要点:
- 明确国际化和本地化的概念和流程。
- 掌握 gettext 相关工具的使用,如 xgettext、msgfmt 等。
- 注意输出格式、变量处理和复数形式的技术细节。
- 采用版本控制、自动化工具和翻译管理平台来应对脚本更新。
通过不断地实践和总结经验,我们可以更好地完成脚本的国际化工作,为全球用户提供更加友好和便捷的使用体验。
超级会员免费看

被折叠的 条评论
为什么被折叠?



