目錄

名稱

Locale::Maketext::TPJ13 -- 關於軟體在地化的文章

語法

# This an article, not a module.

說明

以下這篇文章由 Sean M. Burke 和 Jordan Lachler 最初刊登於《Perl Journal》第 13 期,並受 1999 年 Perl Journal 版權保護。它由 Jon Orwant 和 Perl Journal 提供。這份文件可以與 Perl 本身使用相同的條款進行分發。

在地化與 Perl:gettext 中斷,Maketext 修復

作者:Sean M. Burke 和 Jordan Lachler

這篇文章指出 gettext(一個常見的軟體介面在地化系統,也就是讓軟體介面可以使用使用者選擇的語言)會失敗的情況,原因是人類語言之間的基本差異。然後這篇文章說明 Maketext,一個能夠正確處理這些差異的新系統。

在地化恐怖故事:這可能會發生在你身上

想像一下,你今天的工作是將軟體在地化——幸運的是,這個程式只會輸出兩個訊息,如下所示

I scanned 12 directories.

Your query matched 10 files in 4 directories.

這有多難?你看看產生第一個項目的程式碼,它讀取

printf("I scanned %g directories.",
       $directory_count);

你思考一下,發現它甚至無法正確處理英文,因為它會產生這個輸出

I scanned 1 directories.

所以你將它改寫成

printf("I scanned %g %s.",
       $directory_count,
       $directory_count == 1 ?
         "directory" : "directories",
);

...這樣做是正確的。(如果你不記得,「%g」是針對特定地區的數字內插,而「%s」是針對字串內插。)

但是你仍然必須為你製作此軟體的所有語言進行在地化,因此你從 CPAN 中拉出 Locale::gettext,以便存取你聽說用於在地化任務的標準 C 函式 gettext

你寫下

printf(gettext("I scanned %g %s."),
       $dir_scan_count,
       $dir_scan_count == 1 ?
         gettext("directory") : gettext("directories"),
);

但是你接著在 gettext 手冊(Drepper、Miller 和 Pinard 1995 年)中讀到這不是個好主意,因為像「目錄」或「目錄」這樣的單一字詞翻譯方式可能取決於語境——這是真的,因為在德語或俄語等格位語言中,你可能需要在第一個實例中使用不同格位結尾的這些字詞(字詞是動詞的受詞),而不是在第二個實例中,你甚至還沒處理到(字詞是介系的受詞,「在 %g 個目錄中」)——假設這些字詞在翻譯成這些語言時仍保持相同的語法。

因此,根據 gettext 手冊的建議,你改寫

printf( $dir_scan_count == 1 ?
         gettext("I scanned %g directory.") :
         gettext("I scanned %g directories."),
       $dir_scan_count );

所以,你寄電子郵件給你的各種翻譯人員(老闆決定當今的語言是中文、阿拉伯文、俄文和義大利文,所以你為每種語言找一位翻譯人員),要求翻譯「我掃描了 %g 個目錄。」和「我掃描了 %g 個目錄」。當他們回覆時,你會將其放入 gettext 在在地化軟體時使用的詞彙表中,以便當使用者在「zh」(中文)地區執行時,gettext("我掃描了 %g 個目錄。") 會傳回適當的中文文字,其中包含一個「%g」,printf 可以將 $dir_scan 內插到其中。

您的中文翻譯人員立即回信 -- 他說這兩個片語在中文中的翻譯是一樣的,因為在語言學術語中,中文「沒有數字作為語法範疇」 -- 而英文則有。也就是說,英文有指涉「數字」的語法規則,例如某個東西在語法上是單數還是複數;其中一個規則是強制名詞在複數語境中使用複數後綴(通常為「s」),例如在「一」以外的數字(奇怪的是,包括「零」)之後。中文沒有這樣的規則,因此英文有兩個片語,中文只有一個。不過沒問題,您可以在程式碼的「zh」gettext 詞彙表中,讓這個中文片語顯示為兩個英文片語的翻譯。

受到此鼓舞,您深入研究軟體需要輸出的第二個片語:「您的查詢在 4 個目錄中比對到 10 個檔案」。您注意到,如果您想將片語視為不可分割的,正如 gettext 手冊明智地建議,您現在需要四個案例,而不是兩個,才能涵蓋兩個項目($dir_count 和 $file_count)的單數和複數排列。因此您嘗試這個

printf( $file_count == 1 ?
  ( $directory_count == 1 ?
   gettext("Your query matched %g file in %g directory.") :
   gettext("Your query matched %g file in %g directories.") ) :
  ( $directory_count == 1 ?
   gettext("Your query matched %g files in %g directory.") :
   gettext("Your query matched %g files in %g directories.") ),
 $file_count, $directory_count,
);

(我認為「2 個 [或更多] 目錄中的 1 個檔案」的情況可能發生在符號連結或類似情況中。)

您發現這不是您寫過最漂亮的程式碼,但這似乎是可行的方法。您寄信給翻譯人員,要求翻譯這四個案例。中文人員回覆一個片語,這些片語在中文中的翻譯都是這個,而且這個片語中有兩個「%g」,正如它應該有的 -- 但有一個問題。他逐字翻譯回來:「在 %g 個目錄中包含 %g 個檔案符合您的查詢。」%g 插槽的順序與英文中的相反。您想知道您將如何讓 gettext 處理這個問題。

但您暫時擱置這個問題,並樂觀地希望其他翻譯人員不會遇到這個問題,而且他們的語言表現會更好 -- 也就是說,它們將與英文一樣。

但阿拉伯語翻譯人員是下一個回信的人。首先,你的「我掃描了 %g 個目錄」或「我掃描了 %g 個目錄」的程式碼假設只有單數或複數。但,再用語言學術語來說,阿拉伯語有文法上的數字,就像英語(但不像中文),但它是一個三項分類:單數、雙數和複數。換句話說,你說「目錄」的方式取決於目錄是一個、兩個超過兩個。你的 ($directory == 1) 測試不再能發揮作用。這表示英語的數字文法類別只根據「目錄 [單數]」和「目錄 [複數]」需要第一個句子的兩個排列,阿拉伯語有三個——更糟的是,在第二個句子(「你的查詢在 %g 個目錄中比對到 %g 個檔案」)中,英語有四個,阿拉伯語有九個。你感覺到一個不受歡迎的指數趨勢正在成形。

你的義大利語翻譯人員回信說「我搜尋了 0 個目錄」(你的程式可能輸出的英語)很彆扭,如果你認為那樣是好的英語,那是你的問題,但那絕對不行出現在但丁的語言中。他堅持如果 $directory_count 是 0,你的程式應該產生義大利語文字「我沒有掃描任何目錄」。「我沒有在任何目錄中比對到任何檔案」也是一樣,儘管他說最後的「在任何目錄中」那部分可能應該省略。

你納悶你將如何讓 gettext 處理這個問題;為了適應阿拉伯語、中文和義大利語在這些非常簡單的幾個短語中處理數字的方式,你需要撰寫程式碼,根據問題中的數字值是 1、2、超過 2,或在某些情況下是 0,向 gettext 提出不同的查詢,而且你仍然沒有找出中文不同詞序的問題。

然後你的俄羅斯語翻譯人員打電話來,親自告訴你你的生活將變得有多麼不愉快

俄羅斯語就像德語或拉丁語,是一種屈折語;也就是說,名詞和形容詞必須採用取決於它們的格(即主格、受格、屬格等)的詞尾——這大致上是它們在句子句法中扮演的角色——以及名詞的文法性別(即陽性、陰性、中性)和數字(即單數或複數),以及名詞的變格類別。但與大多數其他屈折語不同的是,在俄羅斯語中,將數字詞組(例如「十」或「四十三」,或它們的阿拉伯數字等價詞)放在名詞前面會改變名詞的格和數字,因此你必須在它上面加上詞尾。

他進一步說明:「在「我掃描了 %g 個目錄」中,你預期「目錄」會以賓格形式出現(因為它是句子中的直接受詞),且為複數,但當 $directory_count 為 1 時,你當然會預期它為單數。就像拉丁文或德文一樣。但是!當 $directory_count % 10 為 1(「%」表示模數,請記住),假設 $directory_count 為整數,且當 $directory_count % 100 不為 11 時,「目錄」會被迫變成語法上的單數,這表示它會得到賓格單數的結尾... 你開始想像要測試到目前為止的問題所需的程式碼,而且仍然適用於中文、阿拉伯文和義大利文,以及這樣需要多少 gettext 項目,但他繼續說... 但當 $directory_count % 10 為 2、3 或 4(但 $directory_count % 100 不為 12、13 或 14 時),「目錄」這個字會被迫變成所有格單數——這表示另一個結尾... 房間開始在你周圍旋轉,一開始很慢... 但對於所有其他整數值,由於「目錄」是一個無生命名詞,當它出現在數字前面且為主格或賓格時(就像這裡一樣,你的運氣很好!),它會保持複數,但會被迫變成所有格——另一個結尾... 你從未聽過他提到你將會遇到類似(但可能略有不同)的問題,例如波蘭文等其他斯拉夫語言,因為地板會升上來迎接你,你會昏迷不醒。

上述警示故事說明了嘗試在地化可能會如何導致程式設計師感到沮喪、程式碼混淆不清,以及需要鎮定劑。但仔細評估後會發現,你選擇的工具只需要進一步考慮即可。

語言觀點

在過去一個世紀中,語言學領域已投入大量心力,試圖找出跨語言適用的語法模式;這是一個不斷有人提出應適用於所有語言的概括,卻發現這些概括往往會失敗的過程——有時只會失敗於少數語言,有時會失敗於整類語言,有時則會失敗於世界上幾乎所有語言,除了英文。廣泛的統計趨勢顯示「平均語言」的規則可能、必須和不可能是什麼樣子。但「平均語言」只是一個概念,就像「平均人」一樣不切實際——它會面臨到沒有任何語言(或人)實際上是平均的這個事實。過去的經驗告訴我們,任何給定的語言都可以做任何它想做的事,以任何順序,並訴諸任何類型的語法範疇——格、數、時態、字詞所指事物的真實或隱喻特徵、基於字詞可以接受的結尾或前綴的任意或可預測分類、對所表達陳述真實性的確定程度或方式,等等,不勝枚舉。

幸運的是,大部分在地化工作都是找出翻譯整句的方法,通常是句子,其中脈絡相對固定,且內容變異通常在於數字表達,就像上述範例句子。翻譯特定、完整的句子在實務上相當萬無一失,這很好,因為這正是許多觀光客依賴的會話手冊中的內容。現在,某個片語(無論在會話手冊或 gettext 詞彙表中)在一種語言中的適用性可能大於或小於該片語翻譯成另一種語言的適用性,例如,嚴格來說,在阿拉伯語中,「您的查詢符合...」中的「您的」會根據使用者是男性或女性而採用不同的形式;因此,阿拉伯語翻譯「您的 [女性] 查詢」的適用性低於對應的英文片語,後者不會區分使用者的性別。(在實務上,讓程式知道使用者的性別不可行,因此預設通常會使用阿拉伯語中的陽性「您」。)

但一般來說,在翻譯完整句子時,這種驚喜很少見,特別是當功能脈絡僅限於電腦與使用者互動以傳達事實或提示提供資訊時。因此,為了在地化的目的,按片語(通常按句子)翻譯是最簡單且問題最少的方法。

打破 gettext

考慮觀光會話手冊中的句子有兩種:一種像「我要如何前往市場?」沒有任何空白需要填寫,另一種像「這些 ___ 多少錢?」,其中有一個或多個空白需要填寫(這些通常會連結到一個單字清單,你可以將單字填入空白中:「魚」、「馬鈴薯」、「番茄」等)。沒有空白的句子沒有問題,但填空題可能沒有那麼簡單。例如,如果這是斯瓦希里語會話手冊,作者可能不會費心告訴你動詞「cost」如何根據你填入空白中的名詞來改變其屈折前綴。如果你在「cost」上使用錯誤的屈折前綴,市場上的商人仍然會明白你在說什麼。畢竟,你不會說標準斯瓦希里語,你只是個觀光客。但觀光客可以很笨,電腦應該很聰明;電腦應該能夠填寫空白,且結果仍然符合文法。

換句話說,一個片語手冊條目會將一些值作為參數(您填入空白或空白中的內容),並根據這些參數提供一個值,而從給定值中取得最終值的方法,嚴格來說,可能涉及一系列任意複雜的操作。(就中文而言,這一點一點都不複雜,至少在本文開頭的範例中是如此;而就俄文而言,這將會是一系列相當複雜的操作。而在某些語言中,複雜性可能會以不同的方式散布:雖然在名詞片語前面加上數字表達式本身可能並不複雜,但它可能會改變您必須如何,例如,在句子中的其他地方對動詞進行屈折變化。這就是語法中所謂的「長距離依賴關係」。)

這種關於參數和任意複雜性的說法只是另一種說法,即片語手冊中的條目在程式語言中會被稱為「函數」。為了讓您不會錯過,這正是本文的重點:片語是一個函數;片語手冊是一堆函數。

使用 gettext 會遇到障礙(就像上面第二人稱恐怖故事中那樣)的原因在於,您嘗試使用字串(或更糟的是,在許多字串中進行選擇)來執行您真正需要函數來執行的任務——這是徒勞的。對您從 gettext 取得的字串執行 (s)printf 內插確實允許您以相當好的方式執行一些常見的事情... 有時... 差不多;但是,用一些人對csh指令碼程式設計的說法來說,「它讓您誤以為可以使用它來執行實際的事情,但您不能,而且直到您已經花了太多時間嘗試,到那時為止已經太遲了,您才會發現這一點。」

取代 gettext

因此,需要取代 gettext 的是一個支援函數詞彙表而非字串詞彙表的系統。來自此類系統的詞彙表中的條目不應如下所示

"J'ai trouv\xE9 %g fichiers dans %g r\xE9pertoires"

[\xE9 在 Latin-1 中是 e-acute。如果我在這裡使用實際字元,一些 pod 渲染器會發出尖叫。-- SB]

而是像這樣,請記住這只是一個初步嘗試

sub I_found_X1_files_in_X2_directories {
  my( $files, $dirs ) = @_[0,1];
  $files = sprintf("%g %s", $files,
    $files == 1 ? 'fichier' : 'fichiers');
  $dirs = sprintf("%g %s", $dirs,
    $dirs == 1 ? "r\xE9pertoire" : "r\xE9pertoires");
  return "J'ai trouv\xE9 $files dans $dirs.";
}

現在,沒有特別明顯的方法可以在 gettext 詞彙表中儲存字串以外的任何內容;因此,看來我們必須重新開始,從頭開始製作更好的東西。我稱我對 gettext 替換系統的嘗試為「Maketext」,或者,用 CPAN 術語來說,Locale::Maketext。

在設計 Maketext 時,我選擇根據「流行語合規性」來規劃其主要功能。以下是這些流行語

流行語:抽象化和封裝

您嘗試輸出片語的語言的複雜性完全抽象在 (並封裝在) 該介面的 Maketext 模組中。當您呼叫

print $lang->maketext("You have [quant,_1,piece] of new mail.",
                     scalar(@messages));

您不知道 (而且事實上無法輕易找出) 這是否會涉及大量計算,例如俄語 (如果 $lang 是俄語模組的控制代碼),或相對較少,例如中文。這種抽象化和封裝可能會鼓勵其他令人愉快的流行語,例如模組化和分層化,具體取決於您做出的設計決策。

流行語:同構

「同構」意指「具有相同的結構或形式」;在程式設計討論中,這個詞採用特殊的特定含義,即您對問題解決方案的實作具有相同的結構,例如,非正式的解決方案口頭描述,或可能是問題本身。綜觀而言,同構是一件好事,這就是問題解決 (和解決方案實作) 應有的樣子。

使用 gettext 的程式碼,例如...

printf( $file_count == 1 ?
  ( $directory_count == 1 ?
   "Your query matched %g file in %g directory." :
   "Your query matched %g file in %g directories." ) :
  ( $directory_count == 1 ?
   "Your query matched %g files in %g directory." :
   "Your query matched %g files in %g directories." ),
 $file_count, $directory_count,
);

首先,它沒有得到很好的抽象化,這些測試語法數量的途徑 (例如,foo == 1 ? singular_form : plural_form 中的表達式) 應抽象到每個語言模組,因為取得語法數量的途徑是特定於語言的。

但其次,它不是同構的,中文的「解決方案」(即,片語手冊條目) 從這四個英文片語對應到適用於所有這些片語的一個中文片語。換句話說,非正式的解決方案將是「在中文中表達您想要表達的內容的方式是使用一個片語『關於您的問題,您會在 Y 個目錄中找到 X 個檔案』」,因此,實作的解決方案應同構地僅是一種直接吐出該片語的方式,並適當地插入數字。它不應必須從其他語言的複雜性對應到這個語言的簡單性。

流行語:繼承

對於相關方言之間的片語共享,或相關語言之間的輔助函式共享,有很大的重複使用可能性。(「輔助函式」是指不產生片語文字的函式,但例如,回答「這個數字之後是否需要複數名詞?」。此類輔助函式將用於實際產生片語文字的函式的內部邏輯中。)

在分享短語的情況下,請考慮您已經針對美式英語進行在地化過介面(可能是因為以美式英語作為原生語言環境所撰寫,但這只是附帶的)。實際上,針對英式英語進行在地化,應該只是讓一位英國人執行並指示哪些短語可受益於拼寫變更或可能進行小幅改寫。在這種情況下,您應該只能在英式英語在地化模組中放入英式英語專用的短語,而其他所有短語則從美式英語模組中繼承。(我預期巴西和葡萄牙語也會適用於這種情況,可能還包括捷克語和斯洛伐克語等非常密切相關的語言,以及我聽說在台灣和中國大陸存在的書面中文的略有不同的「版本」。)

至於輔助功能的分享,請考慮本文開頭提到的俄文數字問題;顯然地,您只會想要撰寫一次複雜的程式碼,在給定數值時,會傳回特定量化名詞應使用的格和數。但假設您在針對烏克蘭語(一種與俄語相關的斯拉夫語,有數百萬人使用,其中許多人會很樂意發現您的網站或軟體介面有提供他們的語言)進行介面在地化時發現,烏克蘭語在量化方面的規則與俄語相同,而且可能在許多其他文法功能方面也相同。雖然俄語和烏克蘭語之間可能沒有共同的短語,但您仍然可以選擇讓烏克蘭語模組繼承俄語模組,只是為了繼承所有不同的文法方法。或者,組織上可能比較好,您可以將這些功能移至稱為 _E_Slavic 或類似名稱的模組,俄語和烏克蘭語可以從中繼承有用的功能,但(假設)不會提供詞彙。

流行語:簡潔

好的,簡潔不是流行語。但它應該是一個,所以我宣布「簡潔」這個新流行語的意思是,簡單常見的事物應該可以用非常少的程式碼行(甚至可能只是幾個字元)表達出來——稱它為「讓簡單的事情變得容易,讓困難的事情變得可能」的特殊情況,並參閱它在 MIDI::Simple 語言中所扮演的角色,在這個問題的 [TPJ#13] 中有其他討論。

考慮我們在「功能詞彙表」中的第一個嘗試

sub I_found_X1_files_in_X2_directories {
  my( $files, $dirs ) = @_[0,1];
  $files = sprintf("%g %s", $files,
    $files == 1 ? 'fichier' : 'fichiers');
  $dirs = sprintf("%g %s", $dirs,
    $dirs == 1 ? "r\xE9pertoire" : "r\xE9pertoires");
  return "J'ai trouv\xE9 $files dans $dirs.";
}

您可能會感覺到詞彙表(使用一個不帶承諾的概括術語,表示您知道如何表達的事物的集合,無論它們是短語或字詞)由如上所述表達的功能組成,會產生冗長且重複的程式碼——即使您明智地重新撰寫它,讓量化(我們稱之為將數字表達式新增到名詞短語)成為一個稱為如下所示的功能

sub I_found_X1_files_in_X2_directories {
  my( $files, $dirs ) = @_[0,1];
  $files = quant($files, "fichier");
  $dirs =  quant($dirs,  "r\xE9pertoire");
  return "J'ai trouv\xE9 $files dans $dirs.";
}

而且您也可能會感覺到,您不想要讓您的翻譯人員費心撰寫 Perl 程式碼——您寧願讓他們將非常昂貴的時間花在翻譯上。這還沒有提到幾乎不可能找到一位商業翻譯人員,他們甚至知道簡單的 Perl。

在 Maketext 的第一個駭客實作中,每種語言模組的詞彙表看起來像這樣

%Lexicon = (
  "I found %g files in %g directories"
  => sub {
     my( $files, $dirs ) = @_[0,1];
     $files = quant($files, "fichier");
     $dirs =  quant($dirs,  "r\xE9pertoire");
     return "J'ai trouv\xE9 $files dans $dirs.";
   },
 ... and so on with other phrase => sub mappings ...
);

但我立即開始尋找更簡潔的方式來表示相同的片語函數,一種方式也能簡潔地表示詞彙表中大多數語言的大多數片語函數。經過長時間,甚至一些實際思考後,我決定使用這個系統

* 當 %Lexicon 哈希中的值是內容豐富的字串,而不是匿名子程式(或可能是一個代碼參考),它會被解釋為子程式功能的一種簡寫表達式。在一個階段中首次存取時,它會被解析、轉換成 Perl 程式碼,然後 eval 成一個匿名子程式;然後那個子程式會取代詞彙表中的原始字串。(這樣,解析和 eval 給定片語的簡寫形式的工作在每個階段中只會執行一次。)

* 對 maketext(Maketext 的主要函數)的呼叫透過「語言階段處理」,在概念上非常像 IO 處理,因為你會在階段開始時開啟一個,並使用它「傳送訊號」給物件,以便讓它傳回你想要的文字。

所以,這

$lang->maketext("You have [quant,_1,piece] of new mail.",
               scalar(@messages));

基本上表示:在 $lang 的詞彙表中尋找(可能繼承自任何數量的其他詞彙表),並找到我們碰巧與字串「You have [quant,_1,piece] of new mail」關聯的函數(在這種情況下,是本機語言設定的「簡寫」函數,也就是英文)。如果你找到這樣的函數,請使用 $lang 作為它的第一個參數(就像它是方法一樣)呼叫它,然後將 scalar(@messages) 的副本作為它的第二個參數,然後傳回那個值。如果找到那個函數,但它是在字串簡寫中而不是完全指定的函數,請在第一次呼叫它之前解析它並將它製成函數。

* 簡寫使用括號中的程式碼來表示應該執行的函數呼叫。這裡不需要完整的說明,但幾個範例就足夠了

"You have [quant,_1,piece] of new mail."

上述程式碼是以下內容的簡寫,並且會被解釋為以下內容

sub {
  my $handle = $_[0];
  my(@params) = @_;
  return join '',
    "You have ",
    $handle->quant($params[1], 'piece'),
    "of new mail.";
}

其中「quant」是你用來使用數字 $params[0] 量化名詞「piece」的方法名稱。

沒有括號呼叫的字串,如下所示

"Your search expression was malformed."

有點像是簡化案例,並且只會轉換成

sub { return "Your search expression was malformed." }

然而,你可以在 Perl 程式碼中編寫的所有內容並不能在上述簡寫系統中編寫,差得很遠。例如,考慮本文開頭的義大利語翻譯者,他希望「I didn't find any files」的義大利語是特殊案例,而不是「I found 0 files」。這無法在我們的簡寫系統中指定(至少無法輕鬆或簡單地指定),並且必須像這樣完整地寫出來

sub {  # pretend the English strings are in Italian
  my($handle, $files, $dirs) = @_[0,1,2];
  return "I didn't find any files" unless $files;
  return join '',
    "I found ",
    $handle->quant($files, 'file'),
    " in ",
    $handle->quant($dirs,  'directory'),
    ".";
}

在充滿簡寫程式碼的詞彙表旁邊,這種情況就像一個大拇指一樣突出,但這畢竟是一個特殊案例;至少它是可能的,即使不像平常一樣簡潔。

至於你如何實作本文開頭的俄語範例,嗯,有很多方法可以做到,但可以像這樣(使用俄語的英文單字,這樣你才知道發生了什麼事)

"I [quant,_1,directory,accusative] scanned."

這將複雜性的負擔轉移到 quant 方法。該方法的參數為:它將用來量化某個東西的數字值;它將要量化的俄語單字;以及參數「accusative」,你用來表示這個句子的語法在那裡需要一個賓格名詞,儘管那個量化方法可能必須推翻,原因是語法,你可能還記得本文開頭的部分內容。

現在,這裡的俄語量化方法不僅負責實作找出俄語數字片語如何對其名詞片語施加格位和數目的奇怪邏輯,還負責對「目錄」這個俄語單字進行變格。如何執行該變格並非小事,在我看過的解決方案中,有些(例如在雜湊中進行簡單查找的變體,其中所有必要的單字都提供了所有可能的型式)很直接,但當你需要變格超過幾十個單字時,可能會變得繁瑣;而其他解決方案(例如使用演算法來建模變格,僅儲存根型和不規則性)可能會涉及比所有最大的詞彙表更難以合理化的開銷。

幸運的是,這個設計決策僅在最棘手的變格語言中才變得至關重要,而俄語絕不是最糟的情況,但比大多數情況更糟。大多數語言具有更簡單的變格系統;例如,在英語或斯瓦希里語中,給定名詞通常最多只有兩種可能的變格形式(「error/errors」;「kosa/makosa」),而產生這些形式的規則相當簡單——或至少,可以制定適用於大多數單字的簡單規則,然後你可以將例外視為「不規則」,至少相對於你的臨時規則。更簡單的變格系統(更簡單的規則,更少的形式)表示設計決策對維持健全性不太重要,而相同的決策可能會在俄語等語言中造成開銷與可擴充性問題。有可能已經為問題語言編寫了程式碼(可能是 Perl,例如 Lingua::EN::Inflect,用於英語名詞),無論是簡單還是複雜。

此外,第三種可能性甚至可能比上述任何一種都更簡單:「只需要求在呼叫給定語言的量化方法時提供所有可能的(或至少適用的)形式,如下所示:」

"I found [quant,_1,file,files]."

這樣,quant 只要選擇它需要的形式,而無需查詢或產生任何東西。儘管可能不適合俄語,但這應該適用於大多數其他語言,因為量化並非一項複雜的操作。

魔鬼藏在細節中

Maketext 的內容遠比上述描述的更多,例如,語言標籤(「en-US」、「i-pwn」、「fi」等)或地區 ID(「en_US」)如何與實際模組命名(「BogoQuery/Locale/en_us.pm」)互動,以及可能產生的神奇效果;如何記錄(並可能協商)Maketext 將以何種字元編碼傳回文字(UTF8?Latin-1?KOI8?)的詳細資訊。有趣的是,Maketext 是用於在地化,但實際上並未在任何地方包含「use locale;」。對於好奇的人來說,我實際上是如何實作類似資料繼承的細節,以便跨模組的 %Lexicon 哈希搜尋可以與 Perl 實作方法繼承的方式相同,這有點令人害怕。

而且,最重要的是,如何實際從 Maketext 衍生以便您可以將其用於您的介面,以及開始和維護個別語言模組的各種工具和慣例,這些都是實務細節。

所有這些都包含在 Locale::Maketext 的文件和隨附模組中,可在 CPAN 中取得。在閱讀完本文後,本文涵蓋了 Maketext 的原因,而文件涵蓋了 Maketext 的方法,應該非常直接。

真憑實據:在地化網站

Maketext 和 gettext 有顯著的差異:gettext 是以 C 編寫,可透過 C 函式庫呼叫存取,而 Maketext 是以 Perl 編寫,而且沒有 Perl 解譯器就無法運作(儘管我認為可以為 C 編寫類似程式)。歷史的意外(不一定幸運)讓 C++ 成為文字處理器、網路瀏覽器,甚至許多內部應用程式(例如自訂查詢系統)實作最常見的語言。目前的狀況使得這些類型的應用程式中不太可能出現下一個以 Perl 編寫的應用程式,儘管顯然是出於習慣和慣性的原因,而不是考慮什麼才是這項工作的合適工具。

然而,歷史上的其他意外事件讓 Perl 成為設計伺服器端程式(通常以 CGI 形式)以供網站介面使用的廣泛接受語言。網站中靜態頁面的在地化很簡單,可透過 Apache 等伺服器中的簡單語言協商功能,或透過某種伺服器端包含語言適當文字到版面範本中來達成。然而,我認為 Perl 為基礎的搜尋系統(或其他種類的動態內容)在網站中的在地化,無論是公開或受限存取,是 Maketext 將會看到最大的使用量的地方。

我假設只有少數的網站會在地化為英文中文義大利文阿拉伯文俄文,回想本文開頭的語言——更別提德文、西班牙文、法文、日文、芬蘭文和印地文,僅舉出一些受益於大量程式設計師、網站瀏覽者或兩者兼具的語言。

然而,網路的國際化與日俱增(無論是根據內容量、內容撰寫者或程式設計師的數量,或內容受眾的大小來衡量),這使得平均網路動態內容服務的介面在地化為兩種或可能三種語言的可能性越來越高。我希望 Maketext 能讓這項任務盡可能簡單,並消除過去在地化與英文不同的語言時所遇到的障礙。

__END__

Sean M. Burke (sburke@cpan.org) 擁有西北大學的語言學碩士學位;他專精於語言技術。Jordan Lachler (lachler@unm.edu) 是新墨西哥大學語言學系的博士生;他專精於北美原住民語言的形態學和教學法。

參考資料

Alvestrand, Harald Tveit。1995 年。RFC 1766:語言識別標籤。 http://www.ietf.org/rfc/rfc1766.txt [現在請參閱 RFC 3066。]

Callon, Ross,編輯。1996 年。RFC 1925:十二個網路真理。 http://www.ietf.org/rfc/rfc1925.txt

Drepper, Ulrich、Peter Miller 和 François Pinard。1995-2001 年。GNU gettext。可於 ftp://prep.ai.mit.edu/pub/gnu/ 中取得,並在發行 tarball 中提供廣泛的文件。[自從我在 1998 年撰寫本文以來,我現在看到 gettext 文件現在正嘗試更多地了解複數。是否有得出有用的結論是另一個問題。——SMB,2001 年 5 月]

Forbes, Nevill。1964 年。俄語文法。第三版,由 J. C. Dumbreck 修訂。牛津大學出版社。