目錄

名稱

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, $! );
...

說明

應用程式(無論是直接執行或透過網路)的常見功能是「在地化」——也就是針對英語使用者呈現英文介面,針對德語使用者呈現德語介面,以此類推,針對程式設計的所有語言都這麼做。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 hash 中找不到 key可能會擲回例外狀況。如果找不到金鑰會發生什麼事,會在後面的章節「控制查詢失敗」中討論。

請注意,在某些情況下,您可能會覺得覆寫 maketext 方法並使用「after 方法」很有用,如果您想要轉譯編碼,甚至是指令碼

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) <或> $lh->blacklist(@list)
$lh->allowlist(@list) <或> $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,你會得到「1 個文件匹配的」。這裡可以接受的技巧是執行以下操作

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

這會根據此語言的慣例,以美觀的方式傳回給定的數字格式。Maketext 的預設方法主要是採用數字的正常字串形式(僅對非常大的數字套用 sprintf "%G"),然後視需要加入逗號。(但如果 $language->{'numf_comma'} 為 true,我們會套用 tr/,./.,/;這是一個小技巧,對將兩百萬表示為「2.000.000」而非「2,000,000」的語言很有用)。

如果您想要更花俏的內容,請考慮使用 Number::Format 或完全執行其他操作來覆寫此內容。

請注意,numf 會由 quant 呼叫,以將所有量化數字字串化。

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

這會根據此語言的慣例,傳回適用于數量 $number 的給定名詞形式。numeratequant 內部使用,以量化名詞。直接使用它(通常來自方括號表示法)以避免 quant 隱含呼叫 numf 和輸出數字量。

$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 或更多)的參數。一個項目會透過字串 key 在 $lh->maketext(key, ...parameters...) 中存取,它應該會傳回一個字串,通常用於對使用者「輸出」——無論這實際上是指列印到 STDOUT、寫入檔案,或放入 GUI 小工具中。

雖然 key 必須是字串值(因為這是 Perl 對雜湊 key 的基本限制),但 lexicon 中的值目前可以有幾種類型:已定義的純量、純量參考,或代碼參考。這些的使用方式已在「「maketext」方法」區段中說明,而字串的方括號表示法則在下一區段中討論。

雖然你可以對 lexicon key 使用任意的唯一 ID(例如「_min_larger_max_error」),但通常會讓項目的 key 本身成為一個有效的值,就像這個範例錯誤訊息

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

比較這段使用任意 ID 的程式碼...

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

...與這段使用 key-as-value 的程式碼

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

簡而言之,第二段程式碼較易讀。特別是,很明顯你提供給該詞組的參數數量(兩個)就是它想要接收的參數數量。(因為你看到 key 中使用了 _1 和 _2。)

此外,一旦專案完成且你開始將它在地化,你可以將所有你使用的各種 key 刮在一起,並傳遞給翻譯人員;然後,如果翻譯人員看到的是這個

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

而不是這個較難理解的混亂內容

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

我認為將 key 作為 lexicon 值會讓完成的 lexicon 項目更易讀

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

此外,如果你設定一個 _AUTO lexicon,那麼將有效的值作為 key 會非常有用。_AUTO lexicon 會在後面的區段中討論。

我幾乎總是使用本身是有效 lexicon 值的 key。一個值得注意的例外是當值很長時。例如,要取得命令列程式在給定未知開關時可能會傳回的滿螢幕資料,我通常只會使用簡短且有說明性的 key,例如「_USAGE_MESSAGE」。在那個時間點,我接著會立即在 ProjectClass::L10N::en lexicon 中定義那個 lexicon 項目(因為英語永遠是我的「專案語言」)

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

然後我就可以像這樣使用它

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

順帶一提,請注意每個類別的 %Lexicon 會繼承並延伸其超類別中的 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() 方法會在每次呼叫時擴充其內部清單。若要重設 denylist 或 allowlist,請建立新的 maketext 物件。

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

對於使用內部快取的字典,已快取為已編譯形式的翻譯不會受到後續對 allowlist 或 denylist 設定的變更影響。使用外部快取的字典會在 allowlist 或 denylist 設定變更時清除其快取。這兩種快取類型之間的差異在「唯讀字典」區段中說明。

denylist 不允許的方法無法由 allowlist 允許。

注意: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 在此為 true,那麼只要在該詞彙表中尋找金鑰為 "找不到檔案 \"[_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——我在 The 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 dist 可在 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 sburke@cpan.org