[Rt-commit] r4934 - in Locale-Maketext-Lexicon: . docs inc
inc/Module/Install lib lib/Locale lib/Locale/Maketext
lib/Locale/Maketext/Extract lib/Locale/Maketext/Lexicon
script t t/locale t/locale/en t/locale/en/LC_MESSAGES
t/locale/zh_CN t/locale/zh_CN/LC_MESSAGES t/locale/zh_TW
t/locale/zh_TW/LC_MESSAGES
autrijus at bestpractical.com
autrijus at bestpractical.com
Sun Apr 2 05:40:31 EDT 2006
Author: autrijus
Date: Sun Apr 2 05:40:20 2006
New Revision: 4934
Added:
Locale-Maketext-Lexicon/AUTHORS
Locale-Maketext-Lexicon/Changes
Locale-Maketext-Lexicon/Makefile.PL
Locale-Maketext-Lexicon/README
Locale-Maketext-Lexicon/docs/
Locale-Maketext-Lexicon/docs/webl10n.html (contents, props changed)
Locale-Maketext-Lexicon/docs/webl10n.zh-tw.html (contents, props changed)
Locale-Maketext-Lexicon/inc/
Locale-Maketext-Lexicon/inc/Module/
Locale-Maketext-Lexicon/inc/Module/Install/
Locale-Maketext-Lexicon/inc/Module/Install.pm
Locale-Maketext-Lexicon/inc/Module/Install/Base.pm
Locale-Maketext-Lexicon/inc/Module/Install/Can.pm
Locale-Maketext-Lexicon/inc/Module/Install/Fetch.pm
Locale-Maketext-Lexicon/inc/Module/Install/Makefile.pm
Locale-Maketext-Lexicon/inc/Module/Install/Metadata.pm
Locale-Maketext-Lexicon/inc/Module/Install/Scripts.pm
Locale-Maketext-Lexicon/inc/Module/Install/Win32.pm
Locale-Maketext-Lexicon/inc/Module/Install/WriteAll.pm
Locale-Maketext-Lexicon/lib/
Locale-Maketext-Lexicon/lib/Locale/
Locale-Maketext-Lexicon/lib/Locale/Maketext/
Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract/
Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract.pm
Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract/Run.pm
Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/
Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon.pm
Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Auto.pm
Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Gettext.pm
Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Msgcat.pm
Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Tie.pm
Locale-Maketext-Lexicon/script/
Locale-Maketext-Lexicon/script/xgettext.pl
Locale-Maketext-Lexicon/t/
Locale-Maketext-Lexicon/t/1-basic.t (contents, props changed)
Locale-Maketext-Lexicon/t/2-lmg.t (contents, props changed)
Locale-Maketext-Lexicon/t/3-big-endian.t (contents, props changed)
Locale-Maketext-Lexicon/t/4-encodings.t (contents, props changed)
Locale-Maketext-Lexicon/t/5-extract.t
Locale-Maketext-Lexicon/t/T_L10N.pm
Locale-Maketext-Lexicon/t/gencat.m
Locale-Maketext-Lexicon/t/locale/
Locale-Maketext-Lexicon/t/locale/en/
Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/
Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/test.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/test_be.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/test_utf8.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/zh_CN/
Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/
Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/test.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/test_be.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/test_utf8.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/zh_TW/
Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/
Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/test.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/test_be.mo (contents, props changed)
Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/test_utf8.mo (contents, props changed)
Locale-Maketext-Lexicon/t/messages.mo (contents, props changed)
Locale-Maketext-Lexicon/t/messages.po
Log:
* Locale::Maketext::Lexicon: Specifying the new "_auto => 1" option
now turns on _AUTO fallback handling for all language handles.
Requested by: Jesse Vincent
Added: Locale-Maketext-Lexicon/AUTHORS
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/AUTHORS Sun Apr 2 05:40:20 2006
@@ -0,0 +1,29 @@
+[Credits]
+
+Here is a list of people and their CPAN id, extracted from the ChangeLog
+file and the mailing list archives. These people have either submitted
+patches or suggestions, or their bug reports or comments have inspired
+the appropriate patches. Corrections, additions, deletions welcome:
+
+Alain Barbet (ALIAN)
+Brian Cassidy (BRICAS)
+Christian Hansen
+Gaal Yahas (GAAL)
+Harmen
+Helmut Lichtenberg
+Hsin-Chan Chien (HCCHIEN)
+Jesse Vincent (JESSE)
+Jiing Deng
+Joe McCarty
+Macpaul Lin
+Mathieu Arnold (MAT)
+Mikael Sennerholm
+Noelle Christian
+Robert Spier (ROBRT)
+Sava Chankov (SAVA)
+Sean M. Burke (SBURKE)
+Stephen Quinney (SJQUINNEY)
+Tatsuhiko Miyagawa (MIYAGAWA)
+Thierry Vignaud
+Wei-Hon Chen
+Yi Ma Mao (IMACAT)
Added: Locale-Maketext-Lexicon/Changes
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/Changes Sun Apr 2 05:40:20 2006
@@ -0,0 +1,327 @@
+[Changes for 0.56 - 2006-04-02]
+
+* Locale::Maketext::Lexicon: Specifying the new "_auto => 1" option
+ now turns on _AUTO fallback handling for all language handles.
+ Requested by: Jesse Vincent
+
+[Changes for 0.55 - 2006-03-22]
+
+* Locale::Maketext::Extract: Fix a long-standing bug that caused
+ double-escaping of backslashes and double quotes in loc strings.
+ Contributed by: Brian Cassidy
+
+[Changes for 0.54 - 2006-03-14]
+
+* When importing into an already defined lexicon and merging with its
+ entries, we need to clear the Locale::Maketext cache, otherwise
+ _AUTO entries generated by the old lexicon will linger around.
+ Contributed by: Helmut Lichtenberg
+
+[Changes for 0.53 - 2005-12-05]
+
+* Make xgettext.pl work with poEdit by not trying to read from empty
+ .po files. Patch from Brian Cassidy.
+
+[Changes for 0.52 - 2005-12-04]
+
+* Christian Hansen pointed out 0.51's fix was bass ackwards.
+
+[Changes for 0.51 - 2005-12-03]
+
+* Locale::Maketext::Extract::Run's use of File::Find::find with
+ (follow => 1) may break on systems without symbolic links.
+ Reported by Brian Cassidy.
+
+[Changes for 0.50 - 2005-11-04]
+
+* Per suggestion from Sava Chankov, add the (quite outdated) webl10n
+ articles to docs/ directory.
+
+Patches from Mikael Sennerholm:
+
+* Added "localize" and "localise" to the list of recognized L10N
+ functions in Locale::Maketext::Extract.
+
+* Skip .svn directories in Locale::Maketext::Extract::Run.
+
+[Changes for 0.49 - 2005-04-13]
+
+* Stephen Quinney notes that we are too eager to die on nonexisting
+ files passed in as part of a lexicon_get_() source. We now silently
+ ignore nonexistant files, only reporting error when it exists but
+ cannot be read or parsed.
+
+[Changes for 0.48 - 2005-03-12]
+
+Patches from Thierry Vignaud:
+
+* In Gettext.pm, do not silently skip last entry of a catalog.
+
+* In Extract.pm, do not silently skip first entry of a catalog
+ if there's only one empty line tween header and first real message.
+
+[Changes for 0.47 - 2005-02-03]
+
+* EXISTS in Locale::Maketext::Lexicon has the potential to trigger deep
+ recursion. Reported by Wei-Hon Chen.
+
+[Changes for 0.46 - 2004-12-16]
+
+* Patch from Thierry Vignaud: Add a "_allow_empty" option to the
+ Gettext backend, to allow empty strings with
+
+* Harmen suggested a doc patch to better document the _AUTO key
+ for allowing empty or missing keys.
+
+* Changed all ::L10N to ::I18N in examples, to agree with the de
+ facto community standard.
+
+[Changes for 0.45 - 2004-10-26]
+
+* Fix decoding problem on Big5 containing "]", as reported by Jiing Deng.
+
+* For \*DATA calls, we can get the 'main' package from $0, skip tracing the
+ caller chain.
+
+[Changes for 0.44 - 2004-09-25]
+
+* Fixed a regression from 0.43 that prevented %*(...) interpolations.
+
+* Remove RCS headers from source files..
+
+[Changes for 0.43 - 2004-09-24]
+
+* Fix decoding problem on Big5 containing "\".
+
+[Changes for 0.42 - 2004-08-25]
+
+* Devel::DProf did not like the closure-based delay loading mechanism;
+ rewrite using OO-based promises instead.
+
+[Changes for 0.41 - 2004-08-24]
+
+* Delayed loading of lexicons now repects the original options.
+
+* Delay actualization of lexicon parsing until the first use.
+ (Suggested by Jesse Vincent)
+
+[Changes for 0.40 - 2004-08-22]
+
+* Moved under SVK management.
+
+* The 'locale' encoding now works on Win32, using
+ Win32::Console::OutputCP() to probe the code page.
+
+[Changes for 0.38 - 2004-04-26]
+
+* The quotemeta() introduced in 0.37 broke Locale::Maketext::Simple.
+
+* L::M::Extract now parses the '# loc' and '# loc_pair'
+ markers before end of lines, as used in RT.
+
+* New module, L::M::Extract::Run, that encapsulates the
+ xgettext.pl script.
+
+[Changes for 0.37 - 2004-04-21]
+
+* Thanks Christian Hansen for reminding me of the
+ quotemeta() bug.
+
+* Forgot to put quotemeta() around dir patterns; pointed
+ out by Jouke Visser. This caused tests on Win32 to fail.
+
+* Respect the LAGUAGES and LC_MESSAGES environment variables.
+
+[Changes for 0.36 - 2004-03-17]
+
+* "_encoding" now takes a special "locale" value, which will
+ get the encoding from user's locale.
+
+* Gettext used to break when the re-encoded lexicon
+ contains Maketext's escape characters like "[" and "~".
+
+* Extract.pm should not prepend an empty "" before a
+ single-line "string\n". Such treatments are meant for
+ multiline strings.
+
+* proper escaping of \ and " in _format.
+
+[Changes for 0.35 - 2004-02-19]
+
+* We no longer depend on Regexp::Common and Pod::Usage.
+
+* Extract strings inside functions that has spaces inside
+ the parens, like 'loc( "test" )', as suggested by Helmut
+ Lichtenberg.
+
+[Changes for 0.34 - 2003-12-31]
+
+* Update copyright years to 2004.
+
+* Fix Msgcat so it also handle CRLF gracefully.
+
+* .po files with DOSish CRLF line-ending was not parsed correctly.
+
+[Changes for 0.33 - 2003-12-08]
+
+* Multiline strings were produced with an extra "\n" by xgettext.pl.
+
+* "xgettext.pl -u" was broken.
+
+* Various documentation and error message fixes.
+
+[Changes for 0.32 - 2003-10-14]
+
+* Excise $& from ::Extract, ::Msgcat and ::Gettext.
+
+* Micro-optimize (encode|decode)_utf8 in the inner loop of
+ ::Gettext instead of (encode|decode)('utf8', ...).
+
+[Changes for 0.31 - 2003-10-13]
+
+* 0.30 still broken on Robert Spiers's darwin box, so
+rewrote them to totally avoid capturing parenthesis.
+
+* Also modify Extract.pm to be not reentrant.
+
+[Changes for 0.30 - 2003-10-13]
+
+* '*' in sources will now get properlly expanded by globbing.
+
+* The reentrant-regex transformation used in ::Gettext was
+ breaking 5.8.0 in subtle ways. Reimplemented to use a two-pass
+ approach.
+
+[Changes for 0.29 - 2003-10-09]
+
+* Update readme and pods to mention ::Extract.
+
+* Add test for extraction.
+
+* Refactor xgettext.pl out into Locale::Maketext::Extract.
+
+* Move bin/ into script/.
+
+[Changes for 0.28 - 2003-08-28]
+
+* Gettext was chomping everything. Fixed.
+
+* Support for Kwiki-style generic loc string: {{...}}
+
+[Changes for 0.27 - 2003-07-07]
+
+* Change tests to use utf8, not big5, for perl 5.6.1
+ people with broken Locale::Maketext (1.06).
+
+* Supress uninitialized warnings.
+
+* Jonas Lincoln reported that empty globs was causing problems.
+
+* Strip the extra parens off tt2 templates.
+
+* Be very extra magical and assume that main:: comes from
+ the caller if it is not yet opened.
+
+* Chia-Liang Kao pointed out that getting sources from glob
+ was not working.
+
+* "xgettext.pl -g" was documented but never enabled.
+
+* Fixed xgettext.pl's multiline handling.
+
+* TT2 regex was wrong; thanks Chia-Liang Kao for pointing out.
+
+[Changes for 0.26 - 2003-05-03]
+
+* Incoporated a modified version of Matthieu Arnold's patch
+ that enables Encode-specific testing for Perl 5.6.1 with
+ Encode::compat.
+
+[Changes for 0.25 - 2003-04-30]
+
+* Change t/TestPkg/L10N.pm to t/T_L10N.pm in accord with
+ imacat's arrangement.
+
+* Fixes cases where there are multiple '*'s in the glob entry.
+
+[Changes for 0.22 - 2003-04-28]
+
+* Document the choice of using the final '*' for
+ glob patterns that has multiple '*'s.
+
+* Add extra debug info.
+
+* prefer the last * for globbing languages.
+
+[Changes for 0.21 - 2003-04-27]
+
+* Remove unneccessary MO files.
+
+* Updates copyrights to 2003.
+
+* Mathieu Arnold pointed out that multiline handling was incorrect.
+
+* Implemented correct parsing of multiple "#, flag1, flag2".
+
+* "#, fuzzy" entries are now ignored, unless _use_fuzzy is
+ set to true.
+
+* Documentation fix: removed the ambiguous [ ... ] by imacat's request.
+
+* Filehandle sources are now seek()ed back to the original
+ position after reading; this allows using the same handle
+ for two language subclasses.
+
+[Changes for 0.20 - 2003-04-26]
+
+* Add a note that we now turns 'zh-tw' into 'zh_tw' during
+ import. this fixed imacat's major gripe with previous
+ versions of L::M::L.
+
+* Nixed the TODO test since it's not really on my todo.
+
+* Add copyright notice around parse_mo() by imacat's request.
+
+* Test suite from imacat's Locale::Maketext::Gettext.
+
+* Documenting the new _encoding, _decode and wildcard support.
+
+* Add IMACAT to authors and acknowledgement list.
+
+* New _decode and _encoding flag to control auto-recoding.
+
+* Globbing support for import lists.
+
+[Changes for 0.16 - 2003-02-22]
+
+* Gaal Yahas's patch to recognize __ in addition of _.
+
+* Wei-Hon Chen pointed out pod errors.
+
+[Changes for 0.15 - 2002-11-29]
+
+* Corrected %quant(,%1) -- should be %quant(%1).
+
+* POD tweaks.
+
+* Random fixes from Alain Barbet.
+
+* Merging from old lexicons now work properly with and without -u.
+
+[Changes for 0.14 - 2002-11-13]
+
+* Escapes are now applicable even with -u.
+
+[Changes for 0.13 - 2002-11-13]
+
+* Alain's parser patch. This makes multi-line handling a breeze.
+
+[Changes for 0.12 - 2002-11-07]
+
+* Patch from Alain Barbett: Fix multiline gettext strings.
+
+* Document "xgettext.pl -u" before I forget about it.
+
+[Changes for 0.11 - 2002-10-27]
+
+* First sane CPAN release version.
Added: Locale-Maketext-Lexicon/Makefile.PL
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/Makefile.PL Sun Apr 2 05:40:20 2006
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use 5.005;
+use inc::Module::Install;
+
+author ('Audrey Tang <audreyt at audreyt.org>');
+abstract ('Use other catalog formats in Maketext');
+name ('Locale-Maketext-Lexicon');
+version_from ('lib/Locale/Maketext/Lexicon.pm',);
+license ('perl');
+install_script ('script/xgettext.pl');
+requires (
+ 'Test::More' => '0.01',
+ 'Locale::Maketext' => '0.01',
+);
+
+WriteAll( sign => 1 );
Added: Locale-Maketext-Lexicon/README
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/README Sun Apr 2 05:40:20 2006
@@ -0,0 +1,32 @@
+This is the README file for Locale::Maketext::Lexicon, a module providing
+lexicon-handling backends, for "Locale::Maketext" to read from other
+localization formats, such as PO files, MO files, or from databases via
+the "Tie" interface.
+
+For extracting translatable strings from source files, a "xgettext.pl"
+utility is also installed by default.
+
+You can also read my presentation "Web Localization in Perl" in the docs/
+directory. It gives an overview for the localization process, features
+a comparison between Gettext, Msgcat and Maketext, and talks about my
+experiences at localizing web applications based on HTML::Mason and the
+Template Toolkit.
+
+* Installation
+
+Locale::Maketext::Lexicon uses the standard perl module install process:
+
+ cpansign -v # optional; see the SIGNATURE file for details
+ perl Makefile.PL
+ make # or 'nmake' on Win32
+ make test
+ make install
+
+* Copyright
+
+Copyright 2002-2006 by Audrey Tang <audreyt at audreyt.org>.
+
+All rights reserved. You can redistribute and/or modify
+this bundle under the same terms as Perl itself.
+
+See <http://www.perl.com/perl/misc/Artistic.html>.
Added: Locale-Maketext-Lexicon/docs/webl10n.html
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/docs/webl10n.html Sun Apr 2 05:40:20 2006
@@ -0,0 +1,777 @@
+<html><head><title>Web Localization in Perl</title>
+<style><!--
+P {text-align: justify}
+P.right {text-align: right}
+--></style></head><body>
+
+<h1>Web Localization in Perl</h1>
+<p class="right" align="right">
+Audrey Tang<br>
+OurInternet, Inc.<br>
+July 2002
+
+<p>
+<h2>Abstract</h2>
+
+<p>
+The practice of internationalization (i18n) enables applications to support multiple languages, date/currency formats and local customs (collectively known as <em>locales</em>); localization (L10n) then deals with the actual implementation of fitting the software into the needs of users in a certain locale. Today, <em>Web applications</em> are one of the key areas that's being massively localized, due to its nature of text-based interface representation formats.
+<p>
+In the Free Software world, many of the most flexible and widely-used technologies are built upon the <em>Perl</em> language, which has long been the language of choice for web application developers. This article presents the author's hands-on experience on localizing several Perl-based applications into Chinese, the detailed usage and pitfalls of common frameworks, as well as <em>best practice</em> tips for managing a localization project.
+
+<h2>Introduction</h2>
+
+<p class="right" align="right">
+<i>``There are a number of languages spoken by human beings in this world.''<br>-- Harald Tveit Alvestrand, in RFC 1766, ``Tags for the Identification of Languages''</i>
+<p>
+Why should someone localize their websites or web applications?
+<p>
+Let us imagine this very question being debated on the Web, with convincing arguments and explanations raised by various parties, in different languages. As a participant in this discussion, you may hear following points being made:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Figure 1: Reasons for Localization (<em>before</em> localization)</font></caption>
+<tr><td><ul>
+<li>ИноÑÑ‚Ñ€Ð°Ð½Ð½Ð°Ñ Ð²Ð°Ð»ÑŽÑ‚Ð°, формат даты, Ñзык и обычаи могут казатьÑÑ Ð½Ð°Ð¼ пугающими
+<li>Menschen sind produktiver wenn sie in ihrer gewohnten Umgebung arbeiten
+<li>Tas veicina daudz labâku sapraðanu un mijiedarbîbu starp daâdâm kultrâm
+<li>Un progretto con molti collaboratori internazionali si evolverá piú in fretta e meglio
+<li>地区化的过程, 有助於软件的模å—化与å¯ç§»æ¤æ€§
+</ul></td></tr></table>
+
+<p>
+But, alas, it is not likely that all parties could understand all these points. This fact had naturally lead to the effect of <em>language barrier</em> -- our field of meaningful discussion are often restricted to a few <em>locale groups</em>: people who speak the same language and share the same culture with us.
+<p>
+However, that is truly sad since the arguments we missed are often valid ones, and usually offer new insights into our present condition. Therefore, it will be truly beneficial if the arguments, interfaces and other texts are translated for us:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Figure 2: Reasons for Localization (<em>localized</em> to English)</font></caption>
+<tr><td><ul>
+<li>It is a distraction to have to deal with interfaces that use foreign languages, date formats, currencies and customs
+<li>People are more productive when they operate in their native environments
+<li>It fosters understanding and communication between cultures
+<li>Projects with more international contributors will evolve faster and better
+<li>Localization tends to improve the software's modularity and portability
+</ul></td></tr></table>
+
+<p>
+As these arguments have pointed out, it is often not possible nor desirable to <em>just speak X</em>, be it Latin, Interlingua, Esperanto, Lojban or, well, English. At such times, localization (L10n) is needed.
+
+<p>
+For proprietary applications, L10n was typically done as a prerequisite of competing in a foreign market. That implies if the localization cost exceeds estimated profit in a given locale, the company would not localize its application at all, and it would be difficult (and maybe illegal) for users to do it themselves without the source code. If the vendor did not design its software with good i18n framework in mind -- well, then we're just out of luck.
+
+<p>
+Fortunately, the case is much simpler and rewarding with open-source applications. As with proprietary ones, the first few versions are often designed with only one locale in mind; but the difference is <em>anybody</em> is allowed to internationalize it <em>at any time</em>. As Sean M. Burke put it:
+
+<p>
+<blockquote>
+ The way that we can do better in open source is by writing our software
+ with the goal that localization should be easy both for the programmers
+ and maybe even for eager users. (After all, practically the definition
+ of "open source" is that it lets anyone be a programmer, if they are
+ interested enough and skilled enough.)
+</blockquote>
+
+<p>
+This article describes detailed techniques to make L10n easy for all parties involved. I will focus on <em>web-based</em> applications written in the <em>Perl</em> language, but the principle should also apply elsewhere.
+
+<h2>Localizing Static Websites</h2>
+<p class="right" align="right">
+<i>``It Has To Work.''<br>
+-- First Networking Truth, RFC 1925</i>
+
+<p>
+Web pages come in two different flavors: <em>static</em> ones provides the same content during many visits, until it is updated; <em>dynamic</em> pages may offer different information depends on various factors. They are commonly referred as <em>web documents</em> and <em>web applications</em>.
+<p>
+However, being static does not mean that all visitors must see the same <em>representation</em> -- different people may prefer different languages, styles or medium (e.g. via auditory outputs instead of visual ones). Part of the Web's strength is its ability to let the client <em>negotiate</em> with the server, and determine the most preferred representation.
+<p>
+For a concrete example, let us consider the author's hypothetical homepage <tt>http://www.autrijus.org/index.html</tt>, written in Chinese:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 1. A simple Chinese page</font></caption>
+<tr><td><PRE>
+<html><head><title><B>å”å®—æ¼¢ - å®¶</B></title></head>
+<body><B>施工ä¸, 請見諒</B></body></html>
+</PRE></td></tr></table>
+<p>
+One day, I decided to translate it for my English-speaking friends:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 2. Translated page in English</font></caption>
+<tr><td><PRE>
+<html><head><title><B>Audrey.Home</B></title></head>
+<body><B>Sorry, this page is under construction.</B></body></html>
+</PRE></td></tr></table>
+
+<P>
+At this point, many websites would decide to offer a <em>language selection page</em> to let the visitor to pick their favorite language. An example is shown in Figure 3:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Figure 3: A typical language selection page</font></caption>
+<tr><td align="center" colspan=4 bgcolor=black>
+<font color="white">Please choose your language:</font>
+</td></tr><tr><td align="center">
+<font size=-1><u>Čeština</u></font>
+</td><td align="center">
+<font size=-1><u>Deutsch</u></font>
+</td><td align="center">
+<font size=-1><u>English</u></font>
+</td><td align="center">
+<font size=-1><u>Español</u></font>
+</td></tr><tr><td align="center">
+<font size=-1><u>Français</u></font>
+</td><td align="center">
+<font size=-1><u>Hrvatski</u></font>
+</td><td align="center">
+<font size=-1><u>Italiano</u></font>
+</td><td align="center">
+<font size=-1><u>日本語</u></font>
+</td></tr><tr><td align="center">
+<font size=-1><u>한êµì–´</u></font>
+</td><td align="center">
+<font size=-1><u>Nederlands</u></font>
+</td><td align="center">
+<font size=-1><u>Polski</u></font>
+</td><td align="center">
+<font size=-1><u>РуÑÑкий Ñзык</u></font>
+</td></tr><tr><td align="center">
+<font size=-1><u>Slovensky</u></font>
+</td><td align="center">
+<font size=-1><u>Slovensci</u></font>
+</td><td align="center">
+<font size=-1><u>Svenska</u></font>
+</td><td align="center">
+<font size=-1><u>䏿–‡ (GB)</u></font><br>
+<font size=-1><u>䏿–‡ (Big5)</u></font>
+</td></tr></table>
+
+<p>
+For both non-technical users and automated programs, that page is confusing, redundant, and highly irritating. Besides demanding an extra search-and-click for each visits, it poses considerable amount of difficulty on <em>web agent</em> programmers, as they now have to parse the page and follow the correct link, which is a highly error-prone thing to do.
+
+<h3>MultiViews: The Easiest L10n Framework</h3>
+<p>
+Of course, it is much better if everybody could see their preferred language automatically. Thankfully, the <em>Content Negotiation</em> feature in HTTP 1.1 addressed this problem quite neatly.
+<p>
+Under this scheme, browsers will always send an <b><tt>Accept-Language</tt></b> header, which specifies one or more preferred language codes; for example, <tt>zh-tw, en-us, en</tt> would mean "Traditional Chinese, American English or English, in this order".
+<p>
+The web server, upon receiving this information, is responsible to present the request content in the most preferred language. Different web servers may implement this process differently; under Apache (the most popular web server), a technique called <b><tt>MultiViews</tt></b> is widely used.
+<p>
+Using <tt>MultiViews</tt>, I will save the English version as <tt>index.html.en</tt> (note the extra file extension), then put this line into Apache's configuration file <tt>httpd.conf</tt> or <tt>.htaccess</tt>:
+<p>
+<PRE>
+ Options <b>+MultiViews</b>
+</PRE>
+<p>
+After that, Apache will examine all requests to <tt>http://www.autrijus.org/index.html</tt>, and see if the client prefers <tt>'en'</tt> in its <tt>Accept-Language</tt> header. Hence, people who prefer English would see the English page; otherwise, the original <tt>index.html</tt> is displayed.
+<p>
+This technique allows gradual introduction of new localized versions of the same documents, so my international friends can contribute more languages over time -- <tt>index.html.fr</tt> for French, <tt>index.html.he</tt> for Hebrew, and so on.
+<p>
+Since a large share of online populace speak only their native language and English, most of the contributed versions would be translated from English, <em>not</em> Chinese. But because both versions represent the same contents, that is not a problem.
+<p>
+... or is it? What if I go back to update the original, <em>Chinese</em> page?
+
+<h3>The Difficulty on Keeping up Translations</h3>
+
+<p>
+As I modify the original page, the first thing I'd notice is that it's impossible to get my French and Hebrew friends to translate <em>from Chinese</em> -- clearly, using English as the <em>base version</em> would be necessary. The same reasoning also applies to most Free Software projects, even if the principal developers do not speak English natively.
+
+<p>
+Moreover, even if it is merely a change to the background color (e.g. <tt><body bgcolor=gold></tt>), I still need to modify all translated pages, in order to keep the layout consistent.
+
+<p>
+Now, if both the layout <em>and</em> contents are changed, things quickly become very complicated. Since the old HTML tags are gone, my translator friends must work from scratch every time! Unless all of them are HTML wizards, errors and conflicts will surely arise. If there are 20 regularly updated pages in my personal site, then pretty soon I will run out of translators -- or even out of friends.
+
+<p>
+As you can see, we need a way to <em>separate data and code</em> (i.e. text and tags), and <em>automate</em> the process of generating localized pages.
+
+<h3>Separate Data and Code with CGI.pm</h3>
+
+<p>
+Actually, the previous sentence pretty much summarized up the modern internationalization(i18n) process: To prepare a web application for localization, one must find a way to separate as much data from code as possible.
+
+<p>
+As the long-established Web development language of choice, Perl offers a bewildering array of modules and toolkits for website construction. The most popular one is probably <tt>CGI.pm</tt>, which has been merged into core perl release since 1997. Let us see a code snippet that uses it to automatically generate translated pages:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 3. Localization with MultiViews and CGI.pm</font></caption>
+<tr><td><PRE>
+use CGI ':standard'; <i># our templating system</i>
+foreach my $language (qw(zh_tw en de fr)) {
+ open OUT, ">index.html.$language" or die $!;
+ print OUT start_html({ title => _(<b>"Audrey.Home"</b>) }),
+ _(<b>"Sorry, this page is under construction."</b>),
+ end_html;
+ sub _ { some_function($language, @_) } <i># XXX: put L10n framework here</i>
+}
+</PRE></td></tr></table>
+
+<p>
+Unlike the HTML pages, this program enforces data/code separation via <tt>CGI.pm</tt>'s HTML-related routines. Tags (e.g. <html>) now become functions calls (<tt>start_html()</tt>), and texts are turned into Perl strings. Therefore, when the localized version is written out to the corresponding static page (<tt>index.html.zh_tw</tt>, <tt>index.html.en</tt>, etc.), the HTML layout will always be identical for each of the four languages listed.
+
+<p>
+<a title="">The <tt>sub _</tt> function is responsible for localizing any text into the current <tt>$language</tt>, by passing the language and text strings to a hypothetical <tt>some_function()</tt>; the latter is known as our <em>localization framework</em>, and we will see three such frameworks in the following section.</a>
+
+<p>
+After writing the snippet, it is a simple matter to <tt>grep</tt> for all strings inside <tt>_(...)</tt>, <em>extract</em> them into a <em>lexicon</em>, and ask translators to fill it out. Note that here <em>lexicon</em> means a set of things that we know how to say in another language -- sometimes single words like (<tt>"Cancel"</tt>), but usually whole phrases (<tt>"Do you want to overwrite?"</tt> or <tt>"5 files found."</tt>). Strings in a lexicon are like entries in travelers' pocket phrasebooks, sometimes with blanks to fill in, as demonstrated in Figure 4:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Figure 4: An English => Haitian lexicon</font></caption>
+<tr>
+<td align="center" bgcolor=black><font color="white">English</font></td>
+<td align="center" bgcolor=black><font color="white">Haitian</font></td>
+</tr><tr><td align="center">
+This costs ___ dollars.
+</td><td>
+Bagay la kute ___ dola yo.
+</td></tr></table>
+
+<p>
+Ideally, the translator should focus solely on this lexicon, instead of peeking at HTML files or the source code. But there is the rub: different localization frameworks use different lexicon formats, so one has to choose the framework that suits the project best.
+
+<h2>Localization Frameworks</h2>
+
+<p align="right" class=right>
+<i>``It is more complicated than you think.''<br>
+-- Eighth Networking Truth, RFC 1925</i>
+
+<p>
+To implement the <tt>some_function()</tt> in figure 4, one needs a library to manipulate lexicon files, look up the corresponding strings in it, and maybe incrementally extract new strings to the lexicon. These are collectively known as a <em>localization framework</em>.
+
+<p>
+From my observation, frameworks mostly differ in their idea about how lexicons should be structured. Here, I will discuss the Perl interface for three such frameworks, starting from the venerable <tt>Msgcat</tt>.
+
+<h3>Msgcat -- Lexicons are Arrays</h3>
+
+<p>
+As one of the earliest L10n frameworks and part of XPG3/XPG4 standards, <tt>Msgcat</tt> enjoys ubiquity in all Un*x platforms. It represents the first-generation paradigm of lexicons: treat entries as numbered strings in an array (a.k.a. <em>message catalog</em>). This approach is straightforward to implement, needs little memory, and is very fast to look up. The <em>resource files</em> used in Windows programming and other platforms uses basically the same idea.
+
+<p>
+For each page or source file, <tt>Msgcat</tt> requires us to make a lexicon file for each language, as shown below:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 4. A <tt>Msgcat</tt> lexicon</font></caption>
+<tr><td><PRE>
+$set <b>7</b> <i># $Id: nls/de/index.pl.m</i>
+<b>1</b> Audrey'.Haus
+<b>2</b> Wir bitten um Entschudigung. Diese Seite ist im Aufbau.
+</PRE></td></tr></table>
+
+<p>
+The above file contains the German translation for each text strings within <tt>index.html</tt>, which is represented by an unique <em>set number</em>, <b>7</b>. Once we finished building the lexicons for all pages, the <tt>gencat</tt> utility is then used to generate the binary lexicon:
+<p>
+<pre>
+ % gencat nls/de.cat nls/de/*.m
+</pre>
+
+<p>
+It is best to imagine the internals of the binary lexicon as a two-dimensional array, as shown in figure 5:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Figure 5: The content of <tt>nls/de.cat</tt></font></caption>
+<tr>
+<td align="right" bgcolor=black><font color="white">set_id<br>msg_id</font></td>
+<td align="center" bgcolor=black><font color="white">1</font></td>
+<td align="center" bgcolor=black><font color="white">2</font></td>
+<td align="center" bgcolor=black><font color="white">3</font></td>
+<td align="center" bgcolor=black><font color="white">4</font></td>
+<td align="center" bgcolor=black><font color="white">5</font></td>
+<td align="center" bgcolor=black><font color="white">6</font></td>
+<td align="center" bgcolor=black><font color="white">7</font></td>
+<td align="center" bgcolor=black><font color="white">8</font></td>
+<td align="center" bgcolor=black><font color="white">9</font></td>
+</tr><tr>
+<td align="center" bgcolor=black><font color="white">1</font></td>
+<td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td>
+<td><tt>Audrey'.Haus</tt></td>
+<td><i>...</i></td><td><i>...</i></td>
+</tr><tr>
+<td align="center" bgcolor=black><font color="white">2</font></td>
+<td><i>...</i></td><td></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td>
+<td><tt>Wir bitten um Entschudigung...</tt></td>
+<td><i>...</i></td><td><i>...</i></td>
+</tr><tr>
+<td align="center" bgcolor=black><font color="white">3</font></td>
+<td><i>...</i></td><td></td><td><i>...</i></td><td><i>...</i></td><td></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td>
+</tr></table>
+
+<p>
+To read from the lexicon file, we use the Perl module <tt>Locale::Msgcat</tt>, available from CPAN (the Comprehensive Perl Archive Network), and implement the earlier <tt>sub _()</tt> function like this:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 5. Sample usage of <tt>Locale::Msgcat</tt></font></caption>
+<tr><td><PRE>
+use Locale::Msgcat;
+my $cat = Locale::Msgcat->new;
+$cat->catopen("nls/$language.cat", 1); <i># it's like a 2D array</i>
+sub _ { $cat->catgets(<b>7</b>, @_) } <i># <b>7</b> is the set_id for index.html</i>
+print _(<b>1</b>, "Audrey.House"); <i># <b>1</b> is the msg_id for this text</i>
+</PRE></td></tr></table>
+
+<p>
+Note that only the <tt>msg_id</tt> matters here; the string <tt>"Audrey.House"</tt> is only used as an optional fall-back when the lookup failed, as well as to improve the program's readability.
+
+<p>
+Because <tt>set_id</tt> and <tt>msg_id</tt> must both be <em>unique</em> and <em>immutable</em>, future revision may only delete entries, and never reassign the number to represent other strings. This characteristic makes revisions very costly, as observed by Drepper et al in the GNU gettext manuals:
+
+<p>
+<blockquote>
+Every time he comes to a translatable string he has to define a number
+(or a symbolic constant) which has also be defined in the message
+catalog file. He also has to take care for duplicate entries,
+duplicate message IDs etc. If he wants to have the same quality in the
+message catalog as the GNU <tt>gettext</tt> program provides he also has to
+put the descriptive comments for the strings and the location in all
+source code files in the message catalog. This is nearly a Mission:
+Impossible.
+</blockquote>
+
+<p>
+Therefore, one should consider using <tt>Msgcat</tt> only if the lexicon is very stable.
+<p>
+Another shortcoming that had plagued <tt>Msgcat</tt>-using programs is the <em>plurality</em> problem. Consider this code snippet:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 6. Incorrect plural form handling</font></caption>
+<tr><td><PRE>
+printf(_(8, "<b>%d</b> files were deleted."), $files);
+</PRE></td></tr></table>
+
+<p>
+This is obviously incorrect when <tt>$files == 1</tt>, and <tt>"<b>%d</b> file(s) were deleted"</tt> is grammatically invalid as well. Hence, programmers are often forced to use two entries:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 7. English-specific plural form handling</font></caption>
+<tr><td><PRE>
+printf(($files == 1) ? _(8, "<b>%d</b> file was deleted.")
+ : _(9, "<b>%d</b> files were deleted."), $files);
+</PRE></td></tr></table>
+
+<p>
+But even that is still bogus, because it is English-specific -- French uses singular with <tt>($files == 0)</tt>, and Slavic languages has three or four plural forms! Trying to retrofit those languages to the <tt>Msgcat</tt> infrastructure is often a futile exercise.
+
+<h3>Gettext -- Lexicons are Hashes</h3>
+
+Due to the various problems of <tt>Msgcat</tt>, the GNU Project has developed its own implementation of the Uniforum <tt>Gettext</tt> interface in 1995, written by Ulrich Drepper. It had since become the <em>de facto</em> L10n framework for C-based free software projects, and has been widely adopted by C++, Tcl and Python programmers.
+
+<p>
+Instead of requiring one lexicon for each source file, <tt>Gettext</tt> maintains a single lexicon (called a <em>PO file</em>) for each language of the entire project. For example, the German lexicon <tt>de.po</tt> for the homepage above would look like this:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 8. A <tt>Gettext</tt> lexicon</font></caption>
+<tr><td><PRE>
+#: index.pl:4
+msgid "Audrey.Home"
+msgstr "Audrey'.Haus"
+
+#: index.pl:5
+msgid "Sorry, this site is under construction."
+msgstr "Wir bitten um Entschudigung. Diese Seite ist im Aufbau."
+</PRE></td></tr></table>
+
+<p>
+
+The <tt>#:</tt> lines are automatically generated from the source file by the program <tt>xgettext</tt>, which can extract strings inside invocations to <tt>gettext()</tt>, and sort them out into a lexicon.
+
+<p>
+Now, we may run <tt>msgfmt</tt> to compile the binary lexicon <tt>locale/de/LC_MESSAGES/web.mo</tt> from <tt>po/de.po</tt>:
+
+<pre>
+ % msgfmt locale/de/LC_MESSAGES/web.mo po/de.po
+</pre>
+
+<p>
+We can then access the binary lexicon using <tt>Locale::gettext</tt> from CPAN, as shown below:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 9. Sample usage of <tt>Locale::gettext</tt></font></caption>
+<tr><td><PRE>
+use POSIX;
+use Locale::gettext;
+POSIX::setlocale(LC_MESSAGES, $language); <i># Set target language</i>
+textdomain("web"); <i># Usually the same as the application's name</i>
+sub _ { gettext(@_) } <i># it's just a shorthand for gettext()</i>
+print _("Sorry, this site is under construction.");
+</PRE></td></tr></table>
+
+<p>
+Recent versions (glibc 2.2+) of <tt>gettext</tt> had also introduced the <tt>ngettext("%d file", "%d files", $files)</tt> syntax. Unfortunately, <tt>Locale::gettext</tt> does not support that interface yet.
+
+<p>
+Also, <tt>gettext</tt> lexicons support multi-line strings, as well as reordering via <tt>printf</tt> and <tt>sprintf</tt>:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 10. A multi-line entry with numbered arguments</font></caption>
+<tr><td><PRE>
+msgid ""
+"This is a multiline string"
+"with <b>%1$s</b> and <b>%2$s</b> as arguments"
+msgstr ""
+"ã“れã¯å¤šç·šã²ã‚‚変数ã¨ã—ã¦"
+"<b>%2$s</b> 㨠<b>%1$s</b> ã®ã§ã‚ã‚‹"
+</PRE></td></tr></table>
+
+<p>
+Finally, GNU <tt>gettext</tt> comes with a very complete tool chain (msgattrib, msgcmp, msgconv, msgexec, msgfmt, msgcat, msgcomm...), which greatly simplified the process of merging, updating and managing lexicon files.
+
+<h3>Locale::Maketext -- Lexicons are Dispatch Tables!</h3>
+
+<p>
+First written in 1998 by Sean M. Burke, the <tt>Locale::Maketext</tt> module was revamped in May 2001 and included in Perl 5.8 core.
+
+<p>
+Unlike the function-based interface of <tt>Msgcat</tt> and <tt>Gettext</tt>, its basic design is object-oriented, with <tt>Locale::Maketext</tt> as an abstract base class, from which a <em>project class</em> is derived. The project class (with a name like <tt>MyApp::L10N</tt>) is in turn the base class for all the <em>language classes</em> in the project (with names like <tt>MyApp::L10N::it</tt>, <tt>MyApp::L10N::fr</tt>, etc.).
+
+<p>
+A language class is really a perl module containing a <tt>%Lexicon</tt> hash as class data, which contains strings in the native language (usually English) as keys, and localized strings as values. The language class may also contain some methods that are of use in interpreting phrases in the lexicon, or otherwise dealing with text in that language.
+
+<p>
+Here is an example:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 11. A <tt>Locale::Maketext</tt> lexicon and its usage</font></caption>
+<tr><td><PRE>
+package MyApp::L10N;
+use base 'Locale::Maketext';
+
+package MyApp::L10N::de;
+use base 'MyApp::L10N';
+our %Lexicon = (
+ "[<b>quant</b>,_1,camel was,camels were] released." =>
+ "[<b>quant</b>,_1,Kamel wurde,Kamele wurden] freigegeben.",
+);
+
+package main;
+my $lh = MyApp::L10N->get_handle('de');
+print $lh->maketext("[<b>quant</b>,_1,camel was,camels were] released.", 5);
+</PRE></td></tr></table>
+
+<p>
+Under its <em>square bracket notation</em>, translators can make use of various linguistic-related functions inside their translated strings. The example above highlights includes built-in plurals and quantifiers support; for languages with other kinds of plural-form characteristics, it is a simple matter of implementing a corresponding <tt>quant()</tt> function. Ordinates and time formats are easy to add, too.
+
+<p>
+Each language class may also implement an <tt>->encoding</tt> method to describe the encoding of its lexicons, which may be linked with <tt>Encode</tt> for transcoding purposes. Language families are also inheritable and subclassable: missing entries in <tt>fr_ca.pm</tt> (Canadian French) would fallbacks to <tt>fr.pm</tt> (Generic French).
+
+<p>
+The handy built-in method <tt>->get_handle()</tt> with no arguments magically detects HTTP, POSIX and Win32 locale settings in CGI, mod_perl or command line; it spares the programmer to parse those settings manually.
+
+<p>
+However, <tt>Locale::Maketext</tt> is not without problems. The most serious issue is its lacking of a <em>toolchain</em> like GNU <tt>Gettext</tt>'s, due to the extreme flexibility of lexicon classes. For the same reason, there are also fewer support in text editors (e.g. the <em>PO Mode</em> in Emacs).
+
+<p>
+Finally, since different projects may use different styles to write the language class, the translator must know some basic Perl syntax -- or somebody has to type in for them.
+
+<h3>Locale::Maketext::Lexicon -- The Best of Both Worlds</h3>
+
+<p>
+Irritated by the irregularity of <tt>Locale::Maketext</tt> lexicons, I implemented a home-brew lexicon format for my company's internal use in May 2002, and asked the <em>perl-i18n</em> mailing list for ideas and feedbacks. Jesse Vincent suggested: "Why not simply standardize on <tt>Gettext</tt>'s PO File format?", so I implemented it to accept lexicons in various formats, handled by different <em>lexicon backend</em> modules. Thus, <tt>Locale::Maketext::Lexicon</tt> was born.
+
+<p>
+The design goal was to combine the flexibility of <tt>Locale::Maketext</tt> lexicon's expression with standard formats supported by utilities designed for <tt>Gettext</tt> and <tt>Msgcat</tt>. It also supports the <tt>Tie</tt> interface, which comes in handy for accessing lexicons stored in relational databases or DBM files.
+
+<p>
+The following program demonstrates a typical application using <tt>Locale::Maketext::Lexicon</tt> and the extended PO File syntax supported by the <tt>Gettext</tt> backend:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 12. A sample application using <tt>Locale::Maketext::Lexicon</tt></font></caption>
+<tr><td bgcolor=black align=right><PRE><font color=white>1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</font></PRE></td><td><PRE>
+use CGI ':standard';
+use base 'Locale::Maketext'; <i># inherits get_handle()</i>
+
+<i># Various lexicon formats and sources</i>
+use Locale::Maketext::Lexicon {
+ en => ['Auto'], fr => ['Tie' => 'DB_File', 'fr.db'],
+ de => ['Gettext' => \*DATA], zh_tw => ['Gettext' => 'zh_tw.mo'],
+};
+
+<i># Ordinate functions for each subclasses of 'main'</i>
+use Lingua::EN::Numbers::Ordinate; use Lingua::FR::Numbers::Ordinate;
+sub en::ord { ordinate($_[1]) } sub fr::ord { ordinate_fr($_[1]) }
+sub de::ord { "$_[1]." } sub zh_tw::ord { "第 $_[1] 個" }
+
+my $lh = __PACKAGE__->get_handle; <i># magically gets the current locale</i>
+sub _ { $lh->maketext(@_) } <i># may also convert encodings if needed</i>
+
+print header, start_html, <i># [<b>*</b>,...] is a shorthand for [<b>quant</b>,...]</i>
+ _("You are my [<b>ord</b>,_1] guest in [<b>*</b>,_2,day].", $hits, $days), end_html;
+
+__DATA__
+# <i>The German lexicon, in extended PO File format</i>
+msgid "You are my <b>%ord(%1)</b> guest in <b>%*(%2,day)</b>."
+msgstr "Innerhalb <b>%*(%2,Tages,Tagen)</b>, sie sind mein <b>%ord(%1)</b> Gast."
+</PRE></td></tr></table>
+
+<p>
+Line 2 tells the current package <tt>main</tt> to inherit from <tt>Locale::Maketext</tt>, so it could acquire the <tt>get_handle</tt> method. Line 5-8 builds four <em>language classes</em> using a variety of lexicon formats and sources:
+
+<ul>
+<li>The <em>Auto</em> backend tells <tt>Locale::Maketext</tt> that no localizing is needed for the English language -- just use the lookup key as the returned string. It is especially useful if you are just starting to prototype a program, and does not want deal with the localization files yet.
+<li>The <em>Tie</em> backend links the French <tt>%Lexicon</tt> hash to a Berkeley DB file; entries will then be fetched whenever it is used, so it will not waste any memory on unused lexicon entries.
+<li>The <em>Gettext</em> backend reads a compiled MO file from disk for Chinese, and reads the German lexicon from the DATA filehandle in PO file format.
+</ul>
+
+<p>
+Line 11-13 implements the <tt>ord</tt> method for each language subclasses of the package <tt>main</tt>, which converts its argument to ordinate numbers (1th, 2nd, 3rd...) in that language. Two CPAN modules are used to handle English and French, while German and Chinese only needs straightforward string interpolation.
+
+<p>
+Line 15 gets a <em>language handle</em> object for the current package. Because we did not specify the language argument, it automatically guesses the current locale by probing the <tt>HTTP_ACCEPT_LANGUAGE</tt> environment variable, POSIX <tt>setlocale()</tt> settings, or via <tt>Win32::Locale</tt> on Windows. Line 16 sets up a simple wrapper funciton that passes all arguments to the handle's <tt>maketext</tt> method.
+
+<p>
+Finally, line 18-19 prints a message containing one string to be localized. The first argument <tt>$hits</tt> will be passed to the <tt>ord</tt> method, and the second argument <tt>$days</tt> will call the built-in <tt>quant</tt> method -- the <tt>[*...]</tt> notation is a shorthand for the previously discussed <tt>[quant,...]</tt>.
+
+<p>
+Line 22-24 is a sample lexicon, in extended PO file format. In addition to ordered arguments via <tt>%1</tt> and <tt>%2</tt>, it also supports <tt>%function(args...)</tt> in entries, which will be transformed to <tt>[function,args...]</tt>. Any <tt>%1</tt>, <tt>%2</tt>... sequences inside the <em>args</em> will have their percent signs (<tt>%</tt>) replaced by underscores (<tt>_</tt>).
+
+<h2>Case Studies</h2>
+
+<p class="right" align="right">
+<i>``One size never fits all.''<br>
+-- Tenth Networking Truth, RFC 1925</i>
+
+<p>
+Armed with the understanding of localization frameworks, let us see how it fits into real-world applications and technologies.
+
+<p>
+For web applications, the place to implement a L10n framework is almost inevitably its <em>representation system</em>, also known as <em>templating system</em>, because that layer determines the extent of an application's data/code separation. For example, the <em>Template Toolkit</em> encourages a clean 3-tier data/code/template model, while the equally popular <em>Mason</em> framework lets you easily mix perl code in a template. In this section, we will survey L10n strategies for those two different frameworks, and the general principle should also apply to <em>AxKit</em>, <em>HTML::Embperl</em>, and other templating systems.
+
+<h3>Request Tracker (Mason)</h3>
+
+<p>
+The <em>Request Tracker</em> is the first application that uses <tt>Locale::Maketext::Lexicon</tt> as its L10n framework. The <em>base language class</em> is <tt>RT::I18N</tt>, with subclasses reading <tt>*.po</tt> files stored in the same directory.
+
+<p>
+Additionally, its <tt>->maketext</tt> method was overridden to uses <tt>Encode</tt> (or in pre-5.8 versions of perl, my <tt>Encode::compat</tt>) to return UTF-8 data on-the-fly. For example, Chinese translator may submit lexicons encoded in <tt>Big5</tt>, but the system will always handle them natively as Unicode strings.
+
+<p>
+In the application's Perl code, all objects use the <Tt>$self->loc</tt> method, inherited from <tt>RT::Base</tt>:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 13. RT's L10n implementation</font></caption>
+<tr><td><PRE>
+sub RT::Base::loc
+ { $self->CurrentUser->loc(@_) }
+sub RT::CurrentUser::loc
+ { $self->LanguageHandle->maketext(@_) }
+sub RT::CurrentUser::LanguageHandle
+ { $self->{'LangHandle'} ||= RT::I18N->get_handle(@_) }
+</PRE></td></tr></table>
+
+<p>
+As you can see, the current user's language settings is used, so different users can use the application in different languages simultaneously. For Mason templates, two styles were devised:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 14. Two ways to mark strings in Mason templates</font></caption>
+<tr><td><PRE>
+% $m->print(<b>loc(</b>"<b>Another line of text</b>", $args...<b>)</b>);
+<&<b>|/l</b>, $args...&><b>Single line of text</b></&>
+</PRE></td></tr></table>
+<p>
+The first style, used in embedded perl chunks and <tt><%PERL></tt> sections, is made possible by exporting a global <tt>loc()</tt> function to the Mason interpreter; it automatically calls the current user's <tt>->loc</tt> method described above.
+
+<p>
+The second style uses the <em>filter component</em> feature in <tt>HTML::Mason</tt>, which takes the enclosed <tt>Single line of text</tt>, passes it to the <tt>/l</tt> component (possibly with arguments), and displays the returned string. Here is the implementation of that component:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 15. Implementation of the <tt>html/l</tt> filter component</font></caption>
+<tr><td><PRE>
+% my $hand = $session{'CurrentUser'}->LanguageHandle;
+% $m->print($hand->maketext($m->content, @_));
+</PRE></td></tr></table>
+
+<p>
+With these constructs, it is a simple matter of extracting messages out of existing templates, comment them, and send to the translators. The initial extraction for 700+ entries took one week; the whole i18n/L10n process took less than two months.
+
+<h3>Slash (Template Toolkit)</h3>
+
+<p>
+Slash -- Slashdot Like Automated Storytelling Homepage -- is the code that runs Slashdot. More than that, however, Slash is an architecture for putting together web sites, built upon Andy Wardley's <em>Template Toolkit</em> module.
+
+<p>
+Due to the clean design of TT2, Slash features a careful separation of code and text, unlike RT/Mason. This largely eliminated the need to localize inside Perl source code.
+
+<p>
+Previous to this article's writing, various whole-template localizations based on the theme system had been attempted, including Chinese, Japanese, and Hebrew versions. However, merging with a new version was very difficult (not to mention plugins), and translations tend to lag behind a lot.
+
+<p>
+Now, let us consider a better approach: An <em>auto-extraction</em> layer above the template provider, based on <tt>HTML::Parser</tt> and <tt>Template::Parser</tt>. Its function would be like this:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 16. Input and output of the TT2 extraction layer</font></caption>
+<tr><td bgcolor=black><font color=white>Input</font></td><td><PRE>
+<B>from the [% story.dept %] dept.</B>
+</PRE></td></tr>
+<tr><td bgcolor=black><font color=white>Output</font></td><td><PRE>
+<B>[%<b>|loc(</b> story.dept <b>)</b>%]from the [<b>_1</b>] dept.[%END%]</B>
+</PRE></td></tr></table>
+
+<p>
+The acute reader will point out that this layer suffer from the same linguistic problems as <tt>Msgcat</tt> does -- what if we want to make ordinates from <tt>[% story.dept %]</tt>, or expand the <tt>dept.</tt> to <tt>department</tt> / <tt>departments</tt>? The same problem has occurred in RT's web interface, where it had to localize messages returned by external modules, which may already contain interpolated variables, e.g. <tt>"Successfully deleted 7 ticket(s) in 'c:\temp'."</tt>.
+
+<p>
+My solution to this problem is to introduce a <em>fuzzy match</em> layer with the module <tt>Locale::Maketext::Fuzzy</tt>, which matches the interpolated string against the list of <em>candidate entries</em> in the current lexicon, to find one that can possibly yield the string (e.g. <tt>"Successfully deleted [*,_1,ticket] in '[_2]'."</tt>). If two or more candidates are found, -- after all, <tt>"Successfully [_1]."</tt> also matches the same string -- tie-breaking heuristics are used to determine the most likely candidate.
+
+<p>
+Combined with <tt>xgettext.pl</tt>, developers can supply compendium lexicons along with each plugin/theme, and the Slash system would employ a multi-layer lookup mechanism: Try plugin-specific entries first; then the theme's; then fallback to the global lexicon.
+
+<h2>Summary</h2>
+
+<p class="right" align="right">
+<i>``...perfection has been reached not when there is nothing left to add, but when there is nothing left to take away.''<br>
+-- Twelfth Networking Truth, RFC 1925</i>
+
+<p>
+From the two case studies above, it is quite easy to see an emergent pattern of how such efforts are carried. This section presents a 9-step guide in localizing <em>existing</em> web applications, as well as tips of how to implement them with minimal hassles.
+
+<h3>The Localization Process</h3>
+
+We can summarize the localization process as several steps, each depending on previous ones:
+
+<ol>
+<li>Assess the website's templating system
+<li>Choose a localization framework and hook it up
+<li>Write a program to locate text strings in templates, and put filters around them
+<li>Extract a test lexicon; fix obvious problems manually
+<li>Locate text strings in the source code by hand; replace them with <tt><b>_(</b>...<b>)</b></tt> calls
+<li>Extract another test lexicon and machine-translate it
+<li>Try the localized version out; fix any remaining problems
+<li>Extract the beta lexicon; mail it to your translator teams for review
+Fix problems reported by translators; extract the official lexicon and mail it out!
+<li>Periodically notify translators of new lexicon entries before each release
+</ol>
+
+Following these steps, one could manage a L10n project fairly easily, and keep the translations up-to-date and minimize errors.
+
+<h3>Localization Tips</H3>
+
+Finally, here are some tips for localizing Web applications, and other softwares in general:
+
+<ul>
+<li><em>Separate data and code</em>, both in design and in practice
+<li>Don't work on i18n/L10n before the website or application takes shape
+<li>Avoid graphic files with text in them
+<li>Leave enough spaces around labels and buttons -- do not overcrowd the UI
+<li>Use complete sentences, instead of concatenated fragments (see listing 17):
+</ul>
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 17. Fragmented vs. complete sentences</font></caption>
+<tr><td><PRE>
+_("Found ") . $files . _(" file(s)."); <i># Fragmented sentence - wrong!</i>
+sprintf(_("Found %s file(s)."), $files); <i># Complete (with sprintf)</i>
+_("Found [*,_1,file].", $files); <i># Complete (Locale::Maketext)</i>
+</PRE></td></tr></table>
+
+<ul>
+<li>Distinguish the same string in different contexts<br>
+e.g. "Home" in RT used to mean both "Homepage" and "Home Phone No."
+<li>Work with your translators as equals; do not apply lexicon patches by yourself
+<li>One person doing draft translations works best
+<li>In lexicons, provide as much comments and metadata as possible:
+</ul>
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>Listing 18. Comments in lexicons</font></caption>
+<tr><td><PRE>
+#: lib/RT/Transaction_Overlay.pm:579
+#. ($field, $self->OldValue, $self->NewValue)
+# <i>Note that 'changed to' here means 'has been modified to...'.</i>
+msgid "<b>%1 %2</b> changed to <b>%3</b>"
+msgstr "<b>%1 %2</b> cambiado a <b>%3</b>"
+</PRE></td></tr></table>
+
+<p>
+Using the <tt>xgettext.pl</tt> utility provided in the <tt>Locale::Maketext::Lexicon</tt> package, the source file, line number (marked by <tt>#:</tt>) and variables (marked by <tt>#.</tt>) can be deduced automatically and incrementally. It would also be very helpful to clarify the meaning of short or ambiguous with normal comments (marked by <tt>#</tt>), as shown in listing 18 above.
+
+<h2>Conclusion</h2>
+
+<p>
+For countries with language dissimilar to English, localization efforts is often the prerequisite for people to participate in other Free Software projects. In Taiwan, L10n projects like the CLE (Chinese Linux Environment), Debian-Chinese and FreeBSD-Chinese were (and still are) the principal place where community contributions are made. However, such efforts are also historically time-consuming, error-prone jobs, partly because of English-specific frameworks and rigid coding practices used by existing applications. The <em>entry barrier</em> for translators was unnecessarily high.
+
+<p>
+On the other hand, ever-increasing internationalization of the Web makes it increasingly likely that the interface to Web-based dynamic content service will be localized to two or more languages. For example, Sean M. Burke led enthusiastic users to localize the popular <em>Apache::MP3</em> module, which powers home-grown Internet jukeboxes everywhere, to dozens of languages in 2002. The module's author, Lincoln D. Stein, did not involve with the project at all -- all he needed to do was integrating the i18n patches and lexicons into the next release.
+
+<p>
+The Free Software projects are not abstractions filled with code, but rather depends on people caring enough to share code, as well as sharing useful feedback in order to improve each other's code. Hence, it is my sincere hope that techniques presented in this article will encourage programmers and eager users to actively internationalize existing applications, instead of passively translating for the relatively few applications with established i18n frameworks.
+
+<h2>Acknowledgments</h2>
+
+<p>
+Thanks to Jesse Vincent for suggesting <tt>Locale::Maketext::Lexicon</tt> to be written, and for allowing me to work with him on RT's L10n model. Thanks also to Sean M. Burke for coming up with <tt>Locale::Maketext</tt>, and encouraging me to experiment with alternative Lexicon syntaxes.
+
+<p>
+Thanks also go to my brilliant colleagues in OurInternet, Inc. for the hard work they did on localizing web applications: Hsin-Chan Chien, Chia-Liang Kao, Whiteg Weng and Jedi Lin. Also thanks to my fellow translators of the Llama book (<em>Learning Perl</em>), who showed me the power of distributed translation teamworks.
+
+<p>
+I would also like to thank to Nick Ing-Simmons, Dan Kogai and Jarkko Hietaniemi for teaching me how to use the <tt>Encode</tt> module, Bruno Haible for his kind permission for me to use his excellent work on GNU libiconv, and Tatsuhiko Miyagawa for proofreading early versions of my <tt>Locale::Maketext::Lexicon</tt> module. Thanks!
+
+<p>
+Finally, if you decide to follow the steps in this article and participate in software internationalization and localization, then you have my utmost gratitude; let's make the Web a truly <em>World Wide</em> place.
+
+<h2>Bibliography</h2>
+
+<p>
+Alvestrand, Harald Tveit. 1995. <em>RFC 1766: Tags for the Identification of Languages.</em>, <tt>ftp://ftp.isi.edu/in-notes/rfc1766.txt</tt>
+
+<p>
+Callon, Ross, editor. 1996. <em>RFC 1925: The Twelve Networking Truths.</em>, <tt>ftp://ftp.isi.edu/in-notes/rfc1925.txt</tt>
+
+<p>
+Drepper, Ulrich, Peter Miller, and François Pinard. 1995-2001. GNU <tt>gettext</tt>. Available in <tt>ftp://prep.ai.mit.edu/pub/gnu/</tt>, with extensive documents in the distribution package.
+
+<p>
+Burke and Lachler. 1999. <em>Localization and Perl: gettext breaks, Maketext fixes</em>, first published in The Perl Journal, issue 13.
+
+<p>
+Burke, Sean M. 2002. <em>Localizing Open-Source Software</em>, first published in the The Perl Journal, Fall 2002 edition.
+
+<p>
+W3C internationalization activity statement, 2001, <tt>http://www.w3.org/International/Activity.html</tt>
+
+<p>
+Mozilla i18n & L10n guidelines, 1999, <tt>http://www.mozilla.org/docs/refList/i18n/</tt>
+
+</body></html>
Added: Locale-Maketext-Lexicon/docs/webl10n.zh-tw.html
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/docs/webl10n.zh-tw.html Sun Apr 2 05:40:20 2006
@@ -0,0 +1,758 @@
+<html><head><title>用 Perl 進行網站本土化</title>
+<style><!--
+P {text-align: justify}
+P.right {text-align: right}
+--></style></head><body>
+
+<h1>用 Perl 進行網站本土化</h1>
+<p class="right" align="right">
+å”å®—æ¼¢<br>
+傲爾網<br>
+2002年12月
+
+<p>
+<h2>摘è¦</h2>
+
+<p>
+å°æ‡‰ç”¨ç¨‹å¼é€²è¡Œåœ‹éš›åŒ–(Internationalization,i18n)的目的,是在使它支æ´å¤šç¨®èªžè¨€ã€æ—¥æœŸã€è²¨å¹£æ ¼å¼ï¼Œä»¥åŠå„地的習俗ç‰ã€Œåœ°å€è¨å®šï¼ˆlocale)ã€ã€‚接下來,本土化(Localization,l10nï¼‰å‰‡è² è²¬å¯¦éš›å°‡è»Ÿé«”è½‰è¯ï¼Œä»¥åˆ‡åˆç‰¹å®šåœ°å€çš„使用者需求。當å‰ï¼Œç¶²é 應用程å¼ï¼ˆWeb Application)由於以文å—ä½œç‚ºç•Œé¢æè¿°çš„æ ¼å¼ï¼Œå·²æˆç‚ºæœ€ç†±é–€çš„æœ¬åœŸåŒ–å°è±¡ä¹‹ä¸€ã€‚
+<p>
+åœ¨è‡ªç”±è»Ÿé«”çš„ä¸–ç•Œè£¡ï¼Œè¨±å¤šæœ€å…·å½ˆæ€§ã€æœ€å—æ¡è¿Žçš„æŠ€è¡“,是用Perl語言開發的。å°ç¶²ç«™æ‡‰ç”¨ç¨‹å¼çš„開發者來說,Perl也是長期以來的ä¸äºŒé¸æ“‡ã€‚æœ¬ç¯‡æ–‡ç« æ˜¯ç†è€…å°Perl應用程å¼é€²è¡Œä¸è¯çš„經驗談,包å«å¯¦ä½œæ–¹å¼ã€å¸¸ç”¨å·¥å…·çš„優劣之處,以åŠç®¡ç†æœ¬åœŸåŒ–專案時需注æ„çš„äº‹é …ã€‚
+
+<h2>簡介</h2>
+
+<p class="right" align="right">
+「在這個世界上,人類的語言為數ä¸å°‘。ã€<br>
+  ï¼ï¼Harald Tveit Alvestrand,RFC 1766,『語言è˜åˆ¥æ¨™è¨˜ã€
+<p>
+網站åŠç¶²é 應用程å¼ï¼Œç‚ºä»€éº¼è¦ä½œæœ¬åœŸåŒ–呢?
+<p>
+請讀者想åƒä¸€ä¸‹ï¼šå‡è¨æœ‰äººåœ¨å…¨çƒè³‡è¨Šç¶²ä¸Šæå‡ºé€™å€‹å•題,許多人用ä¸åŒçš„語言æå‡ºæ„見ã€å½¼æ¤è¨Žè«–。身為其ä¸çš„一份åï¼Œä½ å¯èƒ½æœƒè½åˆ°é€™äº›è«–點:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1><b>圖1: 本土化的ç†ç”±(䏿–‡åŒ–之å‰)</b></font></caption>
+<tr><td><ul>
+<li>ИноÑÑ‚Ñ€Ð°Ð½Ð½Ð°Ñ Ð²Ð°Ð»ÑŽÑ‚Ð°, формат даты, Ñзык и обычаи могут казатьÑÑ Ð½Ð°Ð¼ пугающими
+<li>Menschen sind produktiver wenn sie in ihrer gewohnten Umgebung arbeiten
+<li>Tas veicina daudz labâku sapraðanu un mijiedarbîbu starp daâdâm kultrâm
+<li>Un progretto con molti collaboratori internazionali si evolverá piú in fretta e meglio
+<li>地区化的过程, 有助於软件的模å—化与å¯ç§»æ¤æ€§
+</ul></td></tr></table>
+
+<p>
+ä¸å¹¸çš„æ˜¯ï¼Œé€™äº›è«–é»žä¸¦éžæ¯å€‹äººéƒ½çœ‹å¾—æ‡‚ã€‚é€™å°±é€ æˆäº†ã€Œèªžè¨€éšœç¤™ã€ï¼ï¼èƒ½å…±åŒè¨Žè«–çš„å°è±¡ï¼Œå¾€å¾€ä¾·é™åœ¨å°‘數語言相åŒã€æ–‡åŒ–相近的「地å€ç¤¾ç¾¤ã€ä¸ã€‚
+<p>
+ç„¶è€Œï¼Œæˆ‘å€‘çœ‹ä¸æ‡‚的論點往往很有é“ç†ï¼Œä¸¦ä¸”èƒ½å¸¶ä¾†æ–°çš„çœæ€ã€‚所以,最好能有人將這些æ„è¦‹ã€æ“作界é¢åŠå…¶ä»–è³‡æ–™ç¿»è¯æˆæˆ‘們看得懂的文å—:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>圖2: 本土化的ç†ç”±(䏿–‡åŒ–之後)</font></caption>
+<tr><td><ul>
+<li>è¦é·å°±å¤–åœ°çš„èªžè¨€ã€æ—¥æœŸã€è²¨å¹£åŠç¿’俗,是件煩人的事。
+<li>人在熟悉的æ“作環境下,會比較有生產力。
+<li>它能促進文化間的çžè§£èˆ‡äº¤æµã€‚
+<li>æ“æœ‰è¨±å¤šåœ‹éš›åŒå¥½çš„專案,進æ¥çš„速度更快,å“質也更好。
+<li>本土化的éŽç¨‹ï¼Œæœ‰åŠ©æ–¼è»Ÿé«”çš„æ¨¡çµ„åŒ–èˆ‡å¯ç§»æ¤æ€§ã€‚
+</ul></td></tr></table>
+
+<p>
+æ£å¦‚åŒä¸Šåˆ—è«–é»žæ‰€è¿°ï¼Œé€šå¸¸ç„¡æ³•è¦æ±‚æ‰€æœ‰äººã€Œæ›¸åŒæ–‡ã€ï¼Œä¸€å¾‹æŽ¡ç”¨æ‹‰ä¸èªžã€é€šç”¨èªžã€ä¸–界語ã€é‚è¼¯èªžæˆ–æ˜¯è‹±æ–‡ã€‚é€™æ™‚ï¼Œå°±éœ€è¦æœ¬åœŸåŒ–了。
+
+<p>
+å°å°ˆå±¬è»Ÿé«”è€Œè¨€ï¼Œæœ¬åœŸåŒ–é€šå¸¸æ˜¯æ‰“å…¥åœ‹å¤–å¸‚å ´çš„å…ˆæ±ºæ¢ä»¶ã€‚若是æŸåœ°çš„é æœŸåˆ©æ½¤ä½Žæ–¼æœ¬åœŸåŒ–çš„æˆæœ¬ï¼Œå» å•†ä¾¿ä¸æœƒé€²è¡Œç¿»è¯ï¼›åœ¨æ²’有æºç¢¼çš„æƒ…æ³ä¸‹ï¼Œç•¶åœ°çš„使用者è¦è‡ªå·±é€²è¡Œç¿»è¯ï¼Œä¾¿æ˜¯ä»¶å›°é›£ï¼ˆä¹Ÿå¯èƒ½é•æ³•ï¼‰çš„ä»»å‹™ã€‚å¦‚æžœå» å•†è¨è¨ˆè»Ÿé«”時,完全沒有考慮到國際化的架構,那更是回天ä¹è¡“。
+
+<p>
+相較之下,å°é–‹æ”¾æºç¢¼çš„æ‡‰ç”¨ç¨‹å¼é€²è¡Œæœ¬åœŸåŒ–,則è¦ç°¡å–®å¤šäº†ã€‚和專屬軟體一樣,早期的版本通常祗為單一語言而è¨è¨ˆï¼›ä¸åŒçš„æ˜¯ï¼Œä»»ä½•人都å¯ä»¥éš¨æ™‚åŠ ä¸Šåœ‹éš›åŒ–çš„æž¶æ§‹ã€‚èª å¦‚Sean M. Burke所述:
+
+<p>
+<blockquote>
+「è¦è®“開放æºç¢¼åšå¾—更好,我們å¯ä»¥åœ¨å¯«ç¨‹å¼æ™‚å¤šåŠ ç•™æ„,讓程å¼å“¡å’Œç†±å¿ƒçš„使用者,都能輕易進行本土化。(話說回來,「開放æºç¢¼ã€çš„æœ¬æ„,就是讓任何有æ„é¡˜ã€æœ‰æŠ€è¡“的人,都能æˆç‚ºç¨‹å¼å“¡ã€‚)ã€
+</blockquote>
+
+<p>
+本文敘述的技巧,能有效é™ä½Žæœ¬åœŸåŒ–的難度。雖然é‡é»žæ”¾åœ¨Perl寫的網站應用程å¼ä¸Šï¼Œä½†æ˜¯å…¶ä¸çš„原則應該也é©ç”¨æ–¼å…¶ä»–地方。
+
+<h2>å°éœæ…‹ç¶²ç«™é€²è¡Œæœ¬åœŸåŒ–</h2>
+<p class="right" align="right">
+「它一定得動起來。ã€<br>
+  ï¼ï¼ç¶²çµ¡ç¬¬ä¸€çœŸç†ï¼ŒRFC 1925
+
+<p>
+ç¶²é å¯ä»¥ç²—åˆ†ç‚ºå…©ç¨®ï¼šã€Œéœæ…‹ã€ç¶²é 直到下一次更新為æ¢ï¼Œéš¨æ™‚都æä¾›ç›¸åŒçš„內容;「動態ã€ç¶²é 則便“šå„ç¨®å› ç´ ï¼Œæä¾›ç›¸æ‡‰çš„資訊。通常我們稱å‰è€…ç‚ºã€Œç¶²é æ–‡ä»¶ã€ï¼Œç¨±å¾Œè€…ç‚ºã€Œç¶²é æ‡‰ç”¨ç¨‹å¼ã€ã€‚
+<p>
+ä¸éŽï¼Œå°ä¸åŒçš„ä½¿ç”¨è€…ä¾†èªªï¼Œéœæ…‹ç¶²é 並ä¸ä¸€å®šå¾—ä¿æŒç›¸åŒçš„ã€Œå‘ˆç¾æ–¹å¼ã€ï¼ï¼å®ƒçš„èªžè¨€ã€æ¨£å¼æˆ–媒介å¯ä»¥å› 人而異。(例如,視障人士å¯èƒ½æœƒå好用語音,來代替影åƒè¼¸å‡ºã€‚)全çƒè³‡è¨Šç¶²çš„長處之一,就在它能讓客戶端與伺æœå™¨é€²è¡Œäº¤æ¶‰ï¼Œæ±ºå®šæœ€åˆé©çš„å‘ˆç¾æ–¹å¼ã€‚
+<p>
+舉個實例來說,å‡è¨ç†è€…æœ‰å€‹ä¸æ–‡ç¶²é ,放在<tt>http://www.autrijus.org/index.html</tt>:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表1. åŸºæœ¬çš„ä¸æ–‡ç¶²é </font></caption>
+<tr><td><PRE>
+<html><head><title><B>å”å®—æ¼¢ - å®¶</B></title></head>
+<body><B>施工ä¸, 請見諒</B></body></html>
+</PRE></td></tr></table>
+<p>
+有天我心血來潮,想è¦å°‡å®ƒè¯æˆè‹±æ–‡ç‰ˆçµ¦å¤–地的朋å‹çœ‹ï¼š
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表2. ç¿»è¯è‹±æ–‡çš„ç¶²é </font></caption>
+<tr><td><PRE>
+<html><head><title><B>Audrey.Home</B></title></head>
+<body><B>Sorry, this page is under construction.</B></body></html>
+</PRE></td></tr></table>
+
+<P>
+這時,許多網站會æä¾›ä¸€å€‹ã€Œèªžè¨€é¸æ“‡é é¢ã€ï¼Œè®“訪客挑é¸é©åˆçš„語言,如下所示:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>圖3: å…¸åž‹çš„èªžè¨€é¸æ“‡é é¢</font></caption>
+<tr><td align="center" colspan=4 bgcolor=black>
+<font color="white">Please choose your language:</font>
+</td></tr><tr><td align="center">
+<font size=-1><u>Čeština</u></font>
+</td><td align="center">
+<font size=-1><u>Deutsch</u></font>
+</td><td align="center">
+<font size=-1><u>English</u></font>
+</td><td align="center">
+<font size=-1><u>Español</u></font>
+</td></tr><tr><td align="center">
+<font size=-1><u>Français</u></font>
+</td><td align="center">
+<font size=-1><u>Hrvatski</u></font>
+</td><td align="center">
+<font size=-1><u>Italiano</u></font>
+</td><td align="center">
+<font size=-1><u>日本語</u></font>
+</td></tr><tr><td align="center">
+<font size=-1><u>한êµì–´</u></font>
+</td><td align="center">
+<font size=-1><u>Nederlands</u></font>
+</td><td align="center">
+<font size=-1><u>Polski</u></font>
+</td><td align="center">
+<font size=-1><u>РуÑÑкий Ñзык</u></font>
+</td></tr><tr><td align="center">
+<font size=-1><u>Slovensky</u></font>
+</td><td align="center">
+<font size=-1><u>Slovensci</u></font>
+</td><td align="center">
+<font size=-1><u>Svenska</u></font>
+</td><td align="center">
+<font size=-1><u>䏿–‡ (GB)</u></font><br>
+<font size=-1><u>䏿–‡ (Big5)</u></font>
+</td></tr></table>
+
+<p>
+å°ä¸€èˆ¬çš„使用者和程å¼ä¾†èªªï¼Œé€™æ¨£çš„é é¢éƒ½æ¨¡ç³Šä¸æ¸…ã€å¤šé¤˜è€Œéº»ç…©ã€‚它ä¸ä½†è¿«ä½¿æ¯ä½è¨ªå®¢å¤šæŒ‰ä¸€å€‹éµï¼Œä¹Ÿå°ç¶²é 代ç†ç¨‹å¼çš„ä½œè€…é€ æˆéšœç¤™ï¼šå‰–æžé€™ä¸€é çš„çµæ§‹ã€é¸æ“‡æ£ç¢ºçš„éˆçµï¼Œæ˜¯ä»¶å¾ˆå®¹æ˜“出錯的事。
+
+<h3>MultiViews:最簡單的本土化架構</h3>
+<p>
+ç•¶ç„¶ï¼Œè¦æ˜¯èƒ½è®“æ¯å€‹äººè‡ªå‹•å–å¾—é©åˆçš„語言,那是最好了。HTTP 1.1版æä¾›çš„「內容交涉(Content Negotiation)ã€åŠŸèƒ½ï¼Œå°±æ˜¯é”æˆé€™å€‹ç›®æ¨™çš„好辦法。
+<p>
+在內容交涉的架構下,ç€è¦½å™¨æœƒå‚³é€ã€Œ<tt>Accept-Language</tt>ã€æ¨™é ,代表使用者å好的語言。舉例來說,「<tt>zh-tw, en-us, en</tt>ã€çš„æ„æ€å°±æ˜¯ã€Œæ£é«”䏿–‡ã€ç¾Žå¼è‹±æ–‡ï¼Œä¸ç„¶å°±æ˜¯è‹±æ–‡ã€ã€‚
+<p>
+網站伺æœå™¨æŽ¥åˆ°é€™é …è³‡è¨Šä¹‹å¾Œï¼Œä¾¿è² è²¬æŒ‘é¸æœ€åˆé©çš„語言版本,傳回給使用者。實作æ¤é …æµç¨‹çš„æ–¹å¼ï¼Œå„種伺æœå™¨å¯èƒ½æœ‰æ‰€ä¸åŒï¼›åœ¨æœ€å—æ¡è¿Žçš„Apache下,å¯ä»¥åˆ©ç”¨ã€Œ<tt>MultiViews</tt>ã€æŠ€è¡“ä¾†é”æˆã€‚
+<p>
+è¦ä½¿ç”¨<tt>MultiViews</tt>æ™‚ï¼Œæˆ‘å€‘å°‡è‹±æ–‡ç‰ˆå˜æˆ<tt>index.html.en</tt>(注æ„後é¢çš„<tt>.en</tt>),å†åˆ°<tt>httpd.conf</tt>或<tt>.htaccess</tt>è¨å®šæª”è£¡ï¼ŒåŠ ä¸Šé€™ä¸€åˆ—ï¼š
+<p>
+<PRE>
+ Options <b>+MultiViews</b>
+</PRE>
+<p>
+在æ¤ä¹‹å¾Œï¼ŒApache就會在接到<tt>http://www.autrijus.org/index.html</tt>çš„è¦æ±‚æ™‚ï¼Œæª¢æŸ¥å®¢æˆ¶ç«¯æ˜¯å¦æ–¼<tt>Accept-Language</tt>裡å好英文(<tt>en</tt>)。這樣一來,英語系的讀者就會看到英文é é¢ï¼Œå…¶ä»–人則看到原本的<tt>index.html</tt>。
+<p>
+利用這個技術,我å¯ä»¥è«‹åœ‹éš›å‹äººå¹«å¿™ï¼Œé€æ¼¸åŠ ä¸Šæ–°çš„ç¿»è¯ç‰ˆæœ¬ï¼ï¼æ³•文版是<tt>index.html.fr</tt>,<tt>index.html.he</tt>代表希伯來文ç‰ç‰ã€‚
+<p>
+由於網路上的許多人,都祗會自己的æ¯èªžå’Œè‹±æ–‡ï¼Œå› æ¤æ–°çš„ç‰ˆæœ¬é€šå¸¸éƒ½ä¸æ˜¯å¾žä¸æ–‡ï¼Œè€Œæ˜¯ç”±è‹±æ–‡ç¿»è¯éŽåŽ»çš„ã€‚ä¸éŽï¼Œæ—¢ç„¶ä¸è‹±æ–‡çš„內容相åŒï¼Œé€™ä¹Ÿä¸æˆå•題。
+<p>
+...真的沒å•é¡Œäº†å—Žï¼Ÿè¦æ˜¯æˆ‘å“ªå¤©æ›´æ–°äº†ä¸æ–‡ç‰ˆå‘¢ï¼Ÿ
+
+<h3>ä¿æŒç¿»è¯æ™‚效並ä¸å®¹æ˜“</h3>
+
+<p>
+在我改完原本的網é 之後,就會馬上發ç¾ï¼Œè² è²¬æ³•æ–‡å’Œå¸Œä¼¯ä¾†æ–‡çš„æœ‹å‹æ²’è¾¦æ³•çœ‹æ‡‚ä¸æ–‡ï¼ï¼é¡¯ç„¶ï¼Œæˆ‘æœ‰å¿…è¦æº–備一份英文的「標準版本ã€ã€‚è¨±å¤šè‡ªç”±è»Ÿé«”å°ˆæ¡ˆæ£æ˜¯å› ç‚ºé€™æ¨£ï¼Œå°±ç®—æ ¸å¿ƒåœ˜éšŠçš„æ¯èªžä¸æ˜¯è‹±æ–‡ï¼Œä»ç„¶æŽ¡ç”¨è‹±æ–‡ä½œç‚ºç¨‹å¼çš„é è¨èªžç³»ã€‚
+
+<p>
+æ¤å¤–,就算祗是改了背景é¡è‰²ï¼ˆåƒæ˜¯<tt><body bgcolor="gold"></tt>ï¼‰ï¼Œæˆ‘é‚„æ˜¯å¾—æ›´æ–°æ‰€æœ‰çš„è¯æœ¬ï¼Œå¥½è®“æ ¼å¼ä¿æŒä¸€è‡´ã€‚
+
+<p>
+è‹¥æ˜¯æˆ‘åŒæ™‚æ›´æ–°æ ¼å¼å’Œå…§å®¹ï¼Œäº‹æƒ…就麻煩了。一旦失去了原先的HTML標籤,幫忙翻è¯çš„æœ‹å‹å°±å¿…需從é 來éŽï¼é™¤éžä»–們都是HTML大師,ä¸ç„¶é¦¬ä¸Šå°±æœƒå‡ºéŒ¯ã€‚è¦æ˜¯ç¶²ç«™ä¸Šæœ‰20個經常更新的é é¢ï¼Œé‚£å¾ˆå¿«å°±æ²’人肯åšç¿»è¯ï¼ï¼è‡³é€£æœ‹å‹ä¹Ÿç•¶ä¸æˆäº†ã€‚
+
+<p>
+從上é¢çš„例å看來,顯然有必è¦å°‡è³‡æ–™èˆ‡æºç¢¼ï¼ˆä¹Ÿå°±æ˜¯æ–‡å—和標籤)分開,並且自動化翻è¯ç‰ˆæœ¬çš„生產æµç¨‹ã€‚
+
+<h3>利用CGI.pm將資料與æºç¢¼åˆ†é–‹</h3>
+
+<p>
+事實上,上一段已經一語é“盡了ç¾ä»£çš„國際化(i18n)æµç¨‹ï¼šåœ¨é€²è¡Œç¶²ç«™æ‡‰ç”¨ç¨‹å¼æœ¬åœŸåŒ–之å‰ï¼Œå¿…é ˆå…ˆæ‰¾å‡ºå€åˆ†è³‡æ–™èˆ‡æºç¢¼çš„æ–¹æ³•。
+
+<p>
+多年以來,Perl就是網é 開發的首é¸èªžè¨€ï¼Œä¹Ÿæä¾›äº†å¤šä¸å‹æ•¸çš„çš„æ¨¡çµ„èˆ‡ç¶²ç«™å»ºè£½å·¥å…·ã€‚å…¶ä¸æœ€æ™®é的,è¦ç®—是自1997年以來就併入標準程å¼åº«çš„<tt>CGI.pm</tt>。底下的範例程å¼ï¼Œå°±æ˜¯åˆ©ç”¨<tt>CGI.pm</tt>來自動產生翻è¯ç‰ˆæœ¬ï¼š
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表3. 利用MultiViewsåŠCGI.pm進行本土化</font></caption>
+<tr><td><PRE>
+use CGI ':standard'; <i># æ¤ç¨‹å¼çš„æ¨¡ç‰ˆç³»çµ±ï¼ˆTemplating System)</i>
+foreach my $language (qw(zh_tw en de fr)) {
+ open OUT, ">index.html.$language" or die $!;
+ print OUT start_html({ title => _(<b>"Audrey.Home"</b>) }),
+ _(<b>"Sorry, this page is under construction."</b>),
+ end_html;
+ sub _ { some_function($language, @_) } <i># XXX: 需補上本土化架構</i>
+}
+</PRE></td></tr></table>
+
+<p>
+æ¤ç¨‹å¼åˆ©ç”¨<tt>CGI.pm</tt>çš„HTML</tt>相關函å¼ï¼Œé”æˆè³‡æ–™èˆ‡æºç¢¼çš„分隔,這點與單純的HTMLé é¢ä¸åŒã€‚標籤(如<tt><html></tt>)變æˆäº†å‡½å¼å‘¼å«ï¼ˆ<tt>start_html()</tt>),文å—則以å—ä¸²è¡¨ç¤ºã€‚å› æ¤ï¼Œåœ¨ç”¢ç”Ÿæ¯ä»½æœ¬åœ°åŒ–çš„é 颿™‚(<tt>index.html.zh_tw</tt>ã€<tt>index.html.en</tt>ç‰ç‰ï¼‰ï¼Œå°±èƒ½ä¿æŒç›¸åŒçš„HTMLæ ¼å¼ã€‚
+
+<p>
+「<tt>sub _</tt>ã€é€™å€‹å‡½å¼è² 責å«ç”¨<tt>some_function()</tt>,將輸入的英文å—å¥ç¿»è¯æˆ<tt>$language</tt>變數所代表的語言。<tt>some_function()</tt>就是æ¤ç¨‹å¼çš„「本土化架構(localization framework)ã€ï¼›åœ¨ä¸‹ä¸€ç¯€è£¡ï¼Œæˆ‘們會介紹三種ä¸åŒçš„æž¶æ§‹ã€‚
+
+<p>
+寫完上é¢é€™æ®µå°ç¨‹å¼å¾Œï¼Œæˆ‘們祗需用<tt>grep</tt>找出所有「<tt>_(...)</tt>ã€è£¡çš„å—串,將它們解到一份詞典(lexicon)檔裡,å†è«‹è¯è€…填入所需的翻è¯å³å¯ã€‚在æ¤ï¼Œã€Œè©žå…¸ã€çš„定義是兩種語言之間的å°ç…§è¡¨ï¼šå…¶ä¸æœ‰äº›é …目祗有一個單å—(如「<tt>Cancel</tt>ã€ï¼‰ï¼Œä½†é€šå¸¸å‰‡åŒ…嫿•´å€‹å¥å(如「<tt>Do you want to overwrite?</tt>ã€æˆ–「<tt>5 files found.</tt>ã€ï¼‰ã€‚和觀光手冊上的「外語速æˆã€ä¸€æ¨£ï¼Œè©žå…¸è£¡çš„å—串也許還有待填的空白,如åŒä¸‹åˆ—çš„ä¸æ–‡â•±æµ·åœ°èªžè©žå…¸æ‰€ç¤ºï¼š
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>圖4: 䏿–‡ => 海地語詞典</font></caption>
+<tr>
+<td align="center" bgcolor=black><font color="white">䏿–‡</font></td>
+<td align="center" bgcolor=black><font color="white">Haitian</font></td>
+</tr><tr><td align="center">
+這個æ±è¥¿è¦ ___ 塊錢。
+</td><td>
+Bagay la kute ___ dola yo.
+</td></tr></table>
+
+<p>
+åœ¨ç†æƒ³æƒ…æ³ä¸‹ï¼Œè¯è€…祗需è¦ç¿»è¯è©žå…¸çš„內容,ä¸ç”¨ç®¡HTML或程å¼ç¢¼è£¡å¯«äº›ä»€éº¼ã€‚坿˜¯ï¼Œç”±æ–¼å„ç¨®æœ¬åœŸåŒ–æž¶æ§‹çš„è©žå…¸æ ¼å¼å½¼æ¤ä¸åŒï¼Œæˆ‘們得先é¸å‡ºæœ€é©åˆé€™é …專案的架構。
+
+<h2>本土化架構一覽</h2>
+
+<p align="right" class=right>
+ã€Œå®ƒæ¯”ä½ æƒ³åƒä¸é‚„複雜。ã€<br>
+  ï¼ï¼ç¶²çµ¡ç¬¬å…«çœŸç†ï¼ŒRFC 1925
+
+<p>
+è¦å¯¦ä½œä¸Šä¸€æ®µè£¡çš„<tt>some_function()</tt>函å¼ï¼Œéœ€è¦èƒ½è®€å–è©žå…¸æª”ã€æŸ¥å‡ºç›¸ç¬¦çš„å—串ã€ç”šè‡³å°‡æ–°çš„é …ç›®è§£åˆ°è©žå…¸è£¡çš„ç¨‹å¼åº«ã€‚這樣的程å¼åº«ï¼Œå°±ç¨±ç‚ºã€Œæœ¬åœŸåŒ–æž¶æ§‹ã€ã€‚
+
+<p>
+ä¾ç†è€…的經驗,本土化架構之間的ä¸åŒï¼Œé€šå¸¸è¡¨ç¾åœ¨è©žå…¸æª”çµæ§‹çš„差異上。接下來,讓我們看看Perl如何使用其ä¸çš„三種架構,從最簡單的<tt>Msgcat</tt>開始。
+
+<h3>Msgcat:用陣列當詞典</h3>
+
+<p>
+Msgcat是最早的本土化架構,也是XPG3╱XPG4æ¨™æº–çš„ä¸€éƒ¨ä»½ï¼Œå› æ¤åœ¨Unixå¹³å°ä¸Šéš¨è™•å¯å¾—。它是第一代的詞典檔架構:將å„個å—串以編號表示,循åºå˜æ”¾åœ¨åç‚ºã€Œè¨Šæ¯æ¸…單(message catalog)ã€çš„陣列裡。這種架構容易實作ã€ç¯€çœè¨˜æ†¶é«”,並且查詢速度很快。在Windowsç‰å¹³å°ä¸Šçš„ã€Œè³‡æºæª”(resource file)ã€ä¹Ÿæ˜¯åŸºæ–¼ç›¸åŒçš„æ¦‚念。
+
+<p>
+å°æ–¼æ¯å€‹ç¶²é åŠç¨‹å¼æª”,Msgcat都需è¦ä¸€ä»½ç›¸æ‡‰çš„è©žå…¸æª”ï¼Œæ ¼å¼å¦‚下:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表4. <tt>Msgcat</tt> 詞典檔</font></caption>
+<tr><td><PRE>
+$set <b>7</b> <i># $Id: nls/de/index.pl.m</i>
+<b>1</b> Audrey'.Haus
+<b>2</b> Wir bitten um Entschudigung. Diese Seite ist im Aufbau.
+</PRE></td></tr></table>
+
+<p>
+這份檔案包å«index.html裡所有å—串的德文翻è¯ï¼›å®ƒçš„「集åˆç·¨è™Ÿï¼ˆset idï¼‰ã€æ˜¯7。在åšå®Œæ‰€æœ‰é é¢çš„ç¿»è¯ä¹‹å¾Œï¼Œæˆ‘們利用gencat這支程å¼ä¾†ç”¢ç”ŸäºŒé€²åˆ¶çš„詞典檔:
+<p>
+<pre>
+ % gencat nls/de.cat nls/de/*.m
+</pre>
+
+<p>
+二進制詞典檔的內容,å¯ä»¥æƒ³æˆæ˜¯å¦‚下所示的二ç¶é™£åˆ—:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>圖5: <tt>nls/de.cat</tt> 的內容</font></caption>
+<tr>
+<td align="right" bgcolor=black><font color="white">set_id<br>msg_id</font></td>
+<td align="center" bgcolor=black><font color="white">1</font></td>
+<td align="center" bgcolor=black><font color="white">2</font></td>
+<td align="center" bgcolor=black><font color="white">3</font></td>
+<td align="center" bgcolor=black><font color="white">4</font></td>
+<td align="center" bgcolor=black><font color="white">5</font></td>
+<td align="center" bgcolor=black><font color="white">6</font></td>
+<td align="center" bgcolor=black><font color="white">7</font></td>
+<td align="center" bgcolor=black><font color="white">8</font></td>
+<td align="center" bgcolor=black><font color="white">9</font></td>
+</tr><tr>
+<td align="center" bgcolor=black><font color="white">1</font></td>
+<td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td>
+<td><tt>Audrey'.Haus</tt></td>
+<td><i>...</i></td><td><i>...</i></td>
+</tr><tr>
+<td align="center" bgcolor=black><font color="white">2</font></td>
+<td><i>...</i></td><td></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td>
+<td><tt>Wir bitten um Entschudigung...</tt></td>
+<td><i>...</i></td><td><i>...</i></td>
+</tr><tr>
+<td align="center" bgcolor=black><font color="white">3</font></td>
+<td><i>...</i></td><td></td><td><i>...</i></td><td><i>...</i></td><td></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td><td><i>...</i></td>
+</tr></table>
+
+<p>
+è¦å¾žè©žå…¸æª”ä¸è®€å–å—串,就得利用CPAN(Perl ç¶œåˆå…¸è—網)上的<tt>Locale::Msgcat</tt>模組,來實作å‰é¢æåˆ°çš„「<tt>sub _</tt>ã€ï¼š
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表5. 示範 <tt>Locale::Msgcat</tt> 的用法</font></caption>
+<tr><td><PRE>
+use Locale::Msgcat;
+my $cat = Locale::Msgcat->new;
+$cat->catopen("nls/$language.cat", 1); <i># æƒ³æˆæ˜¯äºŒç¶é™£åˆ—</i>
+sub _ { $cat->catgets(<b>7</b>, @_) } <i># <b>7</b> 是 index.html 的集åˆç·¨è™Ÿï¼ˆset_id)</i>
+print _(<b>1</b>, "Audrey.House"); <i># <b>1</b> 是這串文å—的詢æ¯ç·¨è™Ÿï¼ˆmsg_id)</i>
+</PRE></td></tr></table>
+
+<p>
+è¦æ³¨æ„çš„æ˜¯ï¼Œç¥—æœ‰ç¬¬ä¸€å€‹åƒæ•¸ï¼ˆ<tt>msg_id</tt>)有作用;接在後é¢çš„<tt>"Audrey.House"</tt>祗是作為查詢失敗時的é è¨å‚³å›žå€¼ï¼Œä»¥åŠè®“人比較容易看懂而已。
+
+<p>
+å› ç‚º<tt>set_id</tt>åŠ<tt>msg_id</tt>的組åˆå¿…é ˆç¨ä¸€ç„¡äºŒã€ä¸èƒ½æ›´æ”¹ï¼Œå› æ¤æ›´æ–°æ™‚祗能刪除æŸå€‹ç·¨è™Ÿï¼Œè€Œç„¡æ³•é‡è¤‡åˆ©ç”¨ã€‚é€™é …ç‰¹æ€§å¾€å¾€é€ æˆæ›´æ–°å›°é›£ï¼Œæ£å¦‚Drepperç‰äººåœ¨GNU <tt>gettext</tt>說明文件裡指出的:
+
+<p>
+<blockquote>
+「程å¼å“¡æ¯æ¬¡é‡åˆ°è¦ç¿»è¯çš„å—串時,都得先定義一個數值(或常數符號),å†å°‡å®ƒåŠ é€²è¨Šæ¯æ¸…單裡。他還得費心é¿å…é‡è¤‡çš„å—串ã€é‡è¤‡çš„訊æ¯ç·¨è™Ÿç‰ç‰ã€‚如果想具有與GNU <tt>gettext</tt>ä¸€æ¨£çš„è¨Šæ¯æ¸…å–®å“質,還得在裡é¢åŠ ä¸Šèªªæ˜Žï¼Œä¸¦è¨»æ˜Žç¨‹å¼è£¡å„個å—串的ä½ç½®ã€‚這簡直是『ä¸å¯èƒ½çš„任務ã€ã€‚ã€
+</blockquote>
+
+<p>
+å› æ¤ï¼Œç¥—有在詞典å分穩定的情æ³ä¸‹ï¼Œçº”å¯ä»¥è€ƒæ…®ä½¿ç”¨<tt>Msgcat</tt>作為本地化架構。
+
+<p>
+採用<tt>Msgcat</tt>的程å¼ï¼Œé‚„經常é‡åˆ°ã€Œè¤‡æ•¸å½¢å¼ã€çš„å•題。請看下列程å¼ï¼š
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表6. 錯誤的複數形å¼è™•ç†</font></caption>
+<tr><td><PRE>
+printf(_(8, "<b>%d</b> files were deleted."), $files);
+</PRE></td></tr></table>
+
+<p>
+ç•¶<tt>$files</tt>的值為<tt>1</tt>時,輸出的訊æ¯é¡¯ç„¶æ˜¯éŒ¯èª¤çš„,而<tt>"%d file(s) were deleted"</tt>也ä¸åˆè‹±æ–‡æ–‡æ³•ã€‚å› æ¤ï¼Œæˆ‘們éžå¾—分æˆå…©å€‹å—串表示ä¸å¯ï¼š
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表7. 祗考慮英文的複數形å¼è™•ç†</font></caption>
+<tr><td><PRE>
+printf(($files == 1) ? _(8, "<b>%d</b> file was deleted.")
+ : _(9, "<b>%d</b> files were deleted."), $files);
+</PRE></td></tr></table>
+
+<p>
+但就算這樣寫,在英文以外的語言ä»ç„¶è¡Œä¸é€šï¼ï¼æ³•文的單數形å¼åœ¨<tt>$files</tt>為<tt>0</tt>時也é©ç”¨ï¼Œè€Œæ–¯æ‹‰å¤«èªžç³»çš„è¤‡æ•¸å½¢å¼æ›´å¤šé”ä¸‰åˆ°å››ç¨®ï¼æƒ³åœ¨<tt>Msgcat</tt>的架構下照顧到這些狀æ³ï¼Œå¿…然是徒勞無功。
+
+<h3>Gettext:用雜湊當詞典</h3>
+
+<p>
+為了解決<tt>Msgcat</tt>的諸多å•題,Ulrich Drepper在1995å¹´åƒè€ƒUniforumçš„<tt>Gettext</tt>界é¢ï¼Œç‚ºGNU計劃開發了一套本土化系統。時到如今,在以C語言開發的自由軟體當ä¸ï¼ŒGNU <tt>gettext</tt>已儼然æˆç‚ºæœ¬åœŸåŒ–的標準架構,也廣å—C++ã€TclåŠPython程å¼å“¡çš„æ¡è¿Žã€‚
+
+<p>
+Gettextä¸éœ€è¦ç‚ºæ¯ä»½æºç¢¼æª”案準備å„自的詞典,而是為整個專案,é‡å°æ¯ç¨®èªžè¨€å„製作一份詞典(åˆç¨±ä½œã€ŒPO檔ã€ï¼‰ã€‚舉例來說,上述網é 的德文詞典檔「<tt>de.po</tt>ã€å¯èƒ½å…§å®¹å¦‚下:
+
+<p align="center">
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表8. <tt>Gettext</tt> 詞典檔</font></caption>
+<tr><td><PRE>
+#: index.pl:4
+msgid "Audrey.Home"
+msgstr "Audrey'.Haus"
+
+#: index.pl:5
+msgid "Sorry, this site is under construction."
+msgstr "Wir bitten um Entschudigung. Diese Seite ist im Aufbau."
+</PRE></td></tr></table>
+
+<p>
+
+以「<tt>#:</tt>ã€é–‹é 的兩列是由xgettext這支程å¼è‡ªå‹•ç”¢ç”Ÿçš„ã€‚è©²ç¨‹å¼æœƒæ‰¾å‡ºæºç¢¼è£¡å‘¼å«<tt>gettext()</tt>的地方,將它們ä¾åºè§£åˆ°è©žå…¸æª”裡。
+
+<p>
+接下來,我們執行<tt>msgfmt</tt>,從<tt>po/de.po</tt>產生二進制的詞典檔<tt>locale/de/LC_MESSAGES/web.mo</tt>:
+
+<pre>
+ % msgfmt locale/de/LC_MESSAGES/web.mo po/de.po
+</pre>
+
+<p>
+這樣一來,程å¼å°±å¯ä»¥ç”¨CPAN上的<tt>Locale::gettext</tt>模組來å˜å–二進制執行檔,如下所示:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表9. 示範 <tt>Locale::gettext 的用法</tt></font></caption>
+<tr><td><PRE>
+use POSIX;
+use Locale::gettext;
+POSIX::setlocale(LC_MESSAGES, $language); <i># è¨å®šç›®çš„語言</i>
+textdomain("web"); <i># 通常和程å¼å稱相åŒ</i>
+sub _ { gettext(@_) } <i># 祗是 gettext() 的簡寫</i>
+print _("Sorry, this site is under construction.");
+</PRE></td></tr></table>
+
+<p>
+新版的<tt>gettext</tt>(glibc 2.2版以上)更æä¾›äº†<tt>ngettext("%d file", "%d files", $files)</tt>的複數形å¼è™•ç†ï¼›ä¸éŽï¼Œ<tt>Locale::gettext</tt>模組目å‰é‚„æœªæ”¯æ´æ¤é …界é¢ã€‚ç†è€…å·²å°‡ä¿®æ£æª”é€å‡ºï¼Œå¸Œæœ›è©²æ¨¡çµ„的作者會盡快處ç†ã€‚
+
+<p>
+Also, <tt>gettext</tt> lexicons support multi-line strings, as well as reordering via <tt>printf</tt> and <tt>sprintf</tt>:
+æ¤å¤–,<tt>gettext</tt>的詞典檔也支æ´å¤šåˆ—å—串,以åŠåˆ©ç”¨<tt>printf</tt>與<tt>sprintf</tt>函å¼é”æˆçš„æ›´æ›è®Šæ•¸é †åºåŠŸèƒ½ï¼š
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表10. ä½¿ç”¨ç·¨è™Ÿåƒæ•¸çš„多列å—串</font></caption>
+<tr><td><PRE>
+msgid ""
+"This is a multiline string"
+"with <b>%1$s</b> and <b>%2$s</b> as arguments"
+msgstr ""
+"ã“れã¯å¤šç·šã²ã‚‚変数ã¨ã—ã¦"
+"<b>%2$s</b> 㨠<b>%1$s</b> ã®ã§ã‚ã‚‹"
+</PRE></td></tr></table>
+
+<p>
+最後,GNU <tt>gettext</tt>套件附有相當完整的工具集(<tt>msgattrib</tt>ã€<tt>msgcmp</tt>ã€<tt>msgconv</tt>ã€<tt>msgexec</tt>ã€<tt>msgfmt</tt>ã€<tt>msgcat</tt>ã€<tt>msgcomm</tt>...),大幅簡化了åˆä½µã€æ›´æ–°ã€ç®¡ç†è©žå…¸æª”çš„æµç¨‹ã€‚
+
+<h3>Locale::Maketext:用物件當詞典ï¼</h3>
+
+<p>
+<tt>Locale::Maketext</tt>æ–¼1998å¹´ç”±Sean M. Burke所開發,並於2001å¹´5月修訂後,併入Perl 5.8ç‰ˆçš„æ ¸å¿ƒç¨‹å¼åº«ã€‚
+
+<p>
+æ¤æ¨¡çµ„與<tt>Msgcat</tt>åŠ<tt>Gettext</tt>ç‰å‡½å¼å°Žå‘的架構ä¸åŒï¼Œæ˜¯ä»¥ç‰©ä»¶å°Žå‘çš„è¨è¨ˆï¼Œè®“「<tt>Locale::Maketext</tt>ã€ä½œç‚ºæŠ½è±¡çš„基底類別,è¡ç”Ÿå‡ºç”¨æ–¼ç‰¹å®šå°ˆæ¡ˆçš„「專案類別ã€ã€‚專案類別(å稱如「<tt>MyApp::L10N</tt>ã€ï¼‰é€²ä¸€æ¥è¡ç”Ÿå‡ºæ•¸å€‹ã€Œèªžè¨€é¡žåˆ¥ã€ï¼Œå¦‚「<tt>MyApp::L10N::it</tt>ã€ã€ã€Œ<tt>MyApp::L10N::fr</tt>ã€ç‰ç‰ã€‚
+>
+<p>
+所謂的語言類別,就是具有全域<tt>%Lexicon</tt>雜湊的Perl模組。<tt>%Lexicon</tt>è£¡çš„éµæ˜¯åŽŸæ–‡ï¼ˆé€šå¸¸æ˜¯è‹±æ–‡ï¼‰çš„å—串,雜湊值則是翻è¯éŽçš„å—串。語言類別還å¯ä»¥å®šç¾©æŸäº›æ–¹æ³•,來處ç†è©žå…¸ä¸çš„æ–‡æ³•變æ›ç‰äº‹é …。
+
+<p>
+請見底下範例:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表11. <tt>Locale::Maketext</tt> 詞典檔åŠä½¿ç”¨ç¯„例</font></caption>
+<tr><td><PRE>
+package MyApp::L10N;
+use base 'Locale::Maketext';
+
+package MyApp::L10N::de;
+use base 'MyApp::L10N';
+our %Lexicon = (
+ "[<b>quant</b>,_1,camel was,camels were] released." =>
+ "[<b>quant</b>,_1,Kamel wurde,Kamele wurden] freigegeben.",
+);
+
+package main;
+my $lh = MyApp::L10N->get_handle('de');
+print $lh->maketext("[<b>quant</b>,_1,camel was,camels were] released.", 5);
+</PRE></td></tr></table>
+
+<p>
+利用Maketext的「方括號語法ã€ï¼Œè¯è€…å¯ä»¥åœ¨å—串ä¸å–用å„種文法函å¼ã€‚上é¢çš„例å示範了內建的複數形å¼èˆ‡é‡è©žæ”¯æ´ï¼›å°æ–¼éœ€è¦ç‰¹åˆ¥è™•ç†è¤‡æ•¸å½¢å¼çš„語言,祗需實作相應的<tt>quant()</tt>函å¼å³å¯ã€‚åŒæ¨£çš„ï¼Œä¹Ÿå¾ˆå®¹æ˜“åŠ å…¥è½‰æ›åºæ•¸èˆ‡æ™‚é–“æ ¼å¼çš„功能。
+
+<p>
+æ¯å€‹èªžè¨€é¡žåˆ¥ï¼Œé‚„å¯ä»¥å®šç¾©è‡ªå·±çš„<tt>->encoding</tt>方法,æè¿°å…¶ä¸çš„詞典編碼;這å¯ä»¥ç”¨ä¾†å‚³çµ¦ã€Œ<tt>Encode</tt>ã€æ¨¡çµ„ï¼Œé€²è¡Œå³æ™‚轉碼。語言類別間也å¯ä»¥ç›¸äº’繼承:<tt>fr_ca.pm</tt>ï¼ˆåŠ æ‹¿å¤§æ³•èªžï¼‰è£¡ç¼ºå°‘çš„å—串,é è¨æœƒç”±<tt>fr.pm</tt>(一般法語)補上。
+
+<p>
+內建的<tt>->get_handle()</tt>æ–¹æ³•ï¼Œåœ¨æ²’æœ‰åƒæ•¸æ™‚,會自動在CGIã€mod_perlèˆ‡å‘½ä»¤åˆ—ä¸‹ï¼Œåµæ¸¬HTTPã€POSIXåŠWin32的地å€è¨å®šï¼›ç¨‹å¼å“¡æ¯‹é ˆå†åšåµæ¸¬ï¼Œå°±å¯ä»¥ç«‹åˆ»å‘ˆç¾é©åˆçš„語系給使用者。
+
+<p>
+ä¸éŽï¼Œã€Œ<tt>Locale::Maketext</tt>ã€ä¸¦éžå®Œç¾Žç„¡ç¼ºã€‚它最大的å•題,就是缺ä¹å¦‚GNU <tt>gettext</tt>çš„å·¥å…·é›†ï¼›ä¹Ÿå› ç‚ºè©žå…¸æª”çš„èªžæ³•å¤ªæœ‰å½ˆæ€§ï¼Œä½¿å¾—ç·¨è¼¯å™¨é›£æœ‰åƒEmacs的「<tt>PO Mode</tt>ã€é€™æ¨£çš„æ”¯æ´æ¨¡å¼ã€‚
+
+<p>
+æœ€å¾Œï¼Œå› ç‚ºæŽ¡ç”¨Perl模組作為詞典檔,導致è¯è€…å¿…é ˆç†Ÿæ‚‰åŸºæœ¬çš„Perl語法ï¼ï¼ä¸ç„¶çš„è©±ï¼Œå°±å¾—æœ‰äººå¹«å¿™åšæ ¼å¼è½‰æ›äº†ã€‚
+
+<h3>Locale::Maketext::Lexicon:兩全其美</h3>
+
+<p>
+2002å¹´5月,有感於<tt>Locale::Maketext</tt>詞典太éŽé¬†æ•£çš„æ ¼å¼ï¼Œæˆ‘著手實作公å¸å…§éƒ¨ä½¿ç”¨çš„è©žå…¸æ ¼å¼ï¼Œä¸¦åœ¨perl-i18n郵éžè«–壇上徵詢大家的æ„見。Jesse Vincentå•é“:「為什麼ä¸ç›´æŽ¥ä½¿ç”¨<tt>gettext</tt>çš„POæª”æ ¼å¼å‘¢ï¼Ÿã€æ–¼æ˜¯æˆ‘å°±è¨è¨ˆäº†æŠ½æ›å¼çš„後端系統,能接å—å„種ä¸åŒæ ¼å¼çš„詞典。這樣一來,「<tt>Locale::Maketext::Lexicon</tt>ã€å°±èª•生了。
+
+<p>
+æ¤æ¨¡çµ„è¨è¨ˆæ™‚çš„åˆè¡·ï¼Œåœ¨çµåˆ<tt>Locale::Maketext</tt>彈性的表é”å¼ï¼Œèˆ‡å»£æ³›æ”¯æ´çš„<tt>gettext</tt>或<tt>Msgcat</tt>æª”æ¡ˆæ ¼å¼ã€‚它也支æ´ç¹«çµï¼ˆTie)界é¢ï¼Œè®“è©žå…¸ä¹Ÿèƒ½å˜æ”¾åœ¨é—œè¯å¼è³‡æ–™åº«ï¼Œæˆ–DBM檔ä¸ã€‚
+
+<p>
+底下的應用程å¼ï¼Œç¤ºç¯„了<tt>Locale::Maketext::Lexicon</tt>的數種用法,以åŠã€ŒGettextã€å¾Œç«¯æ‰€æ”¯æ´çš„延伸POæ ¼å¼ï¼š
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表12. <tt>Locale::Maketext::Lexicon</tt> 的範例程å¼</font></caption>
+<tr><td bgcolor=black align=right><PRE><font color=white>1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+</font></PRE></td><td><PRE>
+use CGI ':standard';
+use base 'Locale::Maketext'; <i># 繼承 get_handle()</i>
+
+<i># å„ç¨®è©žå…¸æ ¼å¼åŠä¾†æº</i>
+use Locale::Maketext::Lexicon {
+ en => ['Auto'], fr => ['Tie' => 'DB_File', 'fr.db'],
+ de => ['Gettext' => \*DATA], zh_tw => ['Gettext' => 'zh_tw.mo'],
+};
+
+<i># å° main çš„å„個åé¡žåˆ¥ï¼Œå®šç¾©è©²èªžè¨€è£¡çš„åºæ•¸å‡½å¼</i>
+use Lingua::EN::Numbers::Ordinate; use Lingua::FR::Numbers::Ordinate;
+sub en::ord { ordinate($_[1]) } sub fr::ord { ordinate_fr($_[1]) }
+sub de::ord { "$_[1]." } sub zh_tw::ord { "第 $_[1] 個" }
+
+my $lh = __PACKAGE__->get_handle; <i># 自動å–å¾—ç›®å‰çš„地å€è¨å®š</i>
+sub _ { $lh->maketext(@_) } <i># 如有需è¦ï¼Œä¹Ÿå¯ä»¥è‡ªå‹•進行轉碼</i>
+
+print header, start_html, <i># [<b>*</b>,...] 是 [<b>quant</b>,...] 的簡寫</i>
+ _("You are my [<b>ord</b>,_1] guest in [<b>*</b>,_2,day].", $hits, $days), end_html;
+
+__DATA__
+# <i>以延伸 PO æ ¼å¼å¯«æˆçš„德文詞典</i>
+msgid "You are my <b>%ord(%1)</b> guest in <b>%*(%2,day)</b>."
+msgstr "Innerhalb <b>%*(%2,Tages,Tagen)</b>, sie sind mein <b>%ord(%1)</b> Gast."
+</PRE></td></tr></table>
+
+<p>
+程å¼çš„第2列讓<tt>main</tt>套件繼承<tt>Locale::Maketext</tt>,以å–å¾—<tt>get_handle</tt>方法。第5∼8列定義了四個語言類別,å„自採用ä¸åŒçš„è©žå…¸æ ¼å¼åŠä¾†æºï¼š
+
+<ul>
+<li>「Autoã€å¾Œç«¯å‘Šè¨´<tt>Locale::Maketext</tt>ç„¡é ˆç‰¹åˆ¥è™•ç†è‹±æ–‡ï¼ï¼ç›´æŽ¥å°‡æŽ¥åˆ°çš„雜湊éµå‚³å›žå³å¯ã€‚æ¤å¾Œç«¯ç‰¹åˆ¥é©åˆå‰›é–‹å§‹æ’°å¯«ç¨‹å¼ã€é‚„䏿ƒ³è™•ç†æœ¬åœ°åŒ–的狀æ³ã€‚
+<li>「Tieã€å¾Œç«¯å°‡æ³•文的<tt>%Lexicon</tt>雜湊繫çµåˆ°ä¸€å€‹Berkeley DB檔;其ä¸çš„å—ä¸²åœ¨éœ€è¦æ™‚纔會å–ç”¨ï¼Œå› æ¤ä¸æœƒæµªè²»é¡å¤–的記憶體。
+<li>「Gettextã€å¾Œç«¯å¾žç£ç¢Ÿä¸Šè®€å–ç·¨è¯éŽçš„二進制MOæª”ï¼Œä½œç‚ºä¸æ–‡çš„詞典;æ¤å¤–,它也從<tt>DATA</tt>檔案代號å–å¾—POæª”æ ¼å¼çš„德文詞典。
+</ul>
+
+<p>
+第11∼13列實作å„個語言類別的<tt>ord</tt>æ–¹æ³•ï¼Œè² è²¬å°‡åƒæ•¸è½‰æ›ç‚ºè©²èªžè¨€çš„åºæ•¸ï¼ˆå¦‚1stã€2ndã€3rd...)。英文與法文採用了兩個CPANæ¨¡çµ„é”æˆï¼Œè€Œå¾·æ–‡åŠä¸æ–‡å‰‡ç¥—需è¦å—串安æ’å³å¯ã€‚
+
+<p>
+第15列å–å¾—ç›®å‰å¥—件的「語言代號(language handle)ã€ç‰©ä»¶ã€‚å› ç‚ºæ²’æœ‰çµ¦å®šåƒæ•¸ï¼Œå®ƒæœƒè‡ªå‹•嵿¸¬HTTP_ACCEPT_LANGUAGE環境變數ã€POSIXçš„<tt>setlocale()</tt>è¨å®šï¼Œä»¥åŠWindows環境下的<tt>Win32::Locale</tt>。第16列實作了簡單的本地化函å¼ï¼Œå°‡åƒæ•¸ç›´æŽ¥äº¤çµ¦è©²ç‰©ä»¶çš„<tt>maketext</tt>方法處ç†ã€‚
+
+<p>
+最後,第18∼19列會å°å‡ºä¸€å‰‡ç¶“由本地化處ç†çš„訊æ¯ã€‚ç¬¬ä¸€å€‹åƒæ•¸<tt>$hits</tt>會交給<tt>ord</tt>方法,而<tt>$days</tt>則交給內建的<tt>quant</tt>方法處ç†ï¼ï¼ã€Œ<tt>[*...]</tt>ã€æ˜¯å‰é¢æéŽçš„「<tt>[quant,...]</tt>ã€çš„簡寫。
+
+<p>
+第22∼24列是以延伸POæ ¼å¼å¯«æˆçš„範例詞典。除了藉由<tt>%1</tt>åŠ<tt>%2</tt>èª¿å‹•åƒæ•¸é †åºä¹‹å¾Œï¼Œå®ƒä¹Ÿæ”¯æ´ã€Œ<tt>%function(args...)</tt>ã€èªžæ³•,代表<tt>Locale::Maketext</tt>語法ä¸çš„「<tt>[function,args...]</tt>ã€ï¼›args裡出ç¾çš„<tt>%1</tt>ã€<tt>%2</tt>ç‰è®Šæ•¸ï¼Œéƒ½æœƒè‡ªå‹•æ›¿æ›æˆ<tt>_1</tt>ã€<tt>_2</tt>ç‰ç‰ã€‚
+
+<h2>實例討論</h2>
+
+<p class="right" align="right">
+「沒有大å°é€šå–«é€™å›žäº‹ã€‚ã€<br>
+  ï¼ï¼ç¶²çµ¡ç¬¬å真ç†ï¼ŒRFC 1925
+
+<p>
+çžè§£äº†æœ¬åœŸåŒ–架構的原ç†å¾Œï¼Œè®“我們來看看它們如何應用到實際的應用程å¼ä¸Šã€‚
+
+<p>
+å°ç¶²ç«™æ‡‰ç”¨ç¨‹å¼ä¾†èªªï¼Œæœ¬åœŸåŒ–架構幾乎都在「呈ç¾ç³»çµ±ã€ï¼ˆæˆ–稱「模版系統ã€ï¼‰ä¸Šå¯¦ä½œï¼Œå› 為它決定了應用程å¼å¦‚何分隔資料與æºç¢¼ã€‚舉例來說,「Template Toolkitã€é¼“勵乾淨的三層å¼è³‡æ–™/æºç¢¼/æ¨¡ç‰ˆæž¶æ§‹ï¼›åŒæ¨£å—æ¡è¿Žçš„「Masonã€ç³»çµ±å‰‡å‚¾å‘å°‡Perl程å¼ç¢¼å…§åµŒåœ¨æ¨¡ç‰ˆè£¡ã€‚這一節裡,我們會看到é©ç”¨æ–¼é€™å…©ç¨®æž¶æ§‹çš„æœ¬åœŸåŒ–æ–¹å¼ï¼›å…¶ä¸çš„åŽŸå‰‡ï¼Œä¹ŸåŒæ¨£é©ç”¨æ–¼ã€ŒAxKitã€ã€ã€ŒHTML::Embperlã€ç‰æ¨¡ç‰ˆç³»çµ±ä¸Šã€‚
+
+<h3>Request Tracker(Mason)</h3>
+
+<p>
+「Request Trackerã€æ˜¯ç¬¬ä¸€å€‹ä»¥<tt>Locale::Maketext::Lexicon</tt>作為本土化架構的應用程å¼ã€‚å®ƒçš„ã€ŒåŸºåº•èªžè¨€é¡žåˆ¥ã€æ˜¯ã€Œ<tt>RT::I18N</tt>ã€ï¼Œå類別則由åŒä¸€å€‹ç›®éŒ„下的<tt>*.po</tt>檔案產生。
+
+<p>
+除æ¤ä¹‹å¤–,它的<tt>->maketext</tt>方法也利用「<tt>Encode</tt>ã€æ¨¡çµ„(在5.8版以å‰çš„Perl,則是利用我的「<tt>Encode::compat</tt>ã€æ¨¡çµ„)直接傳回UTF-8ç·¨ç¢¼çš„è³‡æ–™ã€‚é€™æ¨£ä¸€ä¾†ï¼Œè² è²¬ä¸æ–‡çš„è¯è€…å°±å¯ä»¥ç”¨Big5碼編輯詞典檔,系統å»å¯ä»¥å°‡å®ƒè¦–為è¬åœ‹ç¢¼ï¼ˆUnicode)處ç†ã€‚
+
+<p>
+在RTçš„Perlæºç¢¼è£¡ï¼Œæ‰€æœ‰ç‰©ä»¶éƒ½åˆ©ç”¨å¾ž<tt>RT::Base</tt>繼承的<tt>$self->loc</tt>方法來翻è¯è¨Šæ¯ï¼š
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表13. RT 的本土化架構</font></caption>
+<tr><td><PRE>
+sub RT::Base::loc
+ { $self->CurrentUser->loc(@_) }
+sub RT::CurrentUser::loc
+ { $self->LanguageHandle->maketext(@_) }
+sub RT::CurrentUser::LanguageHandle
+ { $self->{'LangHandle'} ||= RT::I18N->get_handle(@_) }
+</PRE></td></tr></table>
+
+<p>
+å¦‚ä¸Šæ‰€ç¤ºï¼Œç¿»è¯æ™‚採用的是ç¾è¡Œä½¿ç”¨è€…çš„èªžç³»ï¼Œå› æ¤å¤šå€‹ä½¿ç”¨è€…å¯ä»¥è—‰ç”±ä¸åŒèªžè¨€ï¼ŒåŒæ™‚執行這個程å¼ã€‚å°Mason模版來說,則採用了兩種方å¼ï¼š
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表14. 兩種標示 Mason 模版ä¸å—串的方å¼</font></caption>
+<tr><td><PRE>
+% $m->print(<b>loc(</b>"<b>Another line of text</b>", $args...<b>)</b>);
+<&<b>|/l</b>, $args...&><b>Single line of text</b></&>
+</PRE></td></tr></table>
+<p>
+第一列的方å¼ï¼Œç”¨åœ¨å…§åµŒçš„Perlæºç¢¼èˆ‡<tt><%PERL></tt>段è½ä¹‹ä¸ï¼›å®ƒæœƒè‡ªå‹•å«ç”¨ç¾è¡Œä½¿ç”¨è€…çš„<tt>->loc</tt>方法,交由上述的函å¼è™•ç†ã€‚
+
+<p>
+第二列則利用<tt>HTML::Mason</tt>模組所æä¾›çš„ã€ŒéŽæ¿¾å…ƒä»¶ï¼ˆfilter component)ã€åŠŸèƒ½ï¼Œå°‡ä¸é–“çš„"Single line of text"傳給「<tt>/l</tt>ã€å…ƒä»¶ï¼ˆä¹Ÿè¨±é‚„é™„åŠ åƒæ•¸ï¼‰ï¼Œæœ€å¾Œå†é¡¯ç¤ºè©²å…ƒä»¶å‚³å›žçš„å—串。底下是æ¤å…ƒä»¶çš„實作方å¼ï¼š
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表15. <tt>html/l</tt> éŽæ¿¾å…ƒä»¶çš„實作方å¼</font></caption>
+<tr><td><PRE>
+% my $hand = $session{'CurrentUser'}->LanguageHandle;
+% $m->print($hand->maketext($m->content, @_));
+</PRE></td></tr></table>
+
+<p>
+有了這些方å¼ï¼Œç¥—è¦å°‡æ—¢æœ‰æ¨¡ç‰ˆè£¡çš„訊æ¯è§£æˆè©žå…¸æª”,å†å¯„給è¯è€…就行了。找出700多則訊æ¯çš„工作約費時一個星期;整個國際化╱本土化的æµç¨‹å‰‡èŠ±äº†ä¸åˆ°å…©å€‹æœˆçš„æ™‚間。
+
+<h3>Slash(Template Toolkit)</h3>
+
+<p>
+Slash(類似於Slashdot的自動說書首é ,Slashdot Like Automated Storytelling Homepage)是Slashdot背後的程å¼ã€‚ä¸éŽï¼ŒSlash更是完整的網站的建置環境,建立在Andy Wardley的「Template Toolkitã€æ¨¡çµ„上。
+
+<p>
+基於TT2乾淨的è¨è¨ˆï¼ŒSlashå°‡æºç¢¼èˆ‡è³‡æ–™å¾¹åº•分開,這點與RT╱Masonä¸åŒã€‚å› æ¤ï¼Œå¹¾ä¹Žæ²’有必è¦åœ¨Perlç¨‹å¼æª”裡進行本土化的工作。
+
+<p>
+在本文撰寫之å‰ï¼Œæœ‰è¨±å¤šç›´æŽ¥å°‡æ¨¡ç‰ˆç¿»è¯è€Œæˆçš„「本土化ã€ç‰ˆæœ¬ï¼ŒåŒ…æ‹¬ä¸æ–‡ã€æ—¥æ–‡ã€å¸Œä¼¯ä¾†æ–‡ç‰ã€‚然而,在新版釋出時需è¦åšçš„åˆä½µï¼ˆmerge)動作éžå¸¸å›°é›£ï¼ˆå¤–掛程å¼å°±æ›´åˆ¥æäº†ï¼‰ï¼Œå› æ¤ç¿»è¯ç‰ˆæœ¬å¾€å¾€å»¶é²è¨±ä¹…纔釋出。
+
+<p>
+這裡,我們æå‡ºä¸€å€‹è¼ƒå¥½çš„解決方å¼ï¼šåœ¨æ¨¡ç‰ˆæä¾›å‡½å¼ä¸Šæž¶è¨ã€Œè‡ªå‹•è§£è¯å±¤ã€ï¼Œåˆ©ç”¨<tt>HTML::Parser</tt>åŠ<tt>Template::Parser</tt>實作。它的功能如下所示:
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表16. TT2 è§£è¯å±¤çš„輸入與輸出</font></caption>
+<tr><td bgcolor=black><font color=white>輸入</font></td><td><PRE>
+<B>from the [% story.dept %] dept.</B>
+</PRE></td></tr>
+<tr><td bgcolor=black><font color=white>輸出</font></td><td><PRE>
+<B>[%<b>|loc(</b> story.dept <b>)</b>%]from the [<b>_1</b>] dept.[%END%]</B>
+</PRE></td></tr></table>
+
+<p>
+眼尖的讀者會發ç¾ï¼Œé€™æ¨£çš„轉è¯å±¤æœƒç¢°åˆ°è·Ÿ<tt>Msgcat</tt>相åŒçš„åŒé¡Œï¼ï¼è¦æ˜¯æˆ‘們想將<tt>[% story.dept %]</tt>轉æˆåºæ•¸ï¼Œæˆ–是把<tt>dept.</tt>ä¾è¤‡æ•¸å½¢å¼å±•é–‹æˆ<tt>department</tt>╱<tt>departments</tt>çš„è©±ï¼Œè©²æ€Žéº¼è¾¦å‘¢ï¼ŸåŒæ¨£çš„å•題,也出ç¾åœ¨RT的網é 界é¢è£¡ï¼Œéœ€è¦ç¿»è¯å¾žå¤–éƒ¨æ¨¡çµ„å‚³å›žçš„è¨Šæ¯æ™‚;舉例來說,<tt>"Successfully deleted 7 ticket(s) in 'c:\temp'."</tt>這樣的å—ä¸²è¦æ€Žéº¼ç¿»å‘¢ï¼Ÿ
+
+<p>
+ç†è€…的解決方法,是開發<tt>Locale::Maketext::Fuzzy</tt>模組,用來將已經安æ’變數的å—串,與詞典檔進行比å°ï¼ï¼ä¾‹å¦‚å‰è¿°çš„å—串,就å¯èƒ½ç¬¦åˆ<tt>"Successfully deleted [*,_1,ticket] in '[_2]'."</tt>這則訊æ¯ã€‚è¦æ˜¯ç¬¦åˆçš„å—䏲䏿¢ä¸€å€‹ï¼ˆç•¢ç«Ÿï¼Œ<tt>"Successfully [_1]."</tt>也算符åˆï¼‰ï¼Œè©²æ¨¡çµ„會ä¾å¾ªç¶“驗法則,找出最é©åˆçš„ç”æ¡ˆã€‚
+
+<p>
+æé…<tt>xgettext.pl</tt>這支程å¼ï¼Œé–‹ç™¼è€…å¯ä»¥ç‚ºæ¯å¥—外掛程å¼åŠä½ˆæ™¯ä¸»é¡Œé™„上å„自的詞典檔,供Slash系統進行多層次的處ç†ï¼šå…ˆå˜—試所屬外掛程å¼çš„詞典;接著å†è©¦ä½ˆæ™¯ä¸»é¡Œï¼›æœ€å¾Œçº”以全域的詞典檔作為é è¨ã€‚
+
+<h2>總çµ</h2>
+
+<p class="right" align="right">
+「所謂盡善盡美,並éžä¸èƒ½å†åŠ ï¼Œè€Œæ˜¯ä¸èƒ½å†æ¸›ã€‚ã€<br>
+  ï¼ï¼ç¶²çµ¡ç¬¬å二真ç†ï¼ŒRFC 1925
+
+<p>
+從以上的兩個例å裡,å¯ä»¥ç´„略看出本土化的共通æµç¨‹ã€‚這一節會介紹如何經由å個階段,來本土化既有的網站應用程å¼ï¼Œä»¥åŠä¸€äº›ç›¸é—œçš„å°ç¥•訣。
+
+<h3>本土化的æµç¨‹</h3>
+
+本土化的æµç¨‹ï¼Œå¯ä»¥ä¾åºç¸½çµæˆä¸‹åˆ—å¹¾é …æ¥é©Ÿï¼š
+
+<ol>
+<li>è©•ä¼°ç¶²é 的模版系統。
+<li>鏿“‡ä¸€é …本土化架構,將它與模版系統銜接上。
+<li>ç”¨ç¨‹å¼æ‰¾å‡ºæ¨¡ç‰ˆè£¡çš„æ–‡å—å—ä¸²ï¼Œç”¨éŽæ¿¾å‡½å¼é€²è¡Œä»£æ›ã€‚
+<li>解出測試用的詞典;手動修掉明顯的å•題。
+<li>手動找出æºç¢¼è£¡çš„æ–‡å—å—ä¸²ï¼Œå°‡å®ƒå€‘ä»£æ›æˆã€Œ<tt><b>_(</b>...<b>)</b></tT>ã€å‡½å¼å‘¼å«ã€‚
+<li>å†è§£å‡ºä¸€ä»½æ¸¬è©¦è©žå…¸ï¼Œäº¤ç”±æ©Ÿå™¨ç¿»è¯ã€‚
+<li>實地測試本土化版本;修掉餘下的å•題。
+<li>解出試用版的詞典,寄給翻è¯åœ˜éšŠæ ¡è¨‚。
+<li>修掉è¯è€…å›žå ±çš„å•題,寄出æ£å¼ç‰ˆçš„詞典ï¼
+<li>在æ¯å€‹æ–°ç‰ˆé‡‹å‡ºå‰ï¼Œå®šæœŸæ•´ç†è©žå…¸è£¡æ–°çš„é …ç›®ï¼Œé€šçŸ¥è¯è€…進行翻è¯ã€‚
+</ol>
+
+照著以上的æ¥é©Ÿï¼Œä½ å°±å¯ä»¥è¼•é¬†åœ°ç®¡ç†æœ¬åœŸåŒ–çš„å°ˆæ¡ˆï¼Œä¸¦ä¸”ä¿æŒç¿»è¯æ™‚æ•ˆã€æ¸›å°‘錯誤。
+
+<h3>一些å°ç¥•訣</H3>
+
+最後,這裡是å°ç¶²é 與其他應用程å¼é€²è¡Œæœ¬åœŸåŒ–æ™‚ï¼Œå¹¾é»žéœ€è¦æ³¨æ„çš„äº‹é …ï¼š
+
+<ul>
+<li>在è¨è¨ˆåŠå¯¦ä½œéšŽæ®µï¼Œéƒ½æ‡‰å°‡è³‡æ–™èˆ‡æºç¢¼åˆ†é–‹ã€‚
+<li>ä¸è¦åœ¨ç¶²ç«™æˆ–ç¨‹å¼æˆå½¢å‰ï¼Œå°±å…ˆè¨è¨ˆåœ‹éš›åŒ–/本土化的架構。
+<li>é¿å…內嫿–‡å—的圖檔。
+<li>é 留標籤和按鈕æ—邊的空白ï¼ï¼é¿å…讓æ“作介é¢éŽåº¦æ“æ“ ã€‚
+<li>使用完整的å¥å,而éžç‰‡æ®µï¼š
+</ul>
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表17. 片段與完整å¥</font></caption>
+<tr><td><PRE>
+_("Found ") . $files . _(" file(s)."); <i># ä¸å®Œæ•´çš„å¥åï¼ï¼éŒ¯äº†ï¼</i>
+sprintf(_("Found %s file(s)."), $files); <i># 完整å¥ï¼ˆåˆ©ç”¨sprintf)</i>
+_("Found [*,_1,file].", $files); <i># 完整å¥ï¼ˆç”¨Locale::Maketext)</i>
+</PRE></td></tr></table>
+
+<ul>
+<li>ä¸åŒèªžå¢ƒä¸‹çš„åŒæ¨£å—串,應該作出å€åˆ†ã€‚舉例來說,RT裡的<tt>"Home"</tt>原本具有<tt>"Homepage"</tt>åŠ<tt>"Home Phone No."</tt>ç‰å…©ç¨®ç”¨æ³•。
+<li>å¹³ç‰å°å¾…è¯è€…,別將他們視為下屬;é¿å…擅自修改詞典檔。
+<li>先讓一個人è¯å‡ºè‰ç¨¿ï¼Œå†è«‹å…¶ä»–人修改。
+<li>盡é‡åœ¨è©žå…¸æª”裡æä¾›è¨»è§£åŠæè¿°è³‡è¨Šï¼š
+</ul>
+
+<p align=center>
+<table border=2 align=center>
+<caption align=bottom><font size=-1>列表18. 詞典檔裡的註解</font></caption>
+<tr><td><PRE>
+#: lib/RT/Transaction_Overlay.pm:579
+#. ($field, $self->OldValue, $self->NewValue)
+# <i>請注æ„:底下的「changed toã€æ„æ€æ˜¯ã€Œå·²æ”¹ç‚º...ã€ã€‚</i>
+msgid "<b>%1 %2</b> changed to <b>%3</b>"
+msgstr "<b>%1 %2</b> cambiado a <b>%3</b>"
+</PRE></td></tr></table>
+
+<p>
+利用<tt>Locale::Maketext::Lexicon</tt>套件ä¸é™„的「<tt>xgettext.pl</tt>ã€é€™æ”¯ç¨‹å¼ï¼Œå¯ä»¥è‡ªå‹•產生詞典檔ä¸çš„æºç¢¼æª”åã€åˆ—號(以<tt>#:</tt>表示)ã€è®Šæ•¸ï¼ˆä»¥<tt>#.</tt>表示),並隨時更新。如上所示,å°å¤ªçŸæˆ–æ„ç¾©ä¸æ˜Žçš„å¥ååŠ ä¸Šè¨»è§£ï¼ˆä»¥<tt>#</tt>表示),å°ç¿»è¯ä¹Ÿæœƒæœ‰å¾ˆå¤§çš„幫助。
+
+<h2>çµè«–</h2>
+
+<p>
+å°éžè‹±èªžç³»åœ‹å®¶çš„人民來說,本土化往往是åƒèˆ‡è‡ªç”±è»Ÿé«”å°ˆæ¡ˆçš„å‰æã€‚ä»¥è‡ºç£è€Œè¨€ï¼ŒCLEï¼ˆä¸æ–‡Linux環境)ã€Debian-ChineseåŠFreeBSD-Chineseç‰æœ¬åœŸåŒ–專案,都是社群貢ç»çš„èšé›†ç„¦é»žã€‚然而,拜以英文為主的僵化軟體架構所賜,這往往也是耗時費力ã€å®¹æ˜“出錯的工作。å°è¯è€…來說,「進入障礙ã€é‚„是太高了些。
+
+<p>
+話說回來,在日漸昇高的網é 國際化趨勢下,網站應用程å¼ç¶“å¸¸æœ‰ç¿»è¯æˆå¤šåœ‹èªžè¨€çš„æ©Ÿæœƒã€‚舉例來說,Sean M.Burke在2002å¹´é ˜å°Žç†±å¿ƒçš„ä½¿ç”¨è€…ï¼Œå°‡å»£å—æ¡è¿Žçš„「Apache::MP3ã€ç·šä¸Šé»žæ’æ¨¡çµ„è¯æˆæ•¸å種語言的版本。模組的原作者Lincoln D. Stein完全未åƒèˆ‡ç¿»è¯ï¼ï¼ä»–祗è¦å°‡ä¿®æ£æª”和詞典併入下個釋出版本就行了。
+
+<p>
+自由軟體並éžä¸€å †æŠ½è±¡çš„程å¼ï¼Œè€Œæ˜¯é 人們共享æºç¢¼ã€å½¼æ¤æä¾›å»ºè°çš„熱情而å˜åœ¨çš„ã€‚å› æ¤ï¼Œæˆ‘èª æ‘°åœ°å¸Œæœ›æœ¬æ–‡ä»‹ç´¹çš„æŠ€å·§ï¼Œèƒ½é¼“å‹µç¨‹å¼å“¡åŠä½¿ç”¨è€…在被動翻è¯ä¹‹å¤–,也能主動國際化既有的應用程å¼ã€‚
+
+<h2>致è¬</h2>
+
+<p>
+感è¬Jesse Vincentå»ºè°æˆ‘開發<tt>Locale::Maketext::Lexicon</tt>,以åŠè®“我å”助他è¨è¨ˆRTçš„æœ¬åœŸåŒ–æž¶æ§‹ã€‚æˆ‘ä¹Ÿè¦æ„Ÿè¬Sean M. Burkeå‰µé€ äº†<tt>Locale::Maketext</tt>,並鼓勵我實驗å„種ä¸åŒçš„è©žå…¸æ ¼å¼ã€‚
+
+<p>
+æ„Ÿè¬æˆ‘在傲爾網的åŒäº‹å€‘(簡信昌ã€é«˜å˜‰è‰¯ã€ç¿åƒå©·ã€æž—å…‹å¯°ï¼‰ç‚ºæœ¬åœŸåŒ–ç¶²é æ‡‰ç”¨ç¨‹å¼ä»˜å‡ºçš„努力。也è¬è¬é§±é¦¬æ›¸ï¼ˆPerlå¸ç¿’手冊)的è¯è€…群,讓我å¸åˆ°äº†åˆ†æ•£å¼ç¿»è¯åœ˜éšŠçš„力é‡ã€‚
+
+<p>
+感è¬Nick Ing-Simmonsã€å°é£¼å¼¾åŠJarkko Hietaniemi教我如何利用<tt>Encode</tt>模組;Bruno Haibleå…許我借用他強大的GNU libiconv;以åŠå®®å·é”å½¥å°<tt>Locale::Maketext::Lexicon</tt>æ—©æœŸç‰ˆæœ¬çš„æ ¡è¨‚å”助。
+
+<p>
+æœ€å¾Œï¼Œå¦‚æžœä½ é¡˜æ„ä¾ç…§æœ¬æ–‡è£¡çš„æ¥é©Ÿï¼Œåƒèˆ‡è»Ÿé«”的國際化與本地化,請容我å‘ä½ è‡´ä¸Šæœ€é«˜çš„è¬å¿±ï¼›è®“æˆ‘å€‘ä¸€èµ·æ‰“é€ ã€Œå…¨çƒã€çš„資訊網å§ï¼
+
+<h2>åƒè€ƒè³‡æ–™</h2>
+
+<ul>
+<li>Harald Tveit Alvestrand,「RFC 1766: Tags for the Identification of Languagesã€ï¼Œ1995年,<a href="ftp://ftp.isi.edu/in-notes/rfc1766.txt"><tt>ftp://ftp.isi.edu/in-notes/rfc1766.txt</tt></a>。
+<li>Ross Callon編,「RFC 1925: The Twelve Networking Truthsã€ï¼Œ1996年,<a href="ftp://ftp.isi.edu/in-notes/rfc1925.txt"><tt>ftp://ftp.isi.edu/in-notes/rfc1925.txt</tt></a>
+<li>Ulrich Drepperã€Peter Millerã€Francois Pinard,GNU "gettext",1995-2001年。附在<a href="ftp://prep.ai.mit.edu/pub/gnu/"><tt>ftp://prep.ai.mit.edu/pub/gnu/</tt></a>套件的說明文件ä¸ã€‚
+<li>Burkeã€Lachler,「Localization and Perl: gettext breaks, Maketext fixesã€ï¼Œ1999年,出版於Perl Journal,第13期。
+<li>Sean M. Burke,「Localizing Open-Source Softwareã€ï¼Œ2002年,出版於Perl Journal,2002å¹´ç§‹å£è™Ÿã€‚
+<li>W3C 國際化行動宣言,2001年,<a href="http://www.w3.org/International/Activity.html"><tt>http://www.w3.org/International/Activity.html</tt></a>
+<li>Mozilla åœ‹éš›åŒ–åŠæœ¬åœŸåŒ–指引,1999年,<a href="http://www.mozilla.org/docs/refList/i18n/"><tt>http://www.mozilla.org/docs/refList/i18n/</tt></a>
+</ul>
+
+</body></html>
Added: Locale-Maketext-Lexicon/inc/Module/Install.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,266 @@
+#line 1
+package Module::Install;
+
+# For any maintainers:
+# The load order for Module::Install is a bit magic.
+# It goes something like this...
+#
+# IF ( host has Module::Install installed, creating author mode ) {
+# 1. Makefile.PL calls "use inc::Module::Install"
+# 2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install
+# 3. The installed version of inc::Module::Install loads
+# 4. inc::Module::Install calls "require Module::Install"
+# 5. The ./inc/ version of Module::Install loads
+# } ELSE {
+# 1. Makefile.PL calls "use inc::Module::Install"
+# 2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install
+# 3. The ./inc/ version of Module::Install loads
+# }
+
+use 5.004;
+use strict 'vars';
+
+use vars qw{$VERSION};
+BEGIN {
+ # All Module::Install core packages now require synchronised versions.
+ # This will be used to ensure we don't accidentally load old or
+ # different versions of modules.
+ # This is not enforced yet, but will be some time in the next few
+ # releases once we can make sure it won't clash with custom
+ # Module::Install extensions.
+ $VERSION = '0.61';
+}
+
+# Whether or not inc::Module::Install is actually loaded, the
+# $INC{inc/Module/Install.pm} is what will still get set as long as
+# the caller loaded module this in the documented manner.
+# If not set, the caller may NOT have loaded the bundled version, and thus
+# they may not have a MI version that works with the Makefile.PL. This would
+# result in false errors or unexpected behaviour. And we don't want that.
+my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm';
+unless ( $INC{$file} ) {
+ die <<"END_DIE";
+Please invoke ${\__PACKAGE__} with:
+
+ use inc::${\__PACKAGE__};
+
+not:
+
+ use ${\__PACKAGE__};
+
+END_DIE
+}
+
+use Cwd ();
+use File::Find ();
+use File::Path ();
+use FindBin;
+
+*inc::Module::Install::VERSION = *VERSION;
+ at inc::Module::Install::ISA = __PACKAGE__;
+
+sub autoload {
+ my $self = shift;
+ my $who = $self->_caller;
+ my $cwd = Cwd::cwd();
+ my $sym = "${who}::AUTOLOAD";
+ $sym->{$cwd} = sub {
+ my $pwd = Cwd::cwd();
+ if ( my $code = $sym->{$pwd} ) {
+ # delegate back to parent dirs
+ goto &$code unless $cwd eq $pwd;
+ }
+ $$sym =~ /([^:]+)$/ or die "Cannot autoload $who - $sym";
+ unshift @_, ($self, $1);
+ goto &{$self->can('call')} unless uc($1) eq $1;
+ };
+}
+
+sub import {
+ my $class = shift;
+ my $self = $class->new(@_);
+ my $who = $self->_caller;
+
+ unless ( -f $self->{file} ) {
+ require "$self->{path}/$self->{dispatch}.pm";
+ File::Path::mkpath("$self->{prefix}/$self->{author}");
+ $self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self );
+ $self->{admin}->init;
+ @_ = ($class, _self => $self);
+ goto &{"$self->{name}::import"};
+ }
+
+ *{"${who}::AUTOLOAD"} = $self->autoload;
+ $self->preload;
+
+ # Unregister loader and worker packages so subdirs can use them again
+ delete $INC{"$self->{file}"};
+ delete $INC{"$self->{path}.pm"};
+}
+
+sub preload {
+ my ($self) = @_;
+
+ unless ( $self->{extensions} ) {
+ $self->load_extensions(
+ "$self->{prefix}/$self->{path}", $self
+ );
+ }
+
+ my @exts = @{$self->{extensions}};
+ unless ( @exts ) {
+ my $admin = $self->{admin};
+ @exts = $admin->load_all_extensions;
+ }
+
+ my %seen;
+ foreach my $obj ( @exts ) {
+ while (my ($method, $glob) = each %{ref($obj) . '::'}) {
+ next unless exists &{ref($obj).'::'.$method};
+ next if $method =~ /^_/;
+ next if $method eq uc($method);
+ $seen{$method}++;
+ }
+ }
+
+ my $who = $self->_caller;
+ foreach my $name ( sort keys %seen ) {
+ *{"${who}::$name"} = sub {
+ ${"${who}::AUTOLOAD"} = "${who}::$name";
+ goto &{"${who}::AUTOLOAD"};
+ };
+ }
+}
+
+sub new {
+ my ($class, %args) = @_;
+
+ # ignore the prefix on extension modules built from top level.
+ my $base_path = Cwd::abs_path($FindBin::Bin);
+ unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) {
+ delete $args{prefix};
+ }
+
+ return $args{_self} if $args{_self};
+
+ $args{dispatch} ||= 'Admin';
+ $args{prefix} ||= 'inc';
+ $args{author} ||= ($^O eq 'VMS' ? '_author' : '.author');
+ $args{bundle} ||= 'inc/BUNDLES';
+ $args{base} ||= $base_path;
+ $class =~ s/^\Q$args{prefix}\E:://;
+ $args{name} ||= $class;
+ $args{version} ||= $class->VERSION;
+ unless ( $args{path} ) {
+ $args{path} = $args{name};
+ $args{path} =~ s!::!/!g;
+ }
+ $args{file} ||= "$args{base}/$args{prefix}/$args{path}.pm";
+
+ bless( \%args, $class );
+}
+
+sub call {
+ my ($self, $method) = @_;
+ my $obj = $self->load($method) or return;
+ splice(@_, 0, 2, $obj);
+ goto &{$obj->can($method)};
+}
+
+sub load {
+ my ($self, $method) = @_;
+
+ $self->load_extensions(
+ "$self->{prefix}/$self->{path}", $self
+ ) unless $self->{extensions};
+
+ foreach my $obj (@{$self->{extensions}}) {
+ return $obj if $obj->can($method);
+ }
+
+ my $admin = $self->{admin} or die <<"END_DIE";
+The '$method' method does not exist in the '$self->{prefix}' path!
+Please remove the '$self->{prefix}' directory and run $0 again to load it.
+END_DIE
+
+ my $obj = $admin->load($method, 1);
+ push @{$self->{extensions}}, $obj;
+
+ $obj;
+}
+
+sub load_extensions {
+ my ($self, $path, $top) = @_;
+
+ unless ( grep { lc $_ eq lc $self->{prefix} } @INC ) {
+ unshift @INC, $self->{prefix};
+ }
+
+ local @INC = ($path, @INC);
+ foreach my $rv ( $self->find_extensions($path) ) {
+ my ($file, $pkg) = @{$rv};
+ next if $self->{pathnames}{$pkg};
+
+ local $@;
+ my $new = eval { require $file; $pkg->can('new') };
+ unless ( $new ) {
+ warn $@ if $@;
+ next;
+ }
+ $self->{pathnames}{$pkg} = delete $INC{$file};
+ push @{$self->{extensions}}, &{$new}($pkg, _top => $top );
+ }
+
+ $self->{extensions} ||= [];
+}
+
+sub find_extensions {
+ my ($self, $path) = @_;
+
+ my @found;
+ File::Find::find( sub {
+ my $file = $File::Find::name;
+ return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is;
+ my $subpath = $1;
+ return if lc($subpath) eq lc($self->{dispatch});
+
+ $file = "$self->{path}/$subpath.pm";
+ my $pkg = "$self->{name}::$subpath";
+ $pkg =~ s!/!::!g;
+
+ # If we have a mixed-case package name, assume case has been preserved
+ # correctly. Otherwise, root through the file to locate the case-preserved
+ # version of the package name.
+ if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) {
+ open PKGFILE, "<$subpath.pm" or die "find_extensions: Can't open $subpath.pm: $!";
+ my $in_pod = 0;
+ while ( <PKGFILE> ) {
+ $in_pod = 1 if /^=\w/;
+ $in_pod = 0 if /^=cut/;
+ next if ($in_pod || /^=cut/); # skip pod text
+ next if /^\s*#/; # and comments
+ if ( m/^\s*package\s+($pkg)\s*;/i ) {
+ $pkg = $1;
+ last;
+ }
+ }
+ close PKGFILE;
+ }
+
+ push @found, [ $file, $pkg ];
+ }, $path ) if -d $path;
+
+ @found;
+}
+
+sub _caller {
+ my $depth = 0;
+ my $call = caller($depth);
+ while ( $call eq __PACKAGE__ ) {
+ $depth++;
+ $call = caller($depth);
+ }
+ return $call;
+}
+
+1;
Added: Locale-Maketext-Lexicon/inc/Module/Install/Base.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Base.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,70 @@
+#line 1
+package Module::Install::Base;
+
+$VERSION = '0.61';
+
+# Suspend handler for "redefined" warnings
+BEGIN {
+ my $w = $SIG{__WARN__};
+ $SIG{__WARN__} = sub { $w };
+}
+
+### This is the ONLY module that shouldn't have strict on
+# use strict;
+
+#line 41
+
+sub new {
+ my ($class, %args) = @_;
+
+ foreach my $method ( qw(call load) ) {
+ *{"$class\::$method"} = sub {
+ shift()->_top->$method(@_);
+ } unless defined &{"$class\::$method"};
+ }
+
+ bless( \%args, $class );
+}
+
+#line 61
+
+sub AUTOLOAD {
+ my $self = shift;
+ local $@;
+ my $autoload = eval { $self->_top->autoload } or return;
+ goto &$autoload;
+}
+
+#line 76
+
+sub _top { $_[0]->{_top} }
+
+#line 89
+
+sub admin {
+ $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new;
+}
+
+sub is_admin {
+ $_[0]->admin->VERSION;
+}
+
+sub DESTROY {}
+
+package Module::Install::Base::FakeAdmin;
+
+my $Fake;
+sub new { $Fake ||= bless(\@_, $_[0]) }
+
+sub AUTOLOAD {}
+
+sub DESTROY {}
+
+# Restore warning handler
+BEGIN {
+ $SIG{__WARN__} = $SIG{__WARN__}->();
+}
+
+1;
+
+#line 138
Added: Locale-Maketext-Lexicon/inc/Module/Install/Can.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Can.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,82 @@
+#line 1
+package Module::Install::Can;
+
+use strict;
+use Module::Install::Base;
+use Config ();
+### This adds a 5.005 Perl version dependency.
+### This is a bug and will be fixed.
+use File::Spec ();
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION @ISA};
+BEGIN {
+ $VERSION = '0.61';
+ @ISA = qw{Module::Install::Base};
+}
+
+
+# check if we can load some module
+### Upgrade this to not have to load the module if possible
+sub can_use {
+ my ($self, $mod, $ver) = @_;
+ $mod =~ s{::|\\}{/}g;
+ $mod .= '.pm' unless $mod =~ /\.pm$/i;
+
+ my $pkg = $mod;
+ $pkg =~ s{/}{::}g;
+ $pkg =~ s{\.pm$}{}i;
+
+ local $@;
+ eval { require $mod; $pkg->VERSION($ver || 0); 1 };
+}
+
+# check if we can run some command
+sub can_run {
+ my ($self, $cmd) = @_;
+
+ my $_cmd = $cmd;
+ return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
+
+ for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
+ my $abs = File::Spec->catfile($dir, $_[1]);
+ return $abs if (-x $abs or $abs = MM->maybe_command($abs));
+ }
+
+ return;
+}
+
+# can we locate a (the) C compiler
+sub can_cc {
+ my $self = shift;
+ my @chunks = split(/ /, $Config::Config{cc}) or return;
+
+ # $Config{cc} may contain args; try to find out the program part
+ while (@chunks) {
+ return $self->can_run("@chunks") || (pop(@chunks), next);
+ }
+
+ return;
+}
+
+# Fix Cygwin bug on maybe_command();
+if ( $^O eq 'cygwin' ) {
+ require ExtUtils::MM_Cygwin;
+ require ExtUtils::MM_Win32;
+ if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
+ *ExtUtils::MM_Cygwin::maybe_command = sub {
+ my ($self, $file) = @_;
+ if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
+ ExtUtils::MM_Win32->maybe_command($file);
+ } else {
+ ExtUtils::MM_Unix->maybe_command($file);
+ }
+ }
+ }
+}
+
+1;
+
+__END__
+
+#line 157
Added: Locale-Maketext-Lexicon/inc/Module/Install/Fetch.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Fetch.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,92 @@
+#line 1
+package Module::Install::Fetch;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA};
+BEGIN {
+ $VERSION = '0.61';
+ @ISA = qw{Module::Install::Base};
+}
+
+sub get_file {
+ my ($self, %args) = @_;
+ my ($scheme, $host, $path, $file) =
+ $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+
+ if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
+ $args{url} = $args{ftp_url}
+ or (warn("LWP support unavailable!\n"), return);
+ ($scheme, $host, $path, $file) =
+ $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
+ }
+
+ $|++;
+ print "Fetching '$file' from $host... ";
+
+ unless (eval { require Socket; Socket::inet_aton($host) }) {
+ warn "'$host' resolve failed!\n";
+ return;
+ }
+
+ return unless $scheme eq 'ftp' or $scheme eq 'http';
+
+ require Cwd;
+ my $dir = Cwd::getcwd();
+ chdir $args{local_dir} or return if exists $args{local_dir};
+
+ if (eval { require LWP::Simple; 1 }) {
+ LWP::Simple::mirror($args{url}, $file);
+ }
+ elsif (eval { require Net::FTP; 1 }) { eval {
+ # use Net::FTP to get past firewall
+ my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
+ $ftp->login("anonymous", 'anonymous at example.com');
+ $ftp->cwd($path);
+ $ftp->binary;
+ $ftp->get($file) or (warn("$!\n"), return);
+ $ftp->quit;
+ } }
+ elsif (my $ftp = $self->can_run('ftp')) { eval {
+ # no Net::FTP, fallback to ftp.exe
+ require FileHandle;
+ my $fh = FileHandle->new;
+
+ local $SIG{CHLD} = 'IGNORE';
+ unless ($fh->open("|$ftp -n")) {
+ warn "Couldn't open ftp: $!\n";
+ chdir $dir; return;
+ }
+
+ my @dialog = split(/\n/, <<"END_FTP");
+open $host
+user anonymous anonymous\@example.com
+cd $path
+binary
+get $file $file
+quit
+END_FTP
+ foreach (@dialog) { $fh->print("$_\n") }
+ $fh->close;
+ } }
+ else {
+ warn "No working 'ftp' program available!\n";
+ chdir $dir; return;
+ }
+
+ unless (-f $file) {
+ warn "Fetching failed: $@\n";
+ chdir $dir; return;
+ }
+
+ return if exists $args{size} and -s $file != $args{size};
+ system($args{run}) if exists $args{run};
+ unlink($file) if $args{remove};
+
+ print(((!exists $args{check_for} or -e $args{check_for})
+ ? "done!" : "failed! ($!)"), "\n");
+ chdir $dir; return !$?;
+}
+
+1;
Added: Locale-Maketext-Lexicon/inc/Module/Install/Makefile.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Makefile.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,186 @@
+#line 1
+package Module::Install::Makefile;
+
+use strict 'vars';
+use Module::Install::Base;
+use ExtUtils::MakeMaker ();
+
+use vars qw{$VERSION @ISA};
+BEGIN {
+ $VERSION = '0.61';
+ @ISA = qw{Module::Install::Base};
+}
+
+sub Makefile { $_[0] }
+
+my %seen = ();
+
+sub prompt {
+ shift;
+
+ # Infinite loop protection
+ my @c = caller();
+ if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) {
+ die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])";
+ }
+
+ # In automated testing, always use defaults
+ if ( $ENV{AUTOMATED_TESTING} and ! $ENV{PERL_MM_USE_DEFAULT} ) {
+ local $ENV{PERL_MM_USE_DEFAULT} = 1;
+ goto &ExtUtils::MakeMaker::prompt;
+ } else {
+ goto &ExtUtils::MakeMaker::prompt;
+ }
+}
+
+sub makemaker_args {
+ my $self = shift;
+ my $args = ($self->{makemaker_args} ||= {});
+ %$args = ( %$args, @_ ) if @_;
+ $args;
+}
+
+# For mm args that take multiple space-seperated args,
+# append an argument to the current list.
+sub makemaker_append {
+ my $self = shift;
+ my $name = shift;
+ my $args = $self->makemaker_args;
+ $args->{name} = defined $args->{$name}
+ ? join( ' ', $args->{name}, @_ )
+ : join( ' ', @_ );
+}
+
+sub build_subdirs {
+ my $self = shift;
+ my $subdirs = $self->makemaker_args->{DIR} ||= [];
+ for my $subdir (@_) {
+ push @$subdirs, $subdir;
+ }
+}
+
+sub clean_files {
+ my $self = shift;
+ my $clean = $self->makemaker_args->{clean} ||= {};
+ %$clean = (
+ %$clean,
+ FILES => join(' ', grep length, $clean->{FILES}, @_),
+ );
+}
+
+sub libs {
+ my $self = shift;
+ my $libs = ref $_[0] ? shift : [ shift ];
+ $self->makemaker_args( LIBS => $libs );
+}
+
+sub inc {
+ my $self = shift;
+ $self->makemaker_args( INC => shift );
+}
+
+sub write {
+ my $self = shift;
+ die "&Makefile->write() takes no arguments\n" if @_;
+
+ my $args = $self->makemaker_args;
+ $args->{DISTNAME} = $self->name;
+ $args->{NAME} = $self->module_name || $self->name || $self->determine_NAME($args);
+ $args->{VERSION} = $self->version || $self->determine_VERSION($args);
+ $args->{NAME} =~ s/-/::/g;
+ if ( $self->tests ) {
+ $args->{test} = { TESTS => $self->tests };
+ }
+ if ($] >= 5.005) {
+ $args->{ABSTRACT} = $self->abstract;
+ $args->{AUTHOR} = $self->author;
+ }
+ if ( eval($ExtUtils::MakeMaker::VERSION) >= 6.10 ) {
+ $args->{NO_META} = 1;
+ }
+ if ( eval($ExtUtils::MakeMaker::VERSION) > 6.17 and $self->sign ) {
+ $args->{SIGN} = 1;
+ }
+ unless ( $self->is_admin ) {
+ delete $args->{SIGN};
+ }
+
+ # merge both kinds of requires into prereq_pm
+ my $prereq = ($args->{PREREQ_PM} ||= {});
+ %$prereq = ( %$prereq, map { @$_ } map { @$_ } grep $_,
+ ($self->build_requires, $self->requires) );
+
+ # merge both kinds of requires into prereq_pm
+ my $subdirs = ($args->{DIR} ||= []);
+ if ($self->bundles) {
+ foreach my $bundle (@{ $self->bundles }) {
+ my ($file, $dir) = @$bundle;
+ push @$subdirs, $dir if -d $dir;
+ delete $prereq->{$file};
+ }
+ }
+
+ if ( my $perl_version = $self->perl_version ) {
+ eval "use $perl_version; 1"
+ or die "ERROR: perl: Version $] is installed, "
+ . "but we need version >= $perl_version";
+ }
+
+ my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_})} keys %$args;
+ if ($self->admin->preop) {
+ $args{dist} = $self->admin->preop;
+ }
+
+ my $mm = ExtUtils::MakeMaker::WriteMakefile(%args);
+ $self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile');
+}
+
+sub fix_up_makefile {
+ my $self = shift;
+ my $makefile_name = shift;
+ my $top_class = ref($self->_top) || '';
+ my $top_version = $self->_top->VERSION || '';
+
+ my $preamble = $self->preamble
+ ? "# Preamble by $top_class $top_version\n"
+ . $self->preamble
+ : '';
+ my $postamble = "# Postamble by $top_class $top_version\n"
+ . ($self->postamble || '');
+
+ local *MAKEFILE;
+ open MAKEFILE, "< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+ my $makefile = do { local $/; <MAKEFILE> };
+ close MAKEFILE or die $!;
+
+ $makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /;
+ $makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g;
+ $makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g;
+ $makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m;
+ $makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m;
+
+ open MAKEFILE, "> $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!";
+ print MAKEFILE "$preamble$makefile$postamble" or die $!;
+ close MAKEFILE or die $!;
+
+ 1;
+}
+
+sub preamble {
+ my ($self, $text) = @_;
+ $self->{preamble} = $text . $self->{preamble} if defined $text;
+ $self->{preamble};
+}
+
+sub postamble {
+ my ($self, $text) = @_;
+ $self->{postamble} ||= $self->admin->postamble;
+ $self->{postamble} .= $text if defined $text;
+ $self->{postamble}
+}
+
+1;
+
+__END__
+
+#line 312
Added: Locale-Maketext-Lexicon/inc/Module/Install/Metadata.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Metadata.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,310 @@
+#line 1
+package Module::Install::Metadata;
+
+use Module::Install::Base;
+ at ISA = qw{Module::Install::Base};
+
+$VERSION = '0.61';
+
+use strict 'vars';
+
+my @scalar_keys = qw{
+ name module_name abstract author version license
+ distribution_type perl_version tests
+};
+
+my @tuple_keys = qw{
+ build_requires requires recommends bundles
+};
+
+sub Meta { shift }
+sub Meta_ScalarKeys { @scalar_keys }
+sub Meta_TupleKeys { @tuple_keys }
+
+foreach my $key (@scalar_keys) {
+ *$key = sub {
+ my $self = shift;
+ return $self->{values}{$key} if defined wantarray and !@_;
+ $self->{values}{$key} = shift;
+ return $self;
+ };
+}
+
+foreach my $key (@tuple_keys) {
+ *$key = sub {
+ my $self = shift;
+ return $self->{values}{$key} unless @_;
+
+ my @rv;
+ while (@_) {
+ my $module = shift or last;
+ my $version = shift || 0;
+ if ( $module eq 'perl' ) {
+ $version =~ s{^(\d+)\.(\d+)\.(\d+)}
+ {$1 + $2/1_000 + $3/1_000_000}e;
+ $self->perl_version($version);
+ next;
+ }
+ my $rv = [ $module, $version ];
+ push @rv, $rv;
+ }
+ push @{ $self->{values}{$key} }, @rv;
+ @rv;
+ };
+}
+
+sub sign {
+ my $self = shift;
+ return $self->{'values'}{'sign'} if defined wantarray and !@_;
+ $self->{'values'}{'sign'} = ( @_ ? $_[0] : 1 );
+ return $self;
+}
+
+sub dynamic_config {
+ my $self = shift;
+ unless ( @_ ) {
+ warn "You MUST provide an explicit true/false value to dynamic_config, skipping\n";
+ return $self;
+ }
+ $self->{'values'}{'dynamic_config'} = $_[0] ? 1 : 0;
+ return $self;
+}
+
+sub all_from {
+ my ( $self, $file ) = @_;
+
+ unless ( defined($file) ) {
+ my $name = $self->name
+ or die "all_from called with no args without setting name() first";
+ $file = join('/', 'lib', split(/-/, $name)) . '.pm';
+ $file =~ s{.*/}{} unless -e $file;
+ die "all_from: cannot find $file from $name" unless -e $file;
+ }
+
+ $self->version_from($file) unless $self->version;
+ $self->perl_version_from($file) unless $self->perl_version;
+
+ # The remaining probes read from POD sections; if the file
+ # has an accompanying .pod, use that instead
+ my $pod = $file;
+ if ( $pod =~ s/\.pm$/.pod/i and -e $pod ) {
+ $file = $pod;
+ }
+
+ $self->author_from($file) unless $self->author;
+ $self->license_from($file) unless $self->license;
+ $self->abstract_from($file) unless $self->abstract;
+}
+
+sub provides {
+ my $self = shift;
+ my $provides = ( $self->{values}{provides} ||= {} );
+ %$provides = (%$provides, @_) if @_;
+ return $provides;
+}
+
+sub auto_provides {
+ my $self = shift;
+ return $self unless $self->is_admin;
+
+ unless (-e 'MANIFEST') {
+ warn "Cannot deduce auto_provides without a MANIFEST, skipping\n";
+ return $self;
+ }
+
+ # Avoid spurious warnings as we are not checking manifest here.
+
+ local $SIG{__WARN__} = sub {1};
+ require ExtUtils::Manifest;
+ local *ExtUtils::Manifest::manicheck = sub { return };
+
+ require Module::Build;
+ my $build = Module::Build->new(
+ dist_name => $self->{name},
+ dist_version => $self->{version},
+ license => $self->{license},
+ );
+ $self->provides(%{ $build->find_dist_packages || {} });
+}
+
+sub feature {
+ my $self = shift;
+ my $name = shift;
+ my $features = ( $self->{values}{features} ||= [] );
+
+ my $mods;
+
+ if ( @_ == 1 and ref( $_[0] ) ) {
+ # The user used ->feature like ->features by passing in the second
+ # argument as a reference. Accomodate for that.
+ $mods = $_[0];
+ } else {
+ $mods = \@_;
+ }
+
+ my $count = 0;
+ push @$features, (
+ $name => [
+ map {
+ ref($_) ? ( ref($_) eq 'HASH' ) ? %$_
+ : @$_
+ : $_
+ } @$mods
+ ]
+ );
+
+ return @$features;
+}
+
+sub features {
+ my $self = shift;
+ while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) {
+ $self->feature( $name, @$mods );
+ }
+ return $self->{values}->{features}
+ ? @{ $self->{values}->{features} }
+ : ();
+}
+
+sub no_index {
+ my $self = shift;
+ my $type = shift;
+ push @{ $self->{values}{no_index}{$type} }, @_ if $type;
+ return $self->{values}{no_index};
+}
+
+sub read {
+ my $self = shift;
+ $self->include_deps( 'YAML', 0 );
+
+ require YAML;
+ my $data = YAML::LoadFile('META.yml');
+
+ # Call methods explicitly in case user has already set some values.
+ while ( my ( $key, $value ) = each %$data ) {
+ next unless $self->can($key);
+ if ( ref $value eq 'HASH' ) {
+ while ( my ( $module, $version ) = each %$value ) {
+ $self->can($key)->($self, $module => $version );
+ }
+ }
+ else {
+ $self->can($key)->($self, $value);
+ }
+ }
+ return $self;
+}
+
+sub write {
+ my $self = shift;
+ return $self unless $self->is_admin;
+ $self->admin->write_meta;
+ return $self;
+}
+
+sub version_from {
+ my ( $self, $file ) = @_;
+ require ExtUtils::MM_Unix;
+ $self->version( ExtUtils::MM_Unix->parse_version($file) );
+}
+
+sub abstract_from {
+ my ( $self, $file ) = @_;
+ require ExtUtils::MM_Unix;
+ $self->abstract(
+ bless(
+ { DISTNAME => $self->name },
+ 'ExtUtils::MM_Unix'
+ )->parse_abstract($file)
+ );
+}
+
+sub _slurp {
+ my ( $self, $file ) = @_;
+
+ local *FH;
+ open FH, "< $file" or die "Cannot open $file.pod: $!";
+ do { local $/; <FH> };
+}
+
+sub perl_version_from {
+ my ( $self, $file ) = @_;
+
+ if (
+ $self->_slurp($file) =~ m/
+ ^
+ use \s*
+ v?
+ ([\d\.]+)
+ \s* ;
+ /ixms
+ )
+ {
+ $self->perl_version($1);
+ }
+ else {
+ warn "Cannot determine perl version info from $file\n";
+ return;
+ }
+}
+
+sub author_from {
+ my ( $self, $file ) = @_;
+ my $content = $self->_slurp($file);
+ if ($content =~ m/
+ =head \d \s+ (?:authors?)\b \s*
+ ([^\n]*)
+ |
+ =head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s*
+ .*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s*
+ ([^\n]*)
+ /ixms) {
+ my $author = $1 || $2;
+ $author =~ s{E<lt>}{<}g;
+ $author =~ s{E<gt>}{>}g;
+ $self->author($author);
+ }
+ else {
+ warn "Cannot determine author info from $file\n";
+ }
+}
+
+sub license_from {
+ my ( $self, $file ) = @_;
+
+ if (
+ $self->_slurp($file) =~ m/
+ =head \d \s+
+ (?:licen[cs]e|licensing|copyright|legal)\b
+ (.*?)
+ (=head\\d.*|=cut.*|)
+ \z
+ /ixms
+ )
+ {
+ my $license_text = $1;
+ my @phrases = (
+ 'under the same (?:terms|license) as perl itself' => 'perl',
+ 'GNU public license' => 'gpl',
+ 'GNU lesser public license' => 'gpl',
+ 'BSD license' => 'bsd',
+ 'Artistic license' => 'artistic',
+ 'GPL' => 'gpl',
+ 'LGPL' => 'lgpl',
+ 'BSD' => 'bsd',
+ 'Artistic' => 'artistic',
+ );
+ while ( my ( $pattern, $license ) = splice( @phrases, 0, 2 ) ) {
+ $pattern =~ s{\s+}{\\s+}g;
+ if ( $license_text =~ /\b$pattern\b/i ) {
+ $self->license($license);
+ return 1;
+ }
+ }
+ }
+
+ warn "Cannot determine license info from $file\n";
+ return 'unknown';
+}
+
+1;
Added: Locale-Maketext-Lexicon/inc/Module/Install/Scripts.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Scripts.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,49 @@
+#line 1
+package Module::Install::Scripts;
+
+use strict;
+use Module::Install::Base;
+use File::Basename ();
+
+use vars qw{$VERSION @ISA};
+BEGIN {
+ $VERSION = '0.61';
+ @ISA = qw(Module::Install::Base);
+}
+
+sub prompt_script {
+ my ($self, $script_file) = @_;
+
+ my ($prompt, $abstract, $default);
+ foreach my $line ( $self->_read_script($script_file) ) {
+ last unless $line =~ /^#/;
+ $prompt = $1 if $line =~ /^#\s*prompt:\s+(.*)/;
+ $default = $1 if $line =~ /^#\s*default:\s+(.*)/;
+ $abstract = $1 if $line =~ /^#\s*abstract:\s+(.*)/;
+ }
+ unless (defined $prompt) {
+ my $script_name = File::Basename::basename($script_file);
+ $prompt = "Do you want to install '$script_name'";
+ $prompt .= " ($abstract)" if defined $abstract;
+ $prompt .= '?';
+ }
+ return unless $self->prompt($prompt, ($default || 'n')) =~ /^[Yy]/;
+ $self->install_script($script_file);
+}
+
+sub install_script {
+ my $self = shift;
+ my $args = $self->makemaker_args;
+ my $exe_files = $args->{EXE_FILES} ||= [];
+ push @$exe_files, @_;
+}
+
+sub _read_script {
+ my ($self, $script_file) = @_;
+ local *SCRIPT;
+ open SCRIPT, $script_file
+ or die "Can't open '$script_file' for input: $!\n";
+ return <SCRIPT>;
+}
+
+1;
Added: Locale-Maketext-Lexicon/inc/Module/Install/Win32.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/Win32.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,64 @@
+#line 1
+package Module::Install::Win32;
+
+use strict;
+use Module::Install::Base;
+
+use vars qw{$VERSION @ISA};
+BEGIN {
+ $VERSION = '0.61';
+ @ISA = qw{Module::Install::Base};
+}
+
+# determine if the user needs nmake, and download it if needed
+sub check_nmake {
+ my $self = shift;
+ $self->load('can_run');
+ $self->load('get_file');
+
+ require Config;
+ return unless (
+ $^O eq 'MSWin32' and
+ $Config::Config{make} and
+ $Config::Config{make} =~ /^nmake\b/i and
+ ! $self->can_run('nmake')
+ );
+
+ print "The required 'nmake' executable not found, fetching it...\n";
+
+ require File::Basename;
+ my $rv = $self->get_file(
+ url => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
+ ftp_url => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
+ local_dir => File::Basename::dirname($^X),
+ size => 51928,
+ run => 'Nmake15.exe /o > nul',
+ check_for => 'Nmake.exe',
+ remove => 1,
+ );
+
+ if (!$rv) {
+ die <<'END_MESSAGE';
+
+-------------------------------------------------------------------------------
+
+Since you are using Microsoft Windows, you will need the 'nmake' utility
+before installation. It's available at:
+
+ http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
+ or
+ ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
+
+Please download the file manually, save it to a directory in %PATH% (e.g.
+C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
+that directory, and run "Nmake15.exe" from there; that will create the
+'nmake.exe' file needed by this module.
+
+You may then resume the installation process described in README.
+
+-------------------------------------------------------------------------------
+END_MESSAGE
+ }
+}
+
+1;
Added: Locale-Maketext-Lexicon/inc/Module/Install/WriteAll.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/inc/Module/Install/WriteAll.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,40 @@
+#line 1
+package Module::Install::WriteAll;
+
+use Module::Install::Base;
+ at ISA = qw(Module::Install::Base);
+
+$VERSION = '0.61';
+
+use strict;
+
+sub WriteAll {
+ my $self = shift;
+ my %args = (
+ meta => 1,
+ sign => 0,
+ inline => 0,
+ check_nmake => 1,
+ @_
+ );
+
+ $self->sign(1) if $args{sign};
+ $self->Meta->write if $args{meta};
+ $self->admin->WriteAll(%args) if $self->is_admin;
+
+ if ( $0 =~ /Build.PL$/i ) {
+ $self->Build->write;
+ } else {
+ $self->check_nmake if $args{check_nmake};
+ unless ( $self->makemaker_args->{'PL_FILES'} ) {
+ $self->makemaker_args( PL_FILES => {} );
+ }
+ if ($args{inline}) {
+ $self->Inline->write;
+ } else {
+ $self->Makefile->write;
+ }
+ }
+}
+
+1;
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,492 @@
+package Locale::Maketext::Extract;
+$Locale::Maketext::Extract::VERSION = '0.11';
+
+use strict;
+
+=head1 NAME
+
+Locale::Maketext::Extract - Extract translatable strings from source
+
+=head1 SYNOPSIS
+
+ my $Ext = Locale::Maketext::Extract->new;
+ $Ext->read_po('messages.po');
+ $Ext->extract_file($_) for <*.pl>;
+ $Ext->compile;
+ $Ext->write_po('messages.po');
+
+=head1 DESCRIPTION
+
+This module can extract translatable strings from files, and write
+them back to PO files. It can also parse existing PO files and merge
+their contents with newly extracted strings.
+
+A command-line utility, L<xgettext.pl>, is installed with this module
+as well.
+
+Following formats of input files are supported:
+
+=over 4
+
+=item Perl source files
+
+Valid localization function names are: C<translate>, C<maketext>,
+C<gettext>, C<loc>, C<x>, C<_> and C<__>.
+
+=item HTML::Mason
+
+Strings inside C<E<lt>&|/lE<gt>I<...>E<lt>/&E<gt>> and
+C<E<lt>&|/locE<gt>I<...>E<lt>/&E<gt>> are extracted.
+
+=item Template Toolkit
+
+Strings inside C<[%|l%]...[%END%]> or C<[%|loc%]...[%END%]>
+are extracted.
+
+=item Text::Template
+
+Sentences between C<STARTxxx> and C<ENDxxx> are extracted individually.
+
+=item Generic Template
+
+Strings inside {{...}} are extracted.
+
+=back
+
+=head1 METHODS
+
+=head2 Constructor
+
+ new
+
+=cut
+
+sub new {
+ my $class = shift;
+ bless({ header => '', entries => {}, lexicon => {}, @_ }, $class);
+}
+
+=head2 Accessors
+
+ header, set_header
+ lexicon, set_lexicon, msgstr, set_msgstr
+ entries, set_entries, entry, add_entry, del_entry
+ clear
+
+=cut
+
+sub header { $_[0]{header} || _default_header() };
+sub set_header { $_[0]{header} = $_[1] };
+
+sub lexicon { $_[0]{lexicon} }
+sub set_lexicon { $_[0]{lexicon} = $_[1] || {}; delete $_[0]{lexicon}{''}; }
+
+sub msgstr { $_[0]{lexicon}{$_[1]} }
+sub set_msgstr { $_[0]{lexicon}{$_[1]} = $_[2] }
+
+sub entries { $_[0]{entries} }
+sub set_entries { $_[0]{entries} = $_[1] || {} }
+
+sub entry { @{$_[0]->entries->{$_[1]} || [] } }
+sub add_entry { push @{$_[0]->entries->{$_[1]}}, $_[2] }
+sub del_entry { delete $_[0]->entries->{$_[1]} }
+
+sub clear {
+ $_[0]->set_header;
+ $_[0]->set_lexicon;
+ $_[0]->set_entries;
+}
+
+=head2 PO File manipulation
+
+ read_po
+ write_po
+
+=cut
+
+sub read_po {
+ my ($self, $file, $verbatim) = @_;
+ my $header = '';
+
+ local *LEXICON;
+ open LEXICON, $file or die $!;
+ while (<LEXICON>) {
+ (1 .. /^$/) or last;
+ $header .= $_;
+ }
+ 1 while chomp $header;
+
+ $self->set_header("$header\n");
+
+ require Locale::Maketext::Lexicon::Gettext;
+ my $lexicon = Locale::Maketext::Lexicon::Gettext->parse($_, <LEXICON>);
+
+ $self->set_lexicon(
+ $verbatim ? { map _to_gettext($_), %$lexicon } : $lexicon
+ );
+ close LEXICON;
+}
+
+sub write_po {
+ my ($self, $file, $add_format) = @_;
+
+ local *LEXICON;
+ open LEXICON, ">$file" or die "Can't write to $file$!\n";
+
+ print LEXICON $self->header;
+
+ foreach my $msgid ($self->msgids) {
+ $self->normalize_space($msgid);
+ print LEXICON "\n";
+ print LEXICON $self->msg_positions($msgid);
+ print LEXICON $self->msg_variables($msgid);
+ print LEXICON $self->msg_format($msgid) if $add_format;
+ print LEXICON $self->msg_out($msgid);
+ }
+}
+
+=head2 Extraction
+
+ extract
+ extract_file
+
+=cut
+
+use constant NUL => 0;
+use constant BEG => 1;
+use constant PAR => 2;
+use constant QUO1 => 3;
+use constant QUO2 => 4;
+use constant QUO3 => 5;
+sub extract {
+ my $self = shift;
+ my $file = shift;
+ local $_ = shift;
+
+ my $entries = $self->entries;
+ my $line = 1; pos($_) = 0;
+
+ # Text::Template
+ if (/^STARTTEXT$/m and /^ENDTEXT$/m) {
+ require HTML::Parser;
+ require Lingua::EN::Sentence;
+
+ {
+ package MyParser;
+ @MyParser::ISA = 'HTML::Parser';
+ *{'text'} = sub {
+ my ($self, $str, $is_cdata) = @_;
+ my $sentences = Lingua::EN::Sentence::get_sentences($str) or return;
+ $str =~ s/\n/ /g; $str =~ s/^\s+//; $str =~ s/\s+$//;
+ $self->add_entry($str => [$file, $line]);
+ };
+ }
+
+ my $p = MyParser->new;
+ while (m/\G((.*?)^(?:START|END)[A-Z]+$)/smg) {
+ my ($str) = ($2);
+ $line += ( () = ($1 =~ /\n/g) ); # cryptocontext!
+ $p->parse($str); $p->eof;
+ }
+ $_ = '';
+ }
+
+ # HTML::Mason
+ $line = 1; pos($_) = 0;
+ while (m!\G(.*?<&\|/l(?:oc)?(.*?)&>(.*?)</&>)!sg) {
+ my ($vars, $str) = ($2, $3);
+ $line += ( () = ($1 =~ /\n/g) ); # cryptocontext!
+ $self->add_entry($str, [ $file, $line, $vars ]);
+ }
+
+ # Template Toolkit
+ $line = 1; pos($_) = 0;
+ while (m!\G(.*?\[%\s*\|l(?:oc)?(.*?)\s*%\](.*?)\[%\s*END\s*%\])!sg) {
+ my ($vars, $str) = ($2, $3);
+ $line += ( () = ($1 =~ /\n/g) ); # cryptocontext!
+ $vars =~ s/^\s*\(//;
+ $vars =~ s/\)\s*$//;
+ $self->add_entry($str, [ $file, $line, $vars ]);
+ }
+
+ # Generic Template:
+ $line = 1; pos($_) = 0;
+ while (m/\G(.*?(?<!\{)\{\{(?!\{)(.*?)\}\})/sg) {
+ my ($vars, $str) = ('', $2);
+ $line += ( () = ($1 =~ /\n/g) ); # cryptocontext!
+ $self->add_entry($str, [ $file, $line, $vars ]);
+ }
+
+ my $quoted = '(\')([^\\\']*(?:\\.[^\\\']*)*)(\')|(\")([^\\\"]*(?:\\.[^\\\"]*)*)(\")';
+
+ # Comment-based mark: "..." # loc
+ $line = 1; pos($_) = 0;
+ while (m/\G(.*?($quoted)[\}\)\],]*\s*\#\s*loc\s*$)/smog) {
+ my $str = substr($2, 1, -1);
+ $line += ( () = ( $1 =~ /\n/g ) ); # cryptocontext!
+ $str =~ s/\\(["'])/$1/g;
+ $self->add_entry($str, [ $file, $line, '' ]);
+ }
+
+ # Comment-based pair mark: "..." => "..." # loc_pair
+ $line = 1; pos($_) = 0;
+ while (m/\G(.*?(\w+)\s*=>\s*($quoted)[\}\)\],]*\s*\#\s*loc_pair\s*$)/smg) {
+ my $key = $2;
+ my $val = substr($3, 1, -1);
+ $line += ( () = ( $1 =~ /\n/g ) ); # cryptocontext!
+ $key =~ s/\\(["'])/$1/g;
+ $val =~ s/\\(["'])/$1/g;
+ $self->add_entry($key, [ $file, $line, '' ]);
+ $self->add_entry($val, [ $file, $line, '' ]);
+ }
+
+ # Perl code:
+ my ($state,$str,$vars,$quo)=(0);
+ pos($_) = 0;
+ my $orig = 1 + (() = ((my $__ = $_) =~ /\n/g));
+
+ PARSER: {
+ $_ = substr($_, pos($_)) if (pos($_));
+ my $line = $orig - (() = ((my $__ = $_) =~ /\n/g));
+
+ # various ways to spell the localization function
+ $state == NUL && m/\b(translate|maketext|gettext|__?|loc(?:ali[sz]e)?|x)/gc
+ && do { $state = BEG; redo };
+ $state == BEG && m/^([\s\t\n]*)/gc && redo;
+
+ # begin ()
+ $state == BEG && m/^([\S\(])\s*/gc
+ && do { $state = ( ($1 eq '(') ? PAR : NUL); redo };
+
+ # begin or end of string
+ $state == PAR && m/^(\')/gc && do { $state = $quo = QUO1; redo };
+ $state == QUO1 && m/^([^\']+)/gc && do { $str .= $1; redo };
+ $state == QUO1 && m/^\'/gc && do { $state = PAR; redo };
+
+ $state == PAR && m/^\"/gc && do { $state = $quo = QUO2; redo };
+ $state == QUO2 && m/^([^\"]+)/gc && do { $str .= $1; redo };
+ $state == QUO2 && m/^\"/gc && do { $state = PAR; redo };
+
+ $state == PAR && m/^\`/gc && do { $state = $quo = QUO3; redo };
+ $state == QUO3 && m/^([^\`]*)/gc && do { $str .= $1; redo };
+ $state == QUO3 && m/^\`/gc && do { $state = PAR; redo };
+
+ # end ()
+ $state == PAR && m/^\s*[\)]/gc && do {
+ $state = NUL;
+ $vars =~ s/[\n\r]//g if ($vars);
+ if ($quo == QUO1) {
+ $str =~ s/\\([\\'])/$1/g; # normalize q strings
+ }
+ else {
+ $str =~ s/(\\(?:[0x]..|c?.))/"qq($1)"/eeg; # normalize qq / qx strings
+ }
+ push @{$entries->{$str}}, [ $file, $line - (() = $str =~ /\n/g), $vars] if ($str);
+ undef $str; undef $vars;
+ redo;
+ };
+
+ # a line of vars
+ $state == PAR && m/^([^\)]*)/gc && do { $vars .= "$1\n"; redo };
+ }
+}
+
+sub extract_file {
+ my ($self, $file) = @_;
+
+ local($/, *FH);
+ open FH, $file or die $!;
+ $self->extract($file => scalar <FH>);
+ close FH;
+}
+
+=head2 Compilation
+
+ compile
+ normalize_space
+
+=cut
+
+sub compile {
+ my ($self, $verbatim) = @_;
+ my $entries = $self->entries;
+ my $lexicon = $self->lexicon;
+
+ foreach my $str (sort keys %$entries) {
+ my $ostr = $str;
+ my $entry = $entries->{$str};
+ my $lexi = $lexicon->{$ostr};
+
+ $str = _to_gettext($str, $verbatim);
+ $lexi = _to_gettext($lexi, $verbatim);
+
+ $lexicon->{$str} ||= '';
+ next if $ostr eq $str;
+
+ $lexicon->{$str} ||= $lexi;
+ delete $entries->{$ostr}; delete $lexicon->{$ostr};
+ $entries->{$str} = $entry;
+ }
+
+ return %$lexicon;
+}
+
+my %Escapes = map {("\\$_" => eval("qq(\\$_)"))} qw(t r f b a e);
+sub normalize_space {
+ my ($self, $msgid) = @_;
+ my $nospace = $msgid;
+ $nospace =~ s/ +$//;
+
+ return unless (!$self->has_msgid($msgid) and $self->has_msgid($nospace));
+
+ $self->set_msgstr(
+ $msgid => $self->msgstr($nospace) .
+ (' ' x (length($msgid) - length($nospace)))
+ );
+}
+
+=head2 Lexicon accessors
+
+ msgids, has_msgid,
+ msgstr, set_msgstr
+ msg_positions, msg_variables, msg_format, msg_out
+
+=cut
+
+sub msgids { sort keys %{$_[0]{lexicon}} }
+sub has_msgid { length $_[0]->msgstr($_[1]) }
+
+sub msg_positions {
+ my ($self, $msgid) = @_;
+ my %files = (map { ( " $_->[0]:$_->[1]" => 1 ) } $self->entry($msgid));
+ return join('', '#:', sort(keys %files), "\n");
+}
+
+sub msg_variables {
+ my ($self, $msgid) = @_;
+ my $out = '';
+
+ my %seen;
+ foreach my $entry ( grep { $_->[2] } $self->entry($msgid) ) {
+ my ($file, $line, $var) = @$entry;
+ $var =~ s/^\s*,\s*//; $var =~ s/\s*$//;
+ $out .= "#. ($var)\n" unless !length($var) or $seen{$var}++;
+ }
+
+ return $out;
+}
+
+sub msg_format {
+ my ($self, $msgid) = @_;
+ return "#, perl-maketext-format\n" if $msgid =~ /%(?:\d|\w+\([^\)]*\))/;
+ return '';
+}
+
+sub msg_out {
+ my ($self, $msgid) = @_;
+
+ return "msgid " . _format($msgid) .
+ "msgstr " . _format($self->msgstr($msgid));
+}
+
+=head2 Internal utilities
+
+ _default_header
+ _to_gettext
+ _escape
+ _format
+
+=cut
+
+sub _default_header {
+ return << '.';
+# 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 at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+.
+}
+
+sub _to_gettext {
+ my ($text, $verbatim) = @_;
+ return '' unless defined $text;
+
+ $text =~ s/\\/\\\\/g;
+ $text =~ s/\"/\\"/g;
+
+ while (my ($char, $esc) = each %Escapes) {
+ $text =~ s/$esc/$char/g;
+ }
+ return $text if $verbatim;
+
+ $text =~ s/((?<!~)(?:~~)*)\[_(\d+)\]/$1%$2/g;
+ $text =~ s/((?<!~)(?:~~)*)\[([A-Za-z#*]\w*),([^\]]+)\]/$1%$2("""$3""")/g;
+ $text = join('', map {
+ /^""".*"""$/ ? _escape(substr($_, 3, -3)) : $_
+ } split(/(""".*?""")/, $text));
+
+ $text =~ s/~([\~\[\]])/$1/g;
+ return $text;
+}
+
+sub _escape {
+ my $text = shift;
+ $text =~ s/\b_(\d+)/%$1/g;
+ return $text;
+}
+
+sub _format {
+ my $str = shift;
+
+ return "\"$str\"\n" unless $str =~ /\n/;
+ my $multi_line = ($str =~ /\n(?!\z)/);
+ $str =~ s/\n/\\n"\n"/g;
+ if ($str =~ /\n"$/) {
+ chop $str;
+ }
+ else {
+ $str .= "\"\n";
+ }
+ return $multi_line ? qq(""\n"$str) : qq("$str);
+}
+
+1;
+
+=head1 ACKNOWLEDGMENTS
+
+Thanks to Jesse Vincent for contributing to an early version of this
+module.
+
+Also to Alain Barbet, who effectively re-wrote the source parser with a
+flex-like algorithm.
+
+=head1 SEE ALSO
+
+L<xgettext.pl>, L<Locale::Maketext>, L<Locale::Maketext::Lexicon>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2003, 2004 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract/Run.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Extract/Run.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,86 @@
+package Locale::Maketext::Extract::Run;
+
+use strict;
+use vars qw( @ISA @EXPORT_OK );
+
+use Cwd;
+use Config ();
+use File::Find;
+use Getopt::Long;
+use Locale::Maketext::Extract;
+use Exporter;
+
+use constant HAS_SYMLINK => ($Config::Config{d_symlink} ? 1 : 0);
+
+ at ISA = 'Exporter';
+ at EXPORT_OK = 'xgettext';
+
+sub xgettext { __PACKAGE__->run(@_) }
+
+sub run {
+ my $self = shift;
+ local @ARGV = @_;
+
+ my %opts;
+ Getopt::Long::Configure("no_ignore_case");
+ Getopt::Long::GetOptions( \%opts,
+ 'f|files-from:s@',
+ 'D|directory:s@',
+ 'u|unescaped',
+ 'g|gnu-gettext',
+ 'o|output:s@',
+ 'd|default-domain:s',
+ 'p|output-dir:s@',
+ 'h|help',
+ ) or help();
+ help() if $opts{h};
+
+ my @po = @{$opts{o} || [($opts{d}||'messages').'.po']};
+
+ foreach my $file (@{$opts{f}||[]}) {
+ open FILE, $file or die "Cannot open $file: $!";
+ while (<FILE>) {
+ push @ARGV, $_ if -r and !-d;
+ }
+ }
+
+ foreach my $dir (@{$opts{D}||[]}) {
+ File::Find::find( {
+ wanted => sub {
+ return if
+ ( -d ) ||
+ ( $File::Find::dir =~ m!\b(?:blib|autogen|var|m4|local|CVS|\.svn)\b! ) ||
+ ( /\.po$|\.bak$|~|,D|,B$/i ) ||
+ ( /^[\.#]/ );
+ push @ARGV, $File::Find::name;
+ },
+ follow => HAS_SYMLINK,
+ }, $dir );
+ }
+
+ @ARGV = ('-') unless @ARGV;
+ s!^.[/\\]!! for @ARGV;
+
+ my $cwd = getcwd();
+
+ foreach my $dir (@{$opts{p}||['.']}) {
+ foreach my $po (@po) {
+ my $Ext = Locale::Maketext::Extract->new;
+ $Ext->read_po($po, $opts{u}) if -r $po and -s _;
+ $Ext->extract_file($_) for grep !/\.po$/i, @ARGV;
+ $Ext->compile($opts{u}) or next;
+
+ chdir $dir;
+ $Ext->write_po($po, $opts{g});
+ chdir $cwd;
+ }
+ }
+}
+
+sub help {
+ local $SIG{__WARN__} = sub {};
+ { exec "perldoc $0"; }
+ { exec "pod2text $0"; }
+}
+
+1;
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,485 @@
+package Locale::Maketext::Lexicon;
+$Locale::Maketext::Lexicon::VERSION = '0.56';
+
+use strict;
+
+=head1 NAME
+
+Locale::Maketext::Lexicon - Use other catalog formats in Maketext
+
+=head1 VERSION
+
+This document describes version 0.56 of Locale::Maketext::Lexicon,
+released April 2, 2006.
+
+=head1 SYNOPSIS
+
+As part of a localization class, automatically glob for available
+lexicons:
+
+ package Hello::I18N;
+ use base 'Locale::Maketext';
+ use Locale::Maketext::Lexicon {
+ '*' => [Gettext => '/usr/local/share/locale/*/LC_MESSAGES/hello.mo'],
+ _decode => 1, # decode lexicon entries into utf8-strings
+ _auto => 1, # fallback when a key is missing from lexicons
+ };
+
+Explicitly specify languages, during compile- or run-time:
+
+ package Hello::I18N;
+ use base 'Locale::Maketext';
+ use Locale::Maketext::Lexicon {
+ de => [Gettext => 'hello_de.po'],
+ fr => [
+ Gettext => 'hello_fr.po',
+ Gettext => 'local/hello/fr.po',
+ ],
+ };
+ # ... incrementally add new lexicons
+ Locale::Maketext::Lexicon->import({
+ de => [Gettext => 'local/hello/de.po'],
+ })
+
+Alternatively, as part of a localization subclass:
+
+ package Hello::I18N::de;
+ use base 'Hello::I18N';
+ use Locale::Maketext::Lexicon (Gettext => \*DATA);
+ __DATA__
+ # Some sample data
+ msgid ""
+ msgstr ""
+ "Project-Id-Version: Hello 1.3.22.1\n"
+ "MIME-Version: 1.0\n"
+ "Content-Type: text/plain; charset=iso8859-1\n"
+ "Content-Transfer-Encoding: 8bit\n"
+
+ #: Hello.pm:10
+ msgid "Hello, World!"
+ msgstr "Hallo, Welt!"
+
+ #: Hello.pm:11
+ msgid "You have %quant(%1,piece) of mail."
+ msgstr "Sie haben %quant(%1,Poststueck,Poststuecken)."
+
+=head1 DESCRIPTION
+
+This module provides lexicon-handling modules to read from other
+localization formats, such as I<Gettext>, I<Msgcat>, and so on.
+
+If you are unfamiliar with the concept of lexicon modules, please
+consult L<Locale::Maketext> and L<http://www.autrijus.org/webl10n/>
+first.
+
+A command-line utility L<xgettext.pl> is also installed with this
+module, for extracting translatable strings from source files.
+
+=head2 The C<import> function
+
+The C<import()> function accepts two forms of arguments:
+
+=over 4
+
+=item (I<format> => I<source> ... )
+
+This form takes any number of argument pairs (usually one);
+I<source> may be a file name, a filehandle, or an array reference.
+
+For each such pair, it pass the contents specified by the second
+argument to B<Locale::Maketext::Lexicon::I<format>>->parse as a
+plain list, and export its return value as the C<%Lexicon> hash
+in the calling package.
+
+In the case that there are multiple such pairs, the lexicon
+defined by latter ones overrides earlier ones.
+
+=item { I<language> => [ I<format>, I<source> ... ] ... }
+
+This form accepts a hash reference. It will export a C<%Lexicon>
+into the subclasses specified by each I<language>, using the process
+described above. It is designed to alleviate the need to set up a
+separate subclass for each localized language, and just use the catalog
+files.
+
+This module will convert the I<language> arguments into lowercase,
+and replace all C<-> with C<_>, so C<zh_TW> and C<zh-tw> will both
+map to the C<zh_tw> subclass.
+
+If I<language> begins with C<_>, it is taken as an option that
+controls how lexicons are parsed. See L</Options> for a list
+of available options.
+
+The C<*> is a special I<language>; it must be used in conjunction
+with a filename that also contains C<*>; all matched files with
+a valid language code in the place of C<*> will be automatically
+prepared as a lexicon subclass. If there is multiple C<*> in
+the filename, the last one is used as the language name.
+
+=back
+
+=head2 Options
+
+=over 4
+
+=item C<_auto>
+
+If set to a true value, missing lookups on lexicons are handled
+silently, as if an C<Auto> lexicon has been appended on all
+language lexicons.
+
+=item C<_decode>
+
+If set to a true value, source entries will be converted into
+utf8-strings (available in Perl 5.6.1 or later). This feature
+needs the B<Encode> or B<Encode::compat> module.
+
+Currently, only the C<Gettext> backend supports this option.
+
+=item C<_encoding>
+
+This option only has effect when C<_decode> is set to true.
+It specifies an encoding to store lexicon entries, instead of
+utf8-strings.
+
+If C<_encoding> is set to C<locale>, the encoding from the
+current locale setting is used.
+
+=head2 Subclassing format handlers
+
+If you wish to override how sources specified in different data types
+are handled, please use a subclass that overrides C<lexicon_get_I<TYPE>>.
+
+XXX: not documented well enough yet. Patches welcome.
+
+=head1 NOTES
+
+When you attempt to localize an entry missing in the lexicon, Maketext
+will throw an exception by default. To inhibit this behaviour, override
+the C<_AUTO> key in your language subclasses, for example:
+
+ $Hello::I18N::en::Lexicon{_AUTO} = 1; # autocreate missing keys
+
+If you want to implement a new C<Lexicon::*> backend module, please note
+that C<parse()> takes an array containing the B<source strings> from the
+specified filehandle or filename, which are I<not> C<chomp>ed. Although
+if the source is an array reference, its elements will probably not contain
+any newline characters anyway.
+
+The C<parse()> function should return a hash reference, which will be
+assigned to the I<typeglob> (C<*Lexicon>) of the language module. All
+it amounts to is that if the returned reference points to a tied hash,
+the C<%Lexicon> will be aliased to the same tied hash if it was not
+initialized previously.
+
+=cut
+
+our %Opts;
+sub option { shift if ref($_[0]); $Opts{lc $_[0]} }
+sub set_option { shift if ref($_[0]); $Opts{lc $_[0]} = $_[1] }
+
+sub encoding {
+ my $encoding = option(@_, 'encoding') or return;
+ return $encoding unless lc($encoding) eq 'locale';
+
+ no warnings 'uninitialized';
+ my ($country_language, $locale_encoding);
+
+ local $@;
+ eval {
+ require I18N::Langinfo;
+ $locale_encoding = I18N::Langinfo::langinfo(I18N::Langinfo::CODESET());
+ } or eval {
+ require Win32::Console;
+ $locale_encoding = 'cp'.Win32::Console::OutputCP();
+ };
+ if (!$locale_encoding) {
+ foreach my $key (qw( LANGUAGE LC_ALL LC_MESSAGES LANG )) {
+ $ENV{$key} =~ /^([^.]+)\.([^.:]+)/ or next;
+ ($country_language, $locale_encoding) = ($1, $2);
+ last;
+ }
+ }
+ if (defined $locale_encoding &&
+ lc($locale_encoding) eq 'euc' &&
+ defined $country_language) {
+ if ($country_language =~ /^ja_JP|japan(?:ese)?$/i) {
+ $locale_encoding = 'euc-jp';
+ } elsif ($country_language =~ /^ko_KR|korean?$/i) {
+ $locale_encoding = 'euc-kr';
+ } elsif ($country_language =~ /^zh_CN|chin(?:a|ese)?$/i) {
+ $locale_encoding = 'euc-cn';
+ } elsif ($country_language =~ /^zh_TW|taiwan(?:ese)?$/i) {
+ $locale_encoding = 'euc-tw';
+ }
+ }
+
+ return $locale_encoding;
+}
+
+sub import {
+ my $class = shift;
+ return unless @_;
+
+ my %entries;
+ if (UNIVERSAL::isa($_[0], 'HASH')) {
+ # a hashref with $lang as keys, [$format, $src ...] as values
+ %entries = %{$_[0]};
+ }
+ elsif (@_ % 2) {
+ %entries = ( '' => [ @_ ] );
+ }
+
+ # expand the wildcard entry
+ if (my $wild_entry = delete $entries{'*'}) {
+ while (my ($format, $src) = splice(@$wild_entry, 0, 2)) {
+ next if ref($src); # XXX: implement globbing for the 'Tie' backend
+
+ my $pattern = quotemeta($src);
+ $pattern =~ s/\\\*(?=[^*]+$)/\([-\\w]+\)/g or next;
+ $pattern =~ s/\\\*/.*?/g;
+ $pattern =~ s/\\\?/./g;
+ $pattern =~ s/\\\[/[/g;
+ $pattern =~ s/\\\]/]/g;
+ $pattern =~ s[\\\{(.*?)\\\\}][
+ '(?:'.join('|', split(/,/, $1)).')'
+ ]eg;
+
+ require File::Glob;
+ foreach my $file (File::Glob::bsd_glob($src)) {
+ $file =~ /$pattern/ or next;
+ push @{$entries{$1}}, ($format => $file) if $1;
+ }
+ delete $entries{$1}
+ unless !defined($1)
+ or exists $entries{$1} and @{$entries{$1}};
+ }
+ }
+
+ %Opts = ();
+ foreach my $key (grep /^_/, keys %entries) {
+ set_option(lc(substr($key, 1)) => delete($entries{$key}));
+ }
+ my $OptsRef = { %Opts };
+
+ while (my ($lang, $entry) = each %entries) {
+ my $export = caller;
+
+ if (length $lang) {
+ # normalize language tag to Maketext's subclass convention
+ $lang = lc($lang);
+ $lang =~ s/-/_/g;
+ $export .= "::$lang";
+ }
+
+ my @pairs = @{$entry||[]} or die "no format specified";
+
+ while (my ($format, $src) = splice(@pairs, 0, 2)) {
+ if (defined($src) and !ref($src) and $src =~ /\*/) {
+ unshift(@pairs, $format => $_) for File::Glob::bsd_glob($src);
+ next;
+ }
+
+ local $@;
+ my @content = eval {
+ $class->lexicon_get($src, scalar caller, $lang);
+ };
+ next if $@ and $@ eq 'next';
+ die $@ if $@;
+
+ no strict 'refs';
+ eval "use $class\::$format; 1" or die $@;
+
+ if (defined %{"$export\::Lexicon"}) {
+ if (ref(tied %{"$export\::Lexicon"}) eq __PACKAGE__) {
+ tied(%{"$export\::Lexicon"})->_force;
+ }
+ # clear the memoized cache for old entries:
+ Locale::Maketext->clear_isa_scan;
+ # be very careful not to pollute the possibly tied lexicon
+ *{"$export\::Lexicon"} = {
+ %{"$export\::Lexicon"},
+ %{"$class\::$format"->parse(@content)},
+ };
+ }
+ else {
+ my $promise;
+ tie %{"$export\::Lexicon"}, __PACKAGE__, {
+ Opts => $OptsRef,
+ Export => "$export\::Lexicon",
+ Class => "$class\::$format",
+ Content => \@content,
+ };
+ }
+
+ push(@{"$export\::ISA"}, scalar caller) if length $lang;
+ }
+ }
+}
+
+sub TIEHASH {
+ my ($class, $args) = @_;
+ return bless($args, $class);
+
+}
+
+{
+ no strict 'refs';
+ sub _force {
+ my $args = shift;
+ if (!$args->{Done}++) {
+ local *Opts = $args->{Opts};
+ *{$args->{Export}} = $args->{Class}->parse(@{$args->{Content}});
+ if (option('auto')) {
+ (\%{$args->{Export}})->{'_AUTO'} = 1;
+ }
+ }
+ return \%{$args->{Export}};
+ }
+ sub FETCH { _force($_[0])->{$_[1]} }
+ sub EXISTS { _force($_[0])->{$_[1]} }
+ sub DELETE { delete _force($_[0])->{$_[1]} }
+ sub SCALAR { scalar %{_force($_[0])} }
+ sub STORE { _force($_[0])->{$_[1]} = $_[2] }
+ sub CLEAR { %{_force($_[0])->{$_[1]}} = () }
+ sub NEXTKEY { each %{_force($_[0])} }
+ sub FIRSTKEY {
+ my $hash = _force($_[0]);
+ my $a = scalar keys %$hash;
+ each %$hash;
+ }
+}
+
+sub lexicon_get {
+ my ($class, $src, $caller, $lang) = @_;
+ return unless defined $src;
+
+ foreach my $type (qw(ARRAY HASH SCALAR GLOB), ref($src)) {
+ next unless UNIVERSAL::isa($src, $type);
+
+ my $method = 'lexicon_get_' . lc($type);
+ die "cannot handle source $type for $src: no $method defined"
+ unless $class->can($method);
+
+ return $class->$method($src, $caller, $lang);
+ }
+
+ # default handler
+ return $class->lexicon_get_($src, $caller, $lang);
+}
+
+# for scalarrefs and arrayrefs we just dereference the $src
+sub lexicon_get_scalar { ${$_[1]} }
+sub lexicon_get_array { @{$_[1]} }
+
+sub lexicon_get_hash {
+ my ($class, $src, $caller, $lang) = @_;
+ return map { $_ => $src->{$_} } sort keys %$src;
+}
+
+sub lexicon_get_glob {
+ my ($class, $src, $caller, $lang) = @_;
+
+ no strict 'refs';
+
+ # be extra magical and check for DATA section
+ if (eof($src) and $src eq \*{"$caller\::DATA"} or $src eq \*{"main\::DATA"}) {
+ # okay, the *DATA isn't initiated yet. let's read.
+ #
+ require FileHandle;
+ my $fh = FileHandle->new;
+ my $package = ( ($src eq \*{"main\::DATA"}) ? 'main' : $caller );
+
+ if ( $package eq 'main' and -e $0 ) {
+ $fh->open($0) or die "Can't open $0: $!";
+ }
+ else {
+ my $level = 1;
+ while ( my ($pkg, $filename) = caller($level++) ) {
+ next unless $pkg eq $package;
+ next unless -e $filename;
+ next;
+
+ $fh->open($filename) or die "Can't open $filename: $!";
+ last;
+ }
+ }
+
+ while (<$fh>) {
+ # okay, this isn't foolproof, but good enough
+ last if /^__DATA__$/;
+ }
+
+ return <$fh>;
+ }
+
+ # fh containing the lines
+ my $pos = tell($src);
+ my @lines = <$src>;
+ seek($src, $pos, 0);
+ return @lines;
+}
+
+# assume filename - search path, open and return its contents
+sub lexicon_get_ {
+ my ($class, $src, $caller, $lang) = @_;
+
+ require FileHandle;
+ require File::Spec;
+
+ my $fh = FileHandle->new;
+ my @path = split('::', $caller);
+ push @path, $lang if length $lang;
+
+ $src = (grep { -e } map {
+ my @subpath = @path[0..$_];
+ map { File::Spec->catfile($_, @subpath, $src) } @INC;
+ } -1 .. $#path)[-1] unless -e $src;
+
+ defined $src or die 'next';
+
+ $fh->open($src) or die "Cannot read $src (called by $caller): $!";
+ binmode($fh);
+ return <$fh>;
+}
+
+1;
+
+=head1 ACKNOWLEDGMENTS
+
+Thanks to Jesse Vincent for suggesting this module to be written.
+
+Thanks also to Sean M. Burke for coming up with B<Locale::Maketext>
+in the first place, and encouraging me to experiment with alternative
+Lexicon syntaxes.
+
+Thanks also to Yi Ma Mao for providing the MO file parsing subroutine,
+as well as inspiring me to implement file globbing and transcoding
+support.
+
+See the F<AUTHORS> file in the distribution for a list of people who
+have sent helpful patches, ideas or comments.
+
+=head1 SEE ALSO
+
+L<xgettext.pl> for extracting translatable strings from common template
+systems and perl source files.
+
+L<Locale::Maketext>, L<Locale::Maketext::Lexicon::Auto>,
+L<Locale::Maketext::Lexicon::Gettext>, L<Locale::Maketext::Lexicon::Msgcat>,
+L<Locale::Maketext::Lexicon::Tie>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002-2006 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Auto.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Auto.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,59 @@
+package Locale::Maketext::Lexicon::Auto;
+$Locale::Maketext::Lexicon::Auto::VERSION = '0.02';
+
+use strict;
+
+=head1 NAME
+
+Locale::Maketext::Lexicon::Auto - Auto fallback lexicon for Maketext
+
+=head1 SYNOPSIS
+
+ package Hello::I18N;
+ use base 'Locale::Maketext';
+ use Locale::Maketext::Lexicon {
+ en => ['Auto'],
+ # ... other languages
+ };
+
+=head1 DESCRIPTION
+
+This module builds a simple Lexicon hash that contains nothing but
+C<( '_AUTO' =E<gt> 1)>, which tells C<Locale::Maketext> that no
+localizing is needed -- just use the lookup key as the returned string.
+
+It is especially useful if you're starting to prototype a program, and
+do not want to deal with the localization files yet.
+
+=head1 CAVEATS
+
+If the key to C<-E<gt>maketext> begins with a C<_>, C<Locale::Maketext>
+will still throw an exception. See L<Locale::Maketext/CONTROLLING LOOKUP
+FAILURE> for how to prevent it.
+
+=cut
+
+sub parse {
+ return { _AUTO => 1 };
+}
+
+1;
+
+=head1 SEE ALSO
+
+L<Locale::Maketext>, L<Locale::Maketext::Lexicon>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002, 2003, 2004 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Gettext.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Gettext.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,274 @@
+package Locale::Maketext::Lexicon::Gettext;
+$Locale::Maketext::Lexicon::Gettext::VERSION = '0.14';
+
+use strict;
+
+=head1 NAME
+
+Locale::Maketext::Lexicon::Gettext - PO and MO file parser for Maketext
+
+=head1 SYNOPSIS
+
+Called via B<Locale::Maketext::Lexicon>:
+
+ package Hello::I18N;
+ use base 'Locale::Maketext';
+ use Locale::Maketext::Lexicon {
+ de => [Gettext => 'hello/de.mo'],
+ };
+
+Directly calling C<parse()>:
+
+ use Locale::Maketext::Lexicon::Gettext;
+ my %Lexicon = %{ Locale::Maketext::Lexicon::Gettext->parse(<DATA>) };
+ __DATA__
+ #: Hello.pm:10
+ msgid "Hello, World!"
+ msgstr "Hallo, Welt!"
+
+ #: Hello.pm:11
+ msgid "You have %quant(%1,piece) of mail."
+ msgstr "Sie haben %quant(%1,Poststueck,Poststuecken)."
+
+=head1 DESCRIPTION
+
+This module implements a perl-based C<Gettext> parser for
+B<Locale::Maketext>. It transforms all C<%1>, C<%2>, <%*>... sequences
+to C<[_1]>, C<[_2]>, C<[_*]>, and so on. It accepts either plain PO
+file, or a MO file which will be handled with a pure-perl parser
+adapted from Imacat's C<Locale::Maketext::Gettext>.
+
+Since version 0.03, this module also looks for C<%I<function>(I<args...>)>
+in the lexicon strings, and transform it to C<[I<function>,I<args...>]>.
+Any C<%1>, C<%2>... sequences inside the I<args> will have their percent
+signs (C<%>) replaced by underscores (C<_>).
+
+The name of I<function> above should begin with a letter or underscore,
+followed by any number of alphanumeric characters and/or underscores.
+As an exception, the function name may also consist of a single asterisk
+(C<*>) or pound sign (C<#>), which are C<Locale::Maketext>'s shorthands
+for C<quant> and C<numf>, respectively.
+
+As an additional feature, this module also parses MIME-header style
+metadata specified in the null msgstr (C<"">), and add them to the
+C<%Lexicon> with a C<__> prefix. For example, the example above will
+set C<__Content-Type> to C<text/plain; charset=iso8859-1>, without
+the newline or the colon.
+
+Any normal entry that duplicates a metadata entry takes precedence.
+Hence, a C<msgid "__Content-Type"> line occurs anywhere should override
+the above value.
+
+=head1 OPTIONS
+
+=head2 use_fuzzy
+
+When parsing PO files, fuzzy entries (entries marked with C<#, fuzzy>)
+are silently ignored. If you wish to use fuzzy entries, specify a true
+value to the C<_use_fuzzy> option:
+
+ use Locale::Maketext::Lexicon {
+ de => [Gettext => 'hello/de.mo'],
+ _use_fuzzy => 1,
+ };
+
+=head2 allow_empty
+
+When parsing PO files, empty entries (entries with C<msgstr "">) are
+silently ignored. If you wish to allow empty entries, specify a true
+value to the C<_allow_empty> option:
+
+ use Locale::Maketext::Lexicon {
+ de => [Gettext => 'hello/de.mo'],
+ _allow_empty => 1,
+ };
+
+=cut
+
+my ($InputEncoding, $OutputEncoding, $DoEncoding);
+
+sub input_encoding { $InputEncoding };
+sub output_encoding { $OutputEncoding };
+
+sub parse {
+ my $self = shift;
+ my (%var, $key, @ret);
+ my @metadata;
+
+ $InputEncoding = $OutputEncoding = $DoEncoding = undef;
+
+ use Carp;
+ Carp::cluck "Undefined source called\n" unless defined $_[0];
+
+ # Check for magic string of MO files
+ return parse_mo(join('', @_))
+ if ($_[0] =~ /^\x95\x04\x12\xde/ or $_[0] =~ /^\xde\x12\x04\x95/);
+
+ local $^W; # no 'uninitialized' warnings, please.
+
+ require Locale::Maketext::Lexicon;
+ my $UseFuzzy = Locale::Maketext::Lexicon::option('use_fuzzy');
+ my $AllowEmpty = Locale::Maketext::Lexicon::option('allow_empty');
+ my $process = sub {
+ if ( length($var{msgstr}) and ($UseFuzzy or !$var{fuzzy}) ) {
+ push @ret, (map transform($_), @var{'msgid', 'msgstr'});
+ }
+ elsif ( $AllowEmpty ) {
+ push @ret, (transform($var{msgid}), '');
+ }
+ push @metadata, parse_metadata($var{msgstr})
+ if $var{msgid} eq '';
+ %var = ();
+ };
+
+ # Parse PO files
+ foreach (@_) {
+ s/[\015\012]*\z//; # fix CRLF issues
+
+ /^(msgid|msgstr) +"(.*)" *$/ ? do { # leading strings
+ $var{$1} = $2;
+ $key = $1;
+ } :
+
+ /^"(.*)" *$/ ? do { # continued strings
+ $var{$key} .= $1;
+ } :
+
+ /^#, +(.*) *$/ ? do { # control variables
+ $var{$_} = 1 for split(/,\s+/, $1);
+ } :
+
+ /^ *$/ && %var ? do { # interpolate string escapes
+ $process->($_);
+ } : ();
+ }
+ # do not silently skip last entry
+ $process->() if keys %var != 0;
+
+ push @ret, map { transform($_) } @var{'msgid', 'msgstr'}
+ if length $var{msgstr};
+ push @metadata, parse_metadata($var{msgstr})
+ if $var{msgid} eq '';
+
+ return {@metadata, @ret};
+}
+
+sub parse_metadata {
+ return map {
+ (/^([^\x00-\x1f\x80-\xff :=]+):\s*(.*)$/) ?
+ ($1 eq 'Content-Type') ? do {
+ my $enc = $2;
+ if ($enc =~ /\bcharset=\s*([-\w]+)/i) {
+ $InputEncoding = $1 || '';
+ $OutputEncoding = Locale::Maketext::Lexicon::encoding() || '';
+ $InputEncoding = 'utf8' if $InputEncoding =~ /^utf-?8$/i;
+ $OutputEncoding = 'utf8' if $OutputEncoding =~ /^utf-?8$/i;
+ if ( Locale::Maketext::Lexicon::option('decode') and
+ (!$OutputEncoding or $InputEncoding ne $OutputEncoding)) {
+ require Encode::compat if $] < 5.007001;
+ require Encode;
+ $DoEncoding = 1;
+ }
+ }
+ ("__Content-Type", $enc);
+ } : ("__$1", $2)
+ : ();
+ } split(/\r*\n+\r*/, transform(pop));
+}
+
+sub transform {
+ my $str = shift;
+
+ if ($DoEncoding and $InputEncoding) {
+ $str = ($InputEncoding eq 'utf8')
+ ? Encode::decode_utf8($str)
+ : Encode::decode($InputEncoding, $str)
+ }
+
+ $str =~ s/\\([0x]..|c?.)/qq{"\\$1"}/eeg;
+
+ if ($DoEncoding and $OutputEncoding) {
+ $str = ($OutputEncoding eq 'utf8')
+ ? Encode::encode_utf8($str)
+ : Encode::encode($OutputEncoding, $str)
+ }
+
+ $str =~ s/([~\[\]])/~$1/g;
+ $str =~ s/(?<![%\\])%([A-Za-z#*]\w*)\(([^\)]*)\)/[$1,~~~$2~~~]/g;
+ $str = join('', map {
+ /^~~~.*~~~$/ ? unescape(substr($_, 3, -3)) : $_
+ } split(/(~~~.*?~~~)/, $str));
+ $str =~ s/(?<![%\\])%(\d+|\*)/\[_$1]/g;
+
+ return $str;
+}
+
+sub unescape {
+ join(',', map {
+ /^%(?:\d+|\*)$/ ? ("_" . substr($_, 1)) : $_
+ } split(/,/, $_[0]));
+}
+
+# This subroutine was derived from Locale::Maketext::Gettext::readmo()
+# under the Perl License; the original author is Yi Ma Mao (IMACAT).
+sub parse_mo {
+ my $content = shift;
+ my $tmpl = (substr($content, 0, 4) eq "\xde\x12\x04\x95") ? 'V' : 'N';
+
+ # Check the MO format revision number
+ # There is only one revision now: revision 0.
+ return if unpack($tmpl, substr($content, 4, 4)) > 0;
+
+ my ($num, $offo, $offt);
+ # Number of strings
+ $num = unpack $tmpl, substr($content, 8, 4);
+ # Offset to the beginning of the original strings
+ $offo = unpack $tmpl, substr($content, 12, 4);
+ # Offset to the beginning of the translated strings
+ $offt = unpack $tmpl, substr($content, 16, 4);
+
+ my (@metadata, @ret);
+ for (0 .. $num - 1) {
+ my ($len, $off, $stro, $strt);
+ # The first word is the length of the string
+ $len = unpack $tmpl, substr($content, $offo+$_*8, 4);
+ # The second word is the offset of the string
+ $off = unpack $tmpl, substr($content, $offo+$_*8+4, 4);
+ # Original string
+ $stro = substr($content, $off, $len);
+
+ # The first word is the length of the string
+ $len = unpack $tmpl, substr($content, $offt+$_*8, 4);
+ # The second word is the offset of the string
+ $off = unpack $tmpl, substr($content, $offt+$_*8+4, 4);
+ # Translated string
+ $strt = substr($content, $off, $len);
+
+ # Hash it
+ push @metadata, parse_metadata($strt) if $stro eq '';
+ push @ret, (map transform($_), $stro, $strt) if length $strt;
+ }
+
+ return {@metadata, @ret};
+}
+
+1;
+
+=head1 SEE ALSO
+
+L<Locale::Maketext>, L<Locale::Maketext::Lexicon>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002, 2003, 2004 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Msgcat.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Msgcat.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,123 @@
+package Locale::Maketext::Lexicon::Msgcat;
+$Locale::Maketext::Lexicon::Msgcat::VERSION = '0.02';
+
+use strict;
+
+=head1 NAME
+
+Locale::Maketext::Lexicon::Msgcat - Msgcat catalog parser Maketext
+
+=head1 SYNOPSIS
+
+ package Hello::I18N;
+ use base 'Locale::Maketext';
+ use Locale::Maketext::Lexicon {
+ en => ['Msgcat', 'en_US/hello.pl.m'],
+ };
+
+ package main;
+ my $lh = Hello::I18N->get_handle('en');
+ print $lh->maketext(1,2); # set 1, msg 2
+ print $lh->maketext("1,2"); # same thing
+
+=head1 DESCRIPTION
+
+This module parses one or more Msgcat catalogs in plain text format,
+and returns a Lexicon hash, which may be looked up either with a
+two-argument form (C<$set_id, $msg_id>) or as a single string
+(C<"$set_id,$msg_id">).
+
+=head1 NOTES
+
+All special characters (C<[>, C<]> and C<~>) in catalogs will be
+escaped so they lose their magic meanings. That means C<-E<gt>maketext>
+calls to this lexicon will I<not> take any additional arguments.
+
+=cut
+
+sub parse {
+ my $set = 0;
+ my $msg = undef;
+ my ($qr, $qq, $qc) = (qr//, '', '');
+ my @out;
+
+ # Set up the msgcat handler
+ { no strict 'refs';
+ *{Locale::Maketext::msgcat} = \&_msgcat; }
+
+ # Parse *.m files; Locale::Msgcat objects and *.cat are not yet supported.
+ foreach (@_) {
+ s/[\015\012]*\z//; # fix CRLF issues
+
+ /^\$set (\d+)/ ? do { # set_id
+ $set = int($1);
+ push @out, $1, "[msgcat,$1,_1]";
+ } :
+
+ /^\$quote (.)/ ? do { # quote character
+ $qc = $1;
+ $qq = quotemeta($1);
+ $qr = qr/$qq?/;
+ } :
+
+ /^(\d+) ($qr)(.*?)\2(\\?)$/ ? do { # msg_id and msg_str
+ local $^W;
+ push @out, "$set,".int($1);
+ if ($4) {
+ $msg = $3;
+ }
+ else {
+ push @out, unescape($qq, $qc, $3);
+ undef $msg;
+ }
+ } :
+
+ (defined $msg and /^($qr)(.*?)\1(\\?)$/) ? do { # continued string
+ local $^W;
+ if ($3) {
+ $msg .= $2;
+ }
+ else {
+ push @out, unescape($qq, $qc, $msg . $2);
+ undef $msg;
+ }
+ } : ();
+ }
+
+ push @out, '' if defined $msg;
+
+ return { @out };
+}
+
+sub _msgcat {
+ my ($self, $set_id, $msg_id, @args) = @_;
+ return $self->maketext(int($set_id).','.int($msg_id), @args)
+}
+
+sub unescape {
+ my ($qq, $qc, $str) = @_;
+ $str =~ s/(\\([ntvbrf\\$qq]))/($2 eq $qc) ? $qc : eval qq("$1")/e;
+ $str =~ s/([\~\[\]])/~$1/g;
+ return $str;
+}
+
+1;
+
+=head1 SEE ALSO
+
+L<Locale::Maketext>, L<Locale::Maketext::Lexicon>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002, 2003, 2004 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Tie.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/lib/Locale/Maketext/Lexicon/Tie.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,67 @@
+package Locale::Maketext::Lexicon::Tie;
+$Locale::Maketext::Lexicon::Tie::VERSION = '0.03';
+
+use strict;
+use Symbol ();
+
+=head1 NAME
+
+Locale::Maketext::Lexicon::Tie - Use tied hashes as lexicons for Maketext
+
+=head1 SYNOPSIS
+
+ package Hello::I18N;
+ use base 'Locale::Maketext';
+ use Locale::Maketext::Lexicon {
+ en => [ Tie => [ DB_File => 'en.db' ] ],
+ };
+
+=head1 DESCRIPTION
+
+This module lets you easily C<tie> the C<%Lexicon> hash to a database
+or other data sources. It takes an array reference of arguments, and
+passes them directly to C<tie()>.
+
+Entries will then be fetched whenever it is used; this module does not
+cache them.
+
+=cut
+
+sub parse {
+ my $self = shift;
+ my $mod = shift;
+ my $sym = Symbol::gensym();
+
+ # Load the target module into memory
+ {
+ no strict 'refs';
+ eval "use $mod; 1" or die $@ unless defined %{"$mod\::"};
+ }
+
+ # Perform the actual tie
+ tie %{*$sym}, $mod, @_;
+
+ # Returns the GLOB reference, so %Lexicon will be tied too
+ return $sym;
+}
+
+1;
+
+=head1 SEE ALSO
+
+L<Locale::Maketext>, L<Locale::Maketext::Lexicon>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002, 2003, 2004 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/script/xgettext.pl
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/script/xgettext.pl Sun Apr 2 05:40:20 2006
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+use strict;
+use Locale::Maketext::Extract::Run 'xgettext';
+xgettext(@ARGV);
+
+=head1 NAME
+
+xgettext.pl - Extract translatable strings from source
+
+=head1 SYNOPSIS
+
+B<xgettext.pl> [I<OPTION>] [I<INPUTFILE>]...
+
+=head1 DESCRIPTION
+
+This program extracts translatable strings from given input files, or
+from B<STDIN> if none are given.
+
+Please see L<Locale::Maketext::Extract> for a list of supported input file
+formats.
+
+=head1 OPTIONS
+
+Mandatory arguments to long options are mandatory for short options too.
+Similarly for optional arguments.
+
+=head2 Input file location:
+
+=over 4
+
+=item I<INPUTFILE>...
+
+Files to extract messages from. If not specified, B<STDIN> is assumed.
+
+=item B<-f>, B<--files-from>=I<FILE>
+
+Get list of input files from I<FILE>.
+
+=item B<-D>, B<--directory>=I<DIRECTORY>
+
+Add I<DIRECTORY> to list for input files search.
+
+=back
+
+=head2 Output file location:
+
+=over 4
+
+=item B<-d>, B<--default-domain>=I<NAME>
+
+Use I<NAME>.po for output, instead of C<messages.po>.
+
+=item B<-o>, B<--output>=I<FILE>
+
+PO file name to be written or incrementally updated; C<-> means writing
+to B<STDOUT>.
+
+=item B<-p>, B<--output-dir>=I<DIR>
+
+Output files will be placed in directory I<DIR>.
+
+=back
+
+=head2 Output details:
+
+=over 4
+
+=item B<-u>, B<--unescaped>
+
+Disables conversion from B<Maketext> format to B<Gettext> format -- i.e.
+leave all brackets alone. This is useful if you are also using the
+B<Gettext> syntax in your program.
+
+=item B<-g>, B<--gnu-gettext>
+
+Enables GNU gettext interoperability by printing C<#, perl-maketext-format>
+before each entry that has C<%> variables.
+
+=back
+
+=head1 SEE ALSO
+
+L<Locale::Maketext::Extract>,
+L<Locale::Maketext::Lexicon::Gettext>,
+L<Locale::Maketext>
+
+=head1 AUTHORS
+
+Audrey Tang E<lt>audreyt at audreyt.orgE<gt>
+
+=head1 COPYRIGHT
+
+Copyright 2002, 2003, 2004 by Audrey Tang E<lt>audreyt at audreyt.orgE<gt>.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+See L<http://www.perl.com/perl/misc/Artistic.html>
+
+=cut
Added: Locale-Maketext-Lexicon/t/1-basic.t
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/1-basic.t Sun Apr 2 05:40:20 2006
@@ -0,0 +1,234 @@
+#!/usr/bin/perl -w
+use strict;
+use Test::More tests => 30;
+
+package Hello::I18N;
+use Test::More;
+use Tie::Hash;
+
+my $warned;
+$SIG{__WARN__} = sub { $warned++ };
+
+use_ok(base => 'Locale::Maketext');
+use_ok(
+ 'Locale::Maketext::Lexicon' => {
+ en => ['Auto'],
+ fr => ['Tie' => [ 'Tie::StdHash' ]],
+ de => ['Gettext' => \*::DATA],
+ zh_tw => ['Gettext' => 't/messages.mo'],
+ zh_cn => ['Msgcat' => 't/gencat.m'],
+ zh_hk => [
+ 'Msgcat' => 't/gencat.m',
+ 'Gettext' => 't/messages.po',
+ ],
+ }
+);
+
+ok(!$warned, 'no warnings on blank lines');
+
+Locale::Maketext::Lexicon->import({
+ de_de => ['Gettext' => \*::DATA],
+ _use_fuzzy => 1,
+});
+
+package main;
+
+################################################################
+
+ok(my $lh = Hello::I18N->get_handle('en-us'), 'Auto - get_handle');
+
+is(
+ $lh->maketext('Heute die Welt'),
+ 'Heute die Welt',
+ 'Auto - autofilling'
+);
+
+################################################################
+
+ok($lh = Hello::I18N->get_handle('de'), 'Gettext - get_handle');
+
+is(
+ $lh->maketext('Hello, World!'),
+ 'Hallo, Welt!',
+ 'Gettext - simple case'
+);
+is(
+ $lh->maketext('You have [*,_1,piece] of mail.', 10),
+ 'Sie haben 10 Poststuecken.',
+ 'Gettext - complex case'
+);
+is(
+ $lh->maketext('[_1] [_2] [_*]', 1, 2, 3),
+ '123 2 1',
+ 'Gettext - asterisk interpolation'
+);
+is(
+ $lh->maketext('[_1][_2][_*]', 1, 2, 3),
+ '12321',
+ 'Gettext - concatenated variables'
+);
+is(
+ $lh->maketext('[_1]()', 10),
+ '10()',
+ 'Gettext - correct parens'
+);
+is(
+ $lh->maketext('__Content-Type'),
+ 'text/plain; charset=ISO-8859-1',
+ 'Gettext - metadata'
+);
+is(
+ $lh->maketext('[_1]()', 10),
+ '10()',
+ 'Gettext - correct parens'
+);
+is(
+ $lh->maketext("\n\nKnowledge\nAnd\nNature\n\n"),
+"\n\n".
+"Ich wuenschte recht gelehrt zu werden,\n".
+"Und moechte gern, was auf der Erden\n".
+"Und in dem Himmel ist, erfassen,\n".
+"Die Wissenschaft und die Natur.\n\n",
+ 'Gettext - multiline'
+);
+
+is(
+ eval { $lh->maketext("The Hitchhiker's Guide to the Galaxy") },
+ undef,
+ 'Gettext - fuzzy entries are ignored'
+);
+
+ok($lh = Hello::I18N->get_handle('de_de'), 'Gettext - get_handle on DATA again');
+is(
+ eval { $lh->maketext("The Hitchhiker's Guide to the Galaxy") },
+ 'Der Fuehrer des Trampers zur Galaxie',
+ 'Gettext - fuzzy entries are recognized with _use_fuzzy'
+);
+
+################################################################
+
+SKIP: {
+ skip("no msgunfmt available", 2) unless `msgunfmt -V` and !$?;
+
+ ok($lh = Hello::I18N->get_handle('zh_tw'), 'Gettext - get_handle');
+
+ is(
+ $lh->maketext('This is a test'),
+ '這是測試',
+ 'Gettext - MO File'
+ );
+}
+
+################################################################
+
+ok($lh = Hello::I18N->get_handle('fr'), 'Tie - get_handle');
+$Hello::I18N::fr::Lexicon{"Good morning"} = 'Bon jour';
+$Hello::I18N::fr::Lexicon{"Good morning, [_1]"} = 'Bon jour, [_1]';
+
+is(
+ $lh->maketext('Good morning'),
+ 'Bon jour',
+ 'Tie - simple case'
+);
+
+is(
+ $lh->maketext('Good morning, [_1]', 'Sean'),
+ 'Bon jour, Sean',
+ 'Tie - complex case'
+);
+
+################################################################
+
+ok($lh = Hello::I18N->get_handle('zh_cn'), 'Msgcat - get_handle');
+is(
+ $lh->maketext(1, 1),
+ 'First string',
+ 'Msgcat - simple case'
+);
+is(
+ $lh->maketext(1, 2),
+ 'Second string',
+ 'Msgcat - continued string'
+);
+is(
+ $lh->maketext(1, 3),
+ 'Third string',
+ 'Msgcat - quote character'
+);
+is(
+ $lh->maketext(1, 4),
+ 'Fourth string',
+ 'Msgcat - quote character + continued string'
+);
+
+################################################################
+
+ok($lh = Hello::I18N->get_handle('zh_hk'), 'Multiple lexicons - get_handle');
+
+is(
+ $lh->maketext(1, 1),
+ 'First string',
+ 'Multiple lexicons - first'
+);
+
+is(
+ $lh->maketext('This is a test'),
+ '這是測試',
+ 'Multiple lexicons - second'
+);
+
+
+__DATA__
+msgid ""
+msgstr ""
+"Project-Id-Version: Test App 0.01\n"
+"POT-Creation-Date: 2002-05-02 11:36+0800\n"
+"PO-Revision-Date: 2002-05-13 02:00+0800\n"
+"Last-Translator: <audreyt at audreyt.org>\n"
+"Language-Team: German <audreyt at audreyt.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: Hello.pm:10
+msgid "Hello, World!"
+msgstr "Hallo, Welt!"
+
+#: Hello.pm:11
+msgid "You have %*(%1,piece) of mail."
+msgstr "Sie haben %*(%1,Poststueck,Poststuecken)."
+
+#: Hello.pm:12
+msgid "%1()"
+msgstr "%1()"
+
+#: Hello.pm:13
+msgid "%1 %2 %*"
+msgstr "%* %2 %1"
+
+#: Hello.pm:14
+msgid "%1%2%*"
+msgstr "%*%2%1"
+
+#: Hello.pm:15
+msgid ""
+"\n"
+"\n"
+"Knowledge\n"
+"And\n"
+"Nature\n"
+"\n"
+msgstr ""
+"\n"
+"\n"
+"Ich wuenschte recht gelehrt zu werden,\n"
+"Und moechte gern, was auf der Erden\n"
+"Und in dem Himmel ist, erfassen,\n"
+"Die Wissenschaft und die Natur.\n"
+"\n"
+
+#: Hello.pm:16
+#, big, furry, fuzzy
+msgid "The Hitchhiker's Guide to the Galaxy"
+msgstr "Der Fuehrer des Trampers zur Galaxie"
+
Added: Locale-Maketext-Lexicon/t/2-lmg.t
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/2-lmg.t Sun Apr 2 05:40:20 2006
@@ -0,0 +1,102 @@
+#! /usr/bin/perl -w
+# Basic test suite
+# Copyright (c) 2003 imacat. All rights reserved. This program is free
+# software; you can redistribute it and/or modify it under the same terms
+# as Perl itself.
+
+use strict;
+use Test;
+
+BEGIN {
+ plan(tests => 0), exit
+ unless eval { require Encode::compat; 1 }
+ or eval { require Encode; 1 }
+}
+BEGIN { plan tests => 14 }
+
+use FindBin;
+use File::Spec::Functions qw(catdir catfile);
+use lib $FindBin::Bin;
+use vars qw($LOCALEDIR);
+$LOCALEDIR = catdir($FindBin::Bin, "locale");
+
+# Basic checks
+use Encode qw(decode);
+
+# bindtextdomain
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("en");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_ = $_->bindtextdomain("test");
+};
+# 1
+ok($@, "");
+# 2
+ok($_, "$LOCALEDIR");
+
+# textdomain
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("en");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_ = $_->textdomain;
+};
+# 3
+ok($@, "");
+# 4
+ok($_, "test");
+
+# readmo
+eval {
+ $_ = catfile($LOCALEDIR, "zh_TW", "LC_MESSAGES", "test.mo");
+ ($_, %_) = T_L10N->readmo($_);
+};
+# 5
+ok($@, "");
+# 6
+ok($_, "Big5");
+# 7
+ok(scalar(keys %_), 2);
+# 8
+ok($_{"Hello, world!"}, decode("Big5", "¤j®a¦n¡C"));
+
+# English
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("en");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_ = $_->maketext("Hello, world!");
+};
+# 9
+ok($@, "");
+# 10
+ok($_, "Hiya :)");
+
+# Traditional Chinese
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_ = $_->maketext("Hello, world!");
+};
+# 11
+ok($@, "");
+# 12
+ok($_, "¤j®a¦n¡C");
+
+# Simplified Chinese
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-cn");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_ = $_->maketext("Hello, world!");
+};
+# 13
+ok($@, "");
+# 14
+ok($_, "´ó¼ÒºÃ¡£");
Added: Locale-Maketext-Lexicon/t/3-big-endian.t
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/3-big-endian.t Sun Apr 2 05:40:20 2006
@@ -0,0 +1,61 @@
+#! /usr/bin/perl -w
+# Test the big endian MO files
+# Copyright (c) 2003 imacat. All rights reserved. This program is free
+# software; you can redistribute it and/or modify it under the same terms
+# as Perl itself.
+
+use strict;
+use Test;
+
+BEGIN {
+ plan(tests => 0), exit
+ unless eval { require Encode::compat; 1 }
+ or eval { require Encode; 1 }
+}
+BEGIN { plan tests => 6 }
+
+use FindBin;
+use File::Spec::Functions qw(catdir);
+use lib $FindBin::Bin;
+use vars qw($LOCALEDIR);
+$LOCALEDIR = catdir($FindBin::Bin, "locale");
+
+# Check reading big-endian PO files
+# English
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("en");
+ $_->bindtextdomain("test_be", $LOCALEDIR);
+ $_->textdomain("test_be");
+ $_ = $_->maketext("Hello, world!");
+};
+# 1
+ok($@, "");
+# 2
+ok($_, "Hiya :)");
+
+# Traditional Chinese
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test_be", $LOCALEDIR);
+ $_->textdomain("test_be");
+ $_ = $_->maketext("Hello, world!");
+};
+# 3
+ok($@, "");
+# 4
+ok($_, "¤j®a¦n¡C");
+
+# Simplified Chinese
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-cn");
+ $_->bindtextdomain("test_be", $LOCALEDIR);
+ $_->textdomain("test_be");
+ $_ = $_->maketext("Hello, world!");
+};
+# 5
+ok($@, "");
+# 6
+ok($_, "´ó¼ÒºÃ¡£");
Added: Locale-Maketext-Lexicon/t/4-encodings.t
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/4-encodings.t Sun Apr 2 05:40:20 2006
@@ -0,0 +1,176 @@
+#! /usr/bin/perl -w
+# Test suite for the different encodings
+# Copyright (c) 2003 imacat. All rights reserved. This program is free
+# software; you can redistribute it and/or modify it under the same terms
+# as Perl itself.
+
+use strict;
+use Test;
+
+BEGIN {
+ %ENV = ();
+ plan(tests => 0), exit
+ unless eval { require Encode::compat; 1 }
+ or eval { require Encode; 1 }
+}
+BEGIN { plan tests => 22 }
+
+use FindBin;
+use File::Spec::Functions qw(catdir);
+use lib $FindBin::Bin;
+use vars qw($LOCALEDIR);
+$LOCALEDIR = catdir($FindBin::Bin, "locale");
+
+# Different encodings
+# English
+# Find the default encoding
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("en");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_ = $_->encoding;
+};
+# 1
+ok($@, "");
+# 2
+ok($_, "US-ASCII");
+
+# Traditional Chinese
+# Find the default encoding
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_ = $_->encoding;
+};
+# 3
+ok($@, "");
+# 4
+ok($_, "Big5");
+
+# Turn to Big5
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_->encoding("Big5");
+ $_ = $_->maketext("Hello, world!");
+};
+# 5
+ok($@, "");
+# 6
+ok($_, "¤j®a¦n¡C");
+
+# Turn to UTF-8
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_->encoding("UTF-8");
+ $_ = $_->maketext("Hello, world!");
+};
+# 7
+ok($@, "");
+# 8
+ok($_, "大家好。");
+
+# Turn to UTF-16LE
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test", $LOCALEDIR);
+ $_->textdomain("test");
+ $_->encoding("UTF-16LE");
+ $_ = $_->maketext("Hello, world!");
+};
+# 9
+ok($@, "");
+# 10
+ok($_, "'Y¶[}Y0");
+
+# Find the default encoding, in UTF-8
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test_utf8", $LOCALEDIR);
+ $_->textdomain("test_utf8");
+ $_ = $_->encoding;
+};
+# 11
+ok($@, "");
+# 12
+ok($_, "UTF-8");
+
+# Turn to UTF-8
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test_utf8", $LOCALEDIR);
+ $_->textdomain("test_utf8");
+ $_->encoding("UTF-8");
+ $_ = $_->maketext("Hello, world!");
+};
+# 13
+ok($@, "");
+# 14
+ok($_, "大家好。");
+
+# Turn to Big5
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test_utf8", $LOCALEDIR);
+ $_->textdomain("test_utf8");
+ $_->encoding("Big5");
+ $_ = $_->maketext("Hello, world!");
+};
+# 15
+ok($@, "");
+# 16
+ok($_, "¤j®a¦n¡C");
+
+# Turn to UTF-16LE
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-tw");
+ $_->bindtextdomain("test_utf8", $LOCALEDIR);
+ $_->textdomain("test_utf8");
+ $_->encoding("UTF-16LE");
+ $_ = $_->maketext("Hello, world!");
+};
+# 17
+ok($@, "");
+# 18
+ok($_, "'Y¶[}Y0");
+
+# Find the default encoding
+# Simplified Chinese
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-cn");
+ $_->bindtextdomain("test_utf8", $LOCALEDIR);
+ $_->textdomain("test_utf8");
+ $_ = $_->encoding;
+};
+# 19
+ok($@, "");
+# 20
+ok($_, "UTF-8");
+
+# Turn to GB2312
+eval {
+ require T_L10N;
+ $_ = T_L10N->get_handle("zh-cn");
+ $_->bindtextdomain("test_utf8", $LOCALEDIR);
+ $_->textdomain("test_utf8");
+ $_->encoding("GB2312");
+ $_ = $_->maketext("Hello, world!");
+};
+# 21
+ok($@, "");
+# 22
+ok($_, "´ó¼ÒºÃ¡£");
Added: Locale-Maketext-Lexicon/t/5-extract.t
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/5-extract.t Sun Apr 2 05:40:20 2006
@@ -0,0 +1,49 @@
+#! /usr/bin/perl -w
+use lib '../lib';
+use strict;
+use Test::More tests => 22;
+
+use_ok('Locale::Maketext::Extract');
+my $Ext = Locale::Maketext::Extract->new;
+isa_ok($Ext => 'Locale::Maketext::Extract');
+
+extract_ok('_("123")' => 123, 'Simple extraction');
+
+extract_ok('_("[_1] is happy")' => '%1 is happy', '[_1] to %1');
+extract_ok('_("[_1] is happy")' => '[_1] is happy', '[_1] verbatim', 1);
+
+extract_ok('_("[*,_1] counts")' => '%*(%1) counts', '[*,_1] to %*(%1)');
+extract_ok('_("[*,_1] counts")' => '[*,_1] counts', '[*,_1] verbatim', 1);
+
+extract_ok('_("[*,_1,_2] counts")' => '%*(%1,%2) counts',
+ '[*,_1,_2] to %*(%1,%2)');
+extract_ok('_("[*,_1,_2] counts")' => '[*,_1,_2] counts',
+ '[*,_1,_2] verbatim', 1);
+
+extract_ok(q(_('foo\$bar')) => 'foo\\\\$bar', 'Escaped \$ in q');
+extract_ok(q(_("foo\$bar")) => 'foo$bar', 'Normalized \$ in qq');
+
+extract_ok(q(_('foo\x20bar')) => 'foo\\\\x20bar', 'Escaped \x in q');
+extract_ok(q(_("foo\x20bar")) => 'foo bar', 'Normalized \x in qq');
+
+extract_ok(q(_('foo\nbar')) => 'foo\\\\nbar', 'Escaped \n in qq');
+extract_ok(q(_("foo\nbar")) => "foo\nbar", 'Normalized \n in qq');
+extract_ok(qq(_("foo\nbar")) => "foo\nbar", 'Normalized literal \n in qq');
+
+extract_ok(q(_("foo\nbar")) => "foo\nbar", 'Trailing \n in qq');
+extract_ok(qq(_("foobar\n")) => "foobar\n", 'Trailing literal \n in qq');
+
+extract_ok(q(_('foo\bar')) => 'foo\\\\bar', 'Escaped \ in q');
+extract_ok(q(_('foo\\\\bar')) => 'foo\\\\bar', 'Normalized \\\\ in q');
+extract_ok(q(_("foo\bar")) => 'foo\bar', 'Interpolated \t in qq');
+
+extract_ok(q([% loc( 'foo "bar" baz' ) %]) => 'foo \\"bar\\" baz', 'Escaped double quote in text');
+
+sub extract_ok {
+ my ($text, $result, $info, $verbatim) = @_;
+ $Ext->extract('' => $text);
+ $Ext->compile($verbatim);
+ is(join('', %{$Ext->lexicon}), $result, $info);
+ $Ext->clear;
+}
+
Added: Locale-Maketext-Lexicon/t/T_L10N.pm
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/T_L10N.pm Sun Apr 2 05:40:20 2006
@@ -0,0 +1,83 @@
+# Test localization class and its subclasses
+# Copyright (c) 2003 imacat. All rights reserved. This program is free
+# software; you can redistribute it and/or modify it under the same terms
+# as Perl itself.
+
+package T_L10N;
+
+use strict;
+use FindBin;
+use File::Spec;
+use base qw(Locale::Maketext);
+
+my (%Domains, $Domain);
+sub bindtextdomain {
+ my ($self, $domain, $dir) = @_;
+ return $Domains{$domain} unless $dir;
+ $Domains{$domain} = $dir;
+
+ require Locale::Maketext::Lexicon;
+ Locale::Maketext::Lexicon->import({
+ '*' => [
+ Gettext => File::Spec->catdir( $dir, qw(* LC_MESSAGES), "$domain.mo" )
+ ],
+ _decode => 1,
+ });
+}
+
+sub textdomain {
+ my ($self, $domain) = @_;
+ $Domain = $domain if $domain;
+ return $Domain;
+}
+
+sub readmo {
+ my ($self, $file) = @_;
+ local ($/, *FH);
+ open FH, $file;
+ binmode(FH);
+
+ require Locale::Maketext::Lexicon::Gettext;
+ my $hashref = Locale::Maketext::Lexicon::Gettext::parse_mo(<FH>);
+ delete @{$hashref}{grep /^__/, keys %$hashref};
+ return Locale::Maketext::Lexicon::Gettext->input_encoding, %$hashref;
+}
+
+sub encoding {
+ my ($self, $encoding) = @_;
+
+ if ($encoding) {
+ $self->{CUR_ENC} = $encoding;
+ }
+ elsif ( !$self->{CUR_ENC} ) {
+ $self->{CUR_ENC} = $1
+ if $self->SUPER::maketext('__Content-Type') =~ /\bcharset=\s*([-\w]+)/i;
+ }
+
+ $self->{CUR_ENC};
+}
+
+sub maketext {
+ my $self = shift;
+
+ require Encode::compat if ($] == 5.006001);
+ require Encode;
+ Encode::encode($self->encoding, $self->SUPER::maketext(@_));
+}
+
+1;
+
+package T_L10N::en;
+use base qw(T_L10N);
+
+1;
+
+package T_L10N::zh_tw;
+use base qw(T_L10N);
+
+1;
+
+package T_L10N::zh_cn;
+use base qw(T_L10N);
+
+1;
Added: Locale-Maketext-Lexicon/t/gencat.m
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/gencat.m Sun Apr 2 05:40:20 2006
@@ -0,0 +1,10 @@
+$ by Audrey Tang <audreyt at audreyt.org>
+$set 1 # $Id: //member/autrijus/Locale-Maketext-Lexicon/t/gencat.m#1 $
+1 First string
+2 Second \
+str\
+ing
+$quote X
+3 XThird stringX
+4 XFourth X\
+XstringX
Added: Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/test.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/test_be.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/en/LC_MESSAGES/test_utf8.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/test.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/test_be.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/zh_CN/LC_MESSAGES/test_utf8.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/test.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/test_be.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/locale/zh_TW/LC_MESSAGES/test_utf8.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/messages.mo
==============================================================================
Binary file. No diff available.
Added: Locale-Maketext-Lexicon/t/messages.po
==============================================================================
--- (empty file)
+++ Locale-Maketext-Lexicon/t/messages.po Sun Apr 2 05:40:20 2006
@@ -0,0 +1,10 @@
+msgid ""
+msgstr ""
+"Last-Translator: Audrey Tang <audreyt at audreyt.org>\n"
+"Language-Team: Chinese <contact at ourinet.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "This is a test"
+msgstr "這是測試"
More information about the Rt-commit
mailing list