目录

名称

Locale::Maketext - 本地化框架

概要

package MyProgram;
use strict;
use MyProgram::L10N;
 # ...which inherits from Locale::Maketext
my $lh = MyProgram::L10N->get_handle() || die "What language?";
...
# And then any messages your program emits, like:
warn $lh->maketext( "Can't open file [_1]: [_2]\n", $f, $! );
...

说明

应用程序(无论是直接运行还是通过 Web 运行)的一个常见功能是“本地化”——即,它们向英语使用者呈现英语界面,向德语使用者呈现德语界面,依此类推,适用于其编程的所有语言。Locale::Maketext 是一个软件本地化框架;它为您提供了组织和访问文本片段和文本处理代码的工具,这些工具是生成本地化应用程序所必需的。

为了理解 Maketext 及其所有组件如何组合在一起,您可能应该先阅读 Locale::Maketext::TPJ13然后 阅读以下文档。

您可能还想阅读 File::Findgrep 及其组成模块的源代码——它们是一个完整的(尽管很小)示例应用程序,使用 Maketext。

快速概览

Locale::Maketext 的基本设计是面向对象的,Locale::Maketext 是一个抽象基类,您可以从中派生一个“项目类”。项目类(名称类似于“TkBocciBall::Localize”,您随后在模块中使用它)反过来又是项目的所有“语言类”的基类(名称为“TkBocciBall::Localize::it”、“TkBocciBall::Localize::en”、“TkBocciBall::Localize::fr”等)。

语言类是一个包含短语词典作为类数据的类,还可能包含一些在词典中解释短语或以其他方式处理该语言文本时有用的方法。

属于语言类的对象称为“语言句柄”;它通常是一个轻量级对象。

正常操作过程是调用

use TkBocciBall::Localize;  # the localization project class
$lh = TkBocciBall::Localize->get_handle();
 # Depending on the user's locale, etc., this will
 # make a language handle from among the classes available,
 # and any defaults that you declare.
die "Couldn't make a language handle??" unless $lh;

从那时起,使用 maketext 函数访问属于您获得的语言句柄的任何词典中的条目。因此,这

print $lh->maketext("You won!"), "\n";

...会针对此语言发出正确的文本。如果 $lh 中的对象属于类“TkBocciBall::Localize::fr”,并且 %TkBocciBall::Localize::fr::Lexicon 包含 ("You won!" => "Tu as gagné!"),则上述代码会愉快地告诉用户“Tu as gagné!”。

方法

Locale::Maketext 提供各种方法,分为三类

以下部分将介绍这些内容。

构造方法

这些与构造语言句柄有关

“maketext”方法

这是 Locale::Maketext 中最重要的一个方法

$text = $lh->maketext(I<key>, ...parameters for this phrase...);

这会在语言句柄 $lh 及其所有超类的 %Lexicon 中查找,查找键为字符串 key 的条目。假设找到这样一个条目,那么根据找到的值,会发生各种事情

如果值是标量引用,则取消标量引用并返回(并忽略所有参数)。

如果值是代码引用,则返回 &$value($lh, ...parameters...)。

如果值是字符串,并且像方括号表示法中那样,则返回它(在 %Lexicon 中用标量引用替换它之后)。

如果值确实像方括号表示法中那样,则将其编译为子例程,用新代码引用替换 %Lexicon 中的字符串,然后返回 &$new_sub($lh, ...parameters...)。

方括号表示法在后面的部分中讨论。请注意,如果字符串在语法上无效(例如,括号不平衡),则尝试将字符串编译为方括号表示法可能会引发异常。

此外,调用 &$coderef($lh, ...parameters...) 可能会引发任何类型的异常(例如,如果该子例程中的代码尝试除以零)。但是,当您有方括号表示法文本表示调用方法“foo”,但没有此类方法时,会发生非常常见的异常。(例如,“You have [quatn,_1,ball].” 将在尝试调用 $lh->quatn($_[1],'ball') 时引发异常——您可能指的是“quant”。)maketext 会捕获这些异常,但仅为了使错误消息更易于阅读,此时它会重新引发异常。

如果在 $lh 的任何 %Lexicon 哈希中找不到key可能会引发异常。如果找不到键,将在后面的部分“控制查找失败”中讨论。

请注意,在某些情况下,如果您要转换编码,甚至脚本,则可以使用“after 方法”覆盖 maketext 方法。

package YrProj::zh_cn; # Chinese with PRC-style glyphs
use base ('YrProj::zh_tw');  # Taiwan-style
sub maketext {
  my $self = shift(@_);
  my $value = $self->maketext(@_);
  return Chineeze::taiwan2mainland($value);
}

或者,如果您要覆盖它以捕获任何异常,如果这对您的程序至关重要,您可能需要使用它。

sub maketext {
  my($lh, @stuff) = @_;
  my $out;
  eval { $out = $lh->SUPER::maketext(@stuff) };
  return $out unless $@;
  ...otherwise deal with the exception...
}

除了这两种情况之外,我认为覆盖 maketext 方法没有用。(如果您遇到它有用的情况,我很有兴趣了解它。)

$lh->fail_with $lh->fail_with(PARAM)
$lh->failure_handler_auto

这两个方法在“控制查找失败”部分中讨论。

$lh->denylist(@list) <or> $lh->blacklist(@list)
$lh->allowlist(@list) <or> $lh->whitelist(@list)

这些方法在“方括号表示法安全性”部分中进行了讨论。

实用方法

这些方法您可能觉得很方便,通常来自您自己的 %Lexicon 例程(无论是否表示为方括号表示法)。

$language->quant($number, $singular)
$language->quant($number, $singular, $plural)
$language->quant($number, $singular, $plural, $negative)

通常,这表示从方括号表示法(稍后讨论)内部进行调用,如下所示

"Your search matched [quant,_1,document]!"

它用于量化名词(即,说明其数量,同时给出其正确形式)。此方法的行为对于英语和一些其他西欧语言来说很方便,对于不适合的语言,您应该覆盖它。您可以随意阅读源代码,但当前实现基本上与此伪代码描述的一样

if $number is 0 and there's a $negative,
   return $negative;
elsif $number is 1,
   return "1 $singular";
elsif there's a $plural,
   return "$number $plural";
else
   return "$number " . $singular . "s";
#
# ...except that we actually call numf to
#  stringify $number before returning it.

因此,对于英语(使用方括号表示法),"...[quant,_1,file]..." 是正确的(对于 0,它返回“0 个文件”,对于 1,它返回“1 个文件”,对于更多,它返回“2 个文件”,依此类推)。

但是对于“目录”,您需要 "[quant,_1,directory,directories]",以便我们基本的 quant 方法不会认为“目录”的复数形式是“directorys”。如果您指定否定形式,您可能会发现输出听起来更好,如下所示

"[quant,_1,file,files,No files] matched your query.\n"

请记住考虑动词一致性(或其他语言中的形容词),如下所示

"[quant,_1,document] were matched.\n"

因为如果 _1 为一,则会得到“匹配了 1 个文档”。这里的一个可接受的技巧是执行类似这样的操作

"[quant,_1,document was, documents were] matched.\n"
$language->numf($number)

这会根据此语言的惯例以漂亮的方式返回给定的数字。Maketext 的默认方法主要是只采用数字的普通字符串形式(仅对非常大的数字应用 sprintf "%G"),然后根据需要添加逗号。(但如果 $language->{'numf_comma'} 为 true,我们会应用 tr/,./.,/;这是一个有点技巧的方法,适用于将 200 万表示为“2.000.000”而不是“2,000,000”的语言)。

如果您想要更花哨的东西,请考虑使用 Number::Format 或完全做其他事情来覆盖它。

请注意,numf 由 quant 调用,用于将所有量化数字字符串化。

$language->numerate($number, $singular, $plural, $negative)

这将返回给定名词形式,该形式根据此语言的惯例适用于数量 $numbernumerate 在内部由 quant 用于量化名词。直接使用它——通常从方括号表示法中——以避免 quantnumf 的隐式调用和数字量的输出。

$language->sprintf($format, @items)

这只是 Perl 的普通 sprintf 函数的包装器。提供它以便您可以在方括号表示法中使用“sprintf”

"Couldn't access datanode [sprintf,%10x=~[%s~],_1,_2]!\n"

返回...

Couldn't access datanode      Stuff=[thangamabob]!
$language->language_tag()

目前,这只是获取 ref($language) 的最后部分,将下划线转换为破折号,并返回它。因此,如果 $language 是 Hee::HOO::Haw::en_us 类的对象,则 $language->language_tag() 返回“en-us”。(是的,该语言标记的通常表示形式为“en-US”,但大小写在语言标记比较中永远不会被视为有意义。)

您可以根据需要覆盖此项;Maketext 不会将其用于任何用途。

$language->encoding()

目前,这没有任何用途,但提供它(默认值为 (ref($language) && $language->{'encoding'})) 或 "iso-8859-1" )作为一种建议,即它可能对将编码与您的语言句柄(无论是按类还是按句柄)相关联很有用/必要。

语言句柄属性和内部结构

语言句柄是一个轻量级对象——即它(不一定)携带任何感兴趣的数据,除了仅仅是它所属的任何类的成员之外。

语言句柄实现为一个受祝福的哈希。您的子类可以在哈希中存储您想要的任何数据。目前,任何关键的 Maketext 方法使用的唯一哈希条目是“fail”,因此您可以随意使用其他任何内容。

记住:如果有任何点让本文件难以理解,请不要害怕阅读 Maketext 源代码。本文件比模块源代码本身长得多。

语言类层次结构

以下是 Locale::Maketext 对由所有语言类形成的类层次结构的假设

每个词典中的条目

典型的 %Lexicon 条目旨在表示一个短语,采用一些数量(0 个或更多)的参数。一个条目旨在通过 $lh->maketext(key, ...parameters...) 中的字符串进行访问,它应该返回一个通常用于对用户进行“输出”的字符串——无论这实际上意味着打印到 STDOUT、写入文件还是放入 GUI 小组件中。

虽然键必须是字符串值(因为这是 Perl 对哈希键施加的基本限制),但词典中的值当前可以是多种类型:已定义标量、标量引用或代码引用。这些用法在““maketext”方法”部分中进行了说明,下一部分将讨论字符串的方括号表示法。

虽然你可以为词典键使用任意唯一 ID(如“_min_larger_max_error”),但通常情况下,如果一个词条的键本身是一个有效值,则很有用,就像此示例错误消息一样

"Minimum ([_1]) is larger than maximum ([_2])!\n",

将此使用任意 ID 的代码与...

die $lh->maketext( "_min_larger_max_error", $min, $max )
 if $min > $max;

...使用键作为值的代码进行比较

die $lh->maketext(
 "Minimum ([_1]) is larger than maximum ([_2])!\n",
 $min, $max
) if $min > $max;

简而言之,第二个更易读。特别是,很明显你提供给该短语的参数数量(两个)是它希望提供的参数数量。(因为你在键中看到了 _1 和 _2。)

此外,一旦项目完成,你开始对其进行本地化,你可以将使用的所有不同键收集在一起,并将其传递给翻译人员;然后,如果向翻译人员展示的内容如下,他们的工作将进行得更快

"Minimum ([_1]) is larger than maximum ([_2])!\n",
 => "",   # fill in something here, Jacques!

而不是这种更晦涩的混乱

"_min_larger_max_error"
 => "",   # fill in something here, Jacques

我认为将键作为词典值可以使完成的词典条目更具可读性

"Minimum ([_1]) is larger than maximum ([_2])!\n",
 => "Le minimum ([_1]) est plus grand que le maximum ([_2])!\n",

此外,如果你设置了 _AUTO 词典,则拥有作为键的有效值将非常有用。_AUTO 词典将在后面的部分中讨论。

我几乎总是使用本身是有效词典值的键。一个值得注意的例外是当值很长时。例如,为了获取命令行程序在给定未知开关时可能返回的屏幕数据,我通常只使用一个简短的自解释键,例如“_USAGE_MESSAGE”。然后,我立即转到 ProjectClass::L10N::en 词典中定义该词典条目(因为英语始终是我的“项目语言”)

'_USAGE_MESSAGE' => <<'EOSTUFF',
...long long message...
EOSTUFF

然后我可以用它作为

getopt('oDI', \%opts) or die $lh->maketext('_USAGE_MESSAGE');

顺便说一下,请注意每个类的 %Lexicon 都会继承并扩展其超类中的词典。这并不是因为这些是特殊哈希本身,而是因为你通过 maketext 方法访问它们,该方法会在语言类其所有祖先类中的所有 %Lexicon 哈希中查找条目。(这是因为“类数据”的概念并未在 Perl 中直接实现,而是留给各个类系统根据需要进行实现。)

请注意,除了输出短语之外,你还可以将内容存储在词典中:例如,如果你的程序从键盘获取输入,询问一个 “(Y/N)” 问题,你可能需要知道无论使用什么语言,“Y[es]/N[o]” 的对应内容是什么。你可能还需要知道答案“y”和“n”的对应内容是什么。你可以将该信息存储在词典中(例如,以键“~answer_y”和“~answer_n”存储,长格式为“~answer_yes”和“~answer_no”,其中“~”只是一个临时字符,旨在向程序员/翻译人员表明这些不是输出短语)。

或者,你可以将此内容存储在语言类的词典中,也可以(在某些情况下,确实应该)将同一知识表示为语言类中方法中的代码。(这在词典(我们知道如何的内容)和词典类中的其他内容(我们知道如何的内容)之间留下了明确的区别。)考虑这个法语“oui/non”问题响应处理器的示例

sub y_or_n {
  return undef unless defined $_[1] and length $_[1];
  my $answer = lc $_[1];  # smash case
  return 1 if $answer eq 'o' or $answer eq 'oui';
  return 0 if $answer eq 'n' or $answer eq 'non';
  return undef;
}

...然后,你可以在类似这样的结构中调用它

my $response;
until(defined $response) {
  print $lh->maketext("Open the pod bay door (y/n)? ");
  $response = $lh->y_or_n( get_input_from_keyboard_somehow() );
}
if($response) { $pod_bay_door->open()         }
else          { $pod_bay_door->leave_closed() }

其他值得存储在词典中的数据可能是语言目标资源的文件名

...
"_main_splash_png"
  => "/styles/en_us/main_splash.png",
"_main_splash_imagemap"
  => "/styles/en_us/main_splash.incl",
"_general_graphics_path"
  => "/styles/en_us/",
"_alert_sound"
  => "/styles/en_us/hey_there.wav",
"_forward_icon"
 => "left_arrow.png",
"_backward_icon"
 => "right_arrow.png",
# In some other languages, left equals
#  BACKwards, and right is FOREwards.
...

你可能希望对表达键绑定或类似内容执行相同操作(因为将“q”硬编码为退出屏幕/菜单/程序的功能的绑定仅在你碰巧将“q”与“quit”关联的语言中才有用!)

方括号表示法

方括号表示法是 Locale::Maketext 的一项关键功能。我的意思是方括号表示法可以替代 sprintf 格式化。你可以使用方括号表示法完成的所有操作都可以使用子块完成,但方括号表示法旨在更加简洁。

方括号表示法就像一个微型的“模板”系统(从 Text::Template 的意义上讲,而不是 C++ 模板的意义上讲),其中普通文本基本按原样传递,但特殊区域中的文本经过特殊解释。在方括号表示法中,你使用方括号 (“[...]”),而不是花括号 (“{...}”) 来标记经过特殊解释的部分。

例如,这里所有按字面意思理解的区域都用“^”下划线标注,所有方括号中的特殊区域都用 X 下划线标注

"Minimum ([_1]) is larger than maximum ([_2])!\n",
 ^^^^^^^^^ XX ^^^^^^^^^^^^^^^^^^^^^^^^^^ XX ^^^^

当该字符串从方括号表示法编译成真正的 Perl 子程序时,它基本上会变成

sub {
  my $lh = $_[0];
  my @params = @_;
  return join '',
    "Minimum (",
    ...some code here...
    ") is larger than maximum (",
    ...some code here...
    ")!\n",
}
# to be called by $lh->maketext(KEY, params...)

换句话说,方括号组之外的文本会变成字符串文字。方括号中的文本更为复杂,目前遵循以下规则

整个组的解释如下

顺便提一下,每个组中的项目都是用逗号分隔的,而不是用 /\s*,\s*/ 分隔的。也就是说,您可能会认为此括号组

"Hoohah [foo, _1 , bar ,baz]!"

将编译为此

sub {
  my $lh = $_[0];
  return join '',
    "Hoohah ",
    $lh->foo( $_[1], "bar", "baz"),
    "!",
}

但它实际上编译为此

sub {
  my $lh = $_[0];
  return join '',
    "Hoohah ",
    $lh->foo(" _1 ", " bar ", "baz"),  # note the <space> in " bar "
    "!",
}

在迄今为止讨论的符号中,字符“【”和“】”被赋予特殊含义,用于打开和关闭括号组,而“,”在括号组内具有特殊含义,用于分隔组中的项目。这引发了一个问题,即如何在括号符号字符串中表示一个字面上的“【”或“】”,以及如何在括号组内表示一个字面上的逗号。为此,我采用了“~”(波浪号)作为转义字符:“~【”表示括号符号中的任何位置的字面字符“【”(即,无论你是否在括号组中),而“~】”表示字面字符“】”,“~, ”表示字面逗号。(虽然“,”在括号组外表示字面逗号——只有在括号组内逗号才具有特殊含义。)

并且,如果你需要在括号表达式中使用字面波浪号,你可以使用“~~”来实现。

当前,在括号或逗号以外的字符之前的未转义的“~”表示仅为“~”和该字符。即,“~X”与“~~X”表示相同的内容——即一个字面波浪号,然后是一个字面“X”。然而,通过使用“~X”,你假设 Maketext 的任何未来版本都不会将“~X”用作魔术转义序列。在实践中,这不是一个大问题,因为首先你可以只写“~~X”而不必担心它;其次,我怀疑我会在括号符号中添加许多新的魔术字符;第三,你不太可能希望在你的消息中使用字面“~”字符,因为它不是自然语言文本中广泛使用的字符。

括号必须平衡——每个左括号必须有一个匹配的右括号,反之亦然。因此,以下所有内容都是无效的

"I ate [quant,_1,rhubarb pie."
"I ate [quant,_1,rhubarb pie[."
"I ate quant,_1,rhubarb pie]."
"I ate quant,_1,rhubarb pie[."

当前,括号组不会嵌套。也就是说,你不能

"Foo [bar,baz,[quux,quuux]]\n";

如果你需要功能如此强大的符号,请使用常规 Perl

%Lexicon = (
  ...
  "some_key" => sub {
    my $lh = $_[0];
    join '',
      "Foo ",
      $lh->bar('baz', $lh->quux('quuux')),
      "\n",
  },
  ...
);

或者编写“bar”方法,这样你就不需要将 quux 调用输出传递给它。

我不认为你需要(或特别希望)嵌套括号组,但欢迎你通过电子邮件向我发送令人信服的(现实生活中的)反驳论点。

括号符号安全性

Locale::Maketext 不使用任何特殊语法来区分括号符号方法与常规类或对象方法。此设计使其在用于处理不受信任用户提供的字符串时容易受到格式字符串攻击。

Locale::Maketext 支持拒绝列表和允许列表功能,以限制哪些方法可以作为方括号表示法方法调用。

默认情况下,Locale::Maketext 拒绝 Locale::Maketext 命名空间中所有以 '_' 字符开头的所有方法,以及所有包含 Perl 命名空间分隔符字符的方法。

Locale::Maketext 的默认拒绝列表还阻止在方括号表示法中使用以下方法

denylist
encoding
fail_with
failure_handler_auto
fallback_language_classes
fallback_languages
get_handle
init
language_tag
maketext
new
allowlist
whitelist
blacklist

此列表可以通过拒绝列出其他“已知不良”方法或仅允许列出“已知良好”方法来扩展。

若要防止在方括号表示法中调用特定方法,请使用 denylist() 方法

my $lh = MyProgram::L10N->get_handle();
$lh->denylist(qw{my_internal_method my_other_method});
$lh->maketext('[my_internal_method]'); # dies

若要将允许的方括号表示法方法限制为特定列表,请使用 allowlist() 方法

my $lh = MyProgram::L10N->get_handle();
$lh->allowlist('numerate', 'numf');
$lh->maketext('[_1] [numerate, _1,shoe,shoes]', 12); # works
$lh->maketext('[my_internal_method]'); # dies

denylist() 和 allowlist() 方法在每次调用时都会扩展其内部列表。若要重置拒绝列表或允许列表,请创建一个新的 maketext 对象。

my $lh = MyProgram::L10N->get_handle();
$lh->denylist('numerate');
$lh->denylist('numf');
$lh->maketext('[_1] [numerate,_1,shoe,shoes]', 12); # dies

对于使用内部缓存的词典,已在其编译形式中缓存的翻译不受随后对允许列表或拒绝列表设置的更改的影响。使用外部缓存的词典将在允许列表或拒绝列表设置更改时清除其缓存。两种缓存类型之间的差异在“只读词典”部分中进行了说明。

拒绝列表不允许的方法不能由允许列表允许。

注意:denylist() 是首选方法名称,而不是历史且不具包容性的方法 blacklist()。blacklist() 可能会在此软件包的未来版本中删除,因此应从用法中删除其用法。

注意:allowlist() 是首选方法名称,而不是历史且不具包容性的方法 whitelist()。whitelist() 可能会在此软件包的未来版本中删除,因此应从用法中删除其用法。

自动词典

如果 maketext 进入单个 %Lexicon 中查找 key 的条目(其中 key 不以下划线开头),并且没有看到,但确实看到 "_AUTO" => some_true_value 的条目,那么我们实际上会立即定义 $Lexicon{key} = key,然后使用该值,就好像它一直存在一样。这发生在我们甚至在任何超类 %Lexicons 中查找之前!

(这在某种程度上类似于 Perl 函数调用系统中的 AUTOLOAD 机制——或者换个角度看,类似于 AutoLoader 模块。)

我可以想象各种情况下,你根本不想让查找失败(因为失败通常意味着 maketext 会抛出一个 die,不过请参阅下一节以获得对它的更大控制)。但以下情况是 _AUTO 词典特别有用的

在编写应用程序时,你可以随时决定需要发出哪些消息。通常你会写下以下内容

if(-e $filename) {
  go_process_file($filename)
} else {
  print qq{Couldn't find file "$filename"!\n};
}

但由于你预期本地化此内容,所以你写下

use ThisProject::I18N;
my $lh = ThisProject::I18N->get_handle();
 # For the moment, assume that things are set up so
 # that we load class ThisProject::I18N::en
 # and that that's the class that $lh belongs to.
...
if(-e $filename) {
  go_process_file($filename)
} else {
  print $lh->maketext(
    qq{Couldn't find file "[_1]"!\n}, $filename
  );
}

现在,在你刚刚写完上述行之后,你通常必须打开文件 ThisProject/I18N/en.pm,并立即添加一个条目

"Couldn't find file \"[_1]\"!\n"
=> "Couldn't find file \"[_1]\"!\n",

但我认为这在某种程度上分散了让主代码正常工作的注意力——更不用说,在决定消息中想要确切的措辞之前,我经常必须多次使用该程序(在这种情况下,我需要更改三行代码:使用该键调用 maketext,然后是 ThisProject/I18N/en.pm 中的两行)。

但是,如果你在 ThisProject/I18N/en.pm 中的 %Lexicon 中设置 "_AUTO => 1"(假设英语 (en) 是所有程序员将用于此项目的内部消息键的语言),那么你永远不必添加类似于以下内容的行

"Couldn't find file \"[_1]\"!\n"
=> "Couldn't find file \"[_1]\"!\n",

到 ThisProject/I18N/en.pm,因为如果 _AUTO 为真,那么只需在该词典中查找键为 "Couldn't find file \"[_1]\"!\n" 的条目,就会导致添加该条目,并带有该值!

请注意,以 "_" 开头的键不受 _AUTO 影响的原因并不是下划线字符有什么神奇之处——我只是想找到一种方法让大多数词典键都可以自动处理,除了可能少数几个键,我武断地决定使用前导下划线作为区分这几个键的信号。

只读词典

如果你的词典是关联哈希,那么缓存已编译值的行为本身可能是致命的。

例如,GDBM_File GDBM_READER 关联哈希将以类似于以下内容的方式死亡

gdbm store returned -1, errno 2, key "..." at ...

你需要做的就是像这样在词典哈希外部启用缓存

sub init {
    my ($lh) = @_;
    ...
    $lh->{'use_external_lex_cache'} = 1;
    ...
}

然后,它不会将编译后的值存储在词典哈希中,而是将它存储在 $lh->{'_external_lex_cache'} 中

控制查找失败

如果您调用 $lh->maketext(key, ...parameters...),并且在 $lh 的类的 %Lexicon 中或在超类的 %Lexicon 哈希中没有条目 key并且如果我们无法自动生成 key(因为它以 "_" 开头,或者因为它的词典中没有 _AUTO => 1,),那么我们无法找到一个正常的方式来生成文本 key。在这种失败条件下会发生什么,取决于 $lh 对象的 "fail" 属性。

如果语言句柄没有 "fail" 属性,maketext 将简单地抛出一个异常(即,它调用 die,提到查找失败的 key,并命名调用 $lh->maketext(key,...) 的行号。

如果语言句柄有一个 "fail" 属性,其值为一个代码引用,那么 $lh->maketext(key,...params...) 将放弃并调用

return $that_subref->($lh, $key, @params);

否则,"fail" 属性的值应为表示方法名称的字符串,以便 $lh->maketext(key,...params...) 可以放弃

return $lh->$that_method_name($phrase, @params);

可以使用 fail_with 方法访问 "fail" 属性

# Set to a coderef:
$lh->fail_with( \&failure_handler );

# Set to a method name:
$lh->fail_with( 'failure_method' );

# Set to nothing (i.e., so failure throws a plain exception)
$lh->fail_with( undef );

# Get the current value
$handler = $lh->fail_with();

现在,至于您可能希望如何处理这些处理程序:也许您希望记录哪个键对哪个类失败,然后终止。也许您不喜欢 die,而是希望将错误消息发送到 STDOUT(或其他任何地方),然后仅仅 exit()

或者也许您根本不想 die!也许您可以使用这样的处理程序

# Make all lookups fall back onto an English value,
#  but only after we log it for later fingerpointing.
my $lh_backup = ThisProject->get_handle('en');
open(LEX_FAIL_LOG, ">>wherever/lex.log") || die "GNAARGH $!";
sub lex_fail {
  my($failing_lh, $key, $params) = @_;
  print LEX_FAIL_LOG scalar(localtime), "\t",
     ref($failing_lh), "\t", $key, "\n";
  return $lh_backup->maketext($key,@params);
}

一些用户表示,他们认为拥有 "fail" 属性的整个机制似乎是一个相当没有意义的复杂性。但我希望 Locale::Maketext 可用于任何规模和类型的软件项目;不同的软件项目对在失败条件下做什么才是正确的事情有不同的想法。我可以简单地说,失败总是会抛出一个异常,如果您想要小心,您只需要将对 $lh->maketext 的每个调用包装在 eval { } 中。但是,我希望程序员保留权利(通过 "fail" 属性)将查找失败视为与配置文件不可读或某些基本资源不可访问的严重程度不同的异常。

“fail” 属性的一个可能值是方法名 “failure_handler_auto”。这是 Locale::Maketext 类中定义的一个方法。您可以使用以下方法设置它

$lh->fail_with('failure_handler_auto');

然后,当您调用 $lh->maketext(key, ...parameters...) 并且在这些词典中没有 key 时,maketext 会放弃

return $lh->failure_handler_auto($key, @params);

但是 failure_handler_auto 不会终止或执行任何操作,而是编译 $key,并将其缓存到

$lh->{'failure_lex'}{$key} = $compiled

然后调用已编译的值,并返回该值。(即,如果 $key 看起来像方括号表示法,则 $compiled 是一个子项,并且我们返回 &{$compiled}(@params);但如果 $key 只是一个普通字符串,我们只返回它。)

使用 “failure_auto_handler” 的效果就像一个 AUTO 词典,除了 1) 它会编译 $key,即使它以 “_” 开头,2) 您可以在新的 hashref $lh->{'failure_lex'} 中记录此对象失败的所有键。这应避免您的程序终止 -- 只要您的键实际上不是方括号代码无效,并且只要它们不尝试调用不存在的方法即可。

“failure_auto_handler” 可能不是您想要的,但我希望它至少向您展示了 maketext 失败可以通过多种非常灵活的方式来缓解。如果您能正式化您想要的内容,您应该能够将其表示为失败处理程序。您甚至可以通过在给定类的 init 中设置它,使其成为该类的每个对象的默认值

sub init {
  my $lh = $_[0];  # a newborn handle
  $lh->SUPER::init();
  $lh->fail_with('my_clever_failure_handler');
  return;
}
sub my_clever_failure_handler {
  ...you clever things here...
}

如何使用 MAKETEXT

以下是有关如何使用 Maketext 对应用程序进行本地化的简要清单

另请参阅

我建议阅读所有这些内容

Locale::Maketext::TPJ13——我在《Perl Journal》上关于 Maketext 的文章。它解释了 Locale::Maketext 设计背后的许多重要概念,以及一些关于为什么 Maketext 比仅是 sprintf 格式数据库的消息目录的传统方法更好的见解。

File::Findgrep 是一个示例应用程序/模块,它使用 Locale::Maketext 来本地化其消息。对于一个较大的国际化系统,请参见 Apache::MP3

I18N::LangTags.

Win32::Locale.

RFC 3066,语言标识标签,网址:http://sunsite.dk/RFC/rfc/rfc3066.html

RFC 2277,IETF 字符集和语言策略,网址:http://sunsite.dk/RFC/rfc/rfc2277.html——其中大部分内容仅对协议设计者有意义,但它解释了一些基本概念,比如区域设置和语言标签之间的区别。

GNU gettext 手册。gettext 分发版可在 ftp://prep.ai.mit.edu/pub/gnu/ 中获取——获取一个最新的 gettext tarball 并查看其“doc/”目录,其中有一个易于浏览的 HTML 版本。gettext 文档提出了许多值得思考的问题,即使其中一些答案有时很古怪,尤其是在它们开始讨论复数形式的时候。

Locale/Maketext.pm 源代码。请注意,该模块比其文档短得多!

版权和免责声明

版权所有 (c) 1999-2004 Sean M. Burke。保留所有权利。

此库是免费软件;您可以在与 Perl 自身相同的条款下重新分发和/或修改它。

本程序以有用的希望分发,但不提供任何保证;甚至不保证适销性或适用于特定用途。

作者

Sean M. Burke [email protected]