[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>
+&lt;html&gt;&lt;head&gt;&lt;title&gt;<B>唐宗漢 - 家</B>&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;<B>施工中, 請見諒</B>&lt;/body&gt;&lt;/html&gt;
+</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>
+&lt;html&gt;&lt;head&gt;&lt;title&gt;<B>Audrey.Home</B>&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;<B>Sorry, this page is under construction.</B>&lt;/body&gt;&lt;/html&gt;
+</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>&lt;body bgcolor=gold&gt;</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, "&gt;index.html.$language" or die $!;
+    print OUT start_html({ title =&gt; _(<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. &lt;html&gt;) 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 =&gt; 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-&gt;new;
+$cat-&gt;catopen("nls/$language.cat", 1); <i># it's like a 2D array</i>
+sub _ { $cat-&gt;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-&gt;get_handle('de');
+print $lh-&gt;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>-&gt;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>-&gt;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 =&gt; ['Auto'],              fr    =&gt; ['Tie' =&gt; 'DB_File', 'fr.db'],
+    de =&gt; ['Gettext' =&gt; \*DATA], zh_tw =&gt; ['Gettext' =&gt; '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__-&gt;get_handle; <i># magically gets the current locale</i>
+sub _ { $lh-&gt;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>-&gt;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-&gt;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-&gt;CurrentUser-&gt;loc(@_) }
+sub RT::CurrentUser::loc
+    { $self-&gt;LanguageHandle-&gt;maketext(@_) }
+sub RT::CurrentUser::LanguageHandle
+    { $self-&gt;{'LangHandle'} ||= RT::I18N-&gt;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-&gt;print(<b>loc(</b>"<b>Another line of text</b>", $args...<b>)</b>);
+&lt;&amp;<b>|/l</b>, $args...&amp;&gt;<b>Single line of text</b>&lt;/&amp;&gt;
+</PRE></td></tr></table>
+<p>
+The first style, used in embedded perl chunks and <tt>&lt;%PERL&gt;</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>-&gt;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'}-&gt;LanguageHandle;
+% $m-&gt;print($hand-&gt;maketext($m-&gt;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>
+&lt;B&gt;from the [% story.dept %] dept.&lt;/B&gt;
+</PRE></td></tr>
+<tr><td bgcolor=black><font color=white>Output</font></td><td><PRE>
+&lt;B&gt;[%<b>|loc(</b> story.dept <b>)</b>%]from the [<b>_1</b>] dept.[%END%]&lt;/B&gt;
+</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&ccedil;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>
+&lt;html&gt;&lt;head&gt;&lt;title&gt;<B>唐宗漢 - 家</B>&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;<B>施工中, 請見諒</B>&lt;/body&gt;&lt;/html&gt;
+</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>
+&lt;html&gt;&lt;head&gt;&lt;title&gt;<B>Audrey.Home</B>&lt;/title&gt;&lt;/head&gt;
+&lt;body&gt;<B>Sorry, this page is under construction.</B>&lt;/body&gt;&lt;/html&gt;
+</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>&lt;body bgcolor="gold"&gt;</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, "&gt;index.html.$language" or die $!;
+    print OUT start_html({ title =&gt; _(<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>&lt;html&gt;</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: 中文 =&gt; 海地語詞典</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-&gt;new;
+$cat-&gt;catopen("nls/$language.cat", 1); <i># 想成是二維陣列</i>
+sub _ { $cat-&gt;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-&gt;get_handle('de');
+print $lh-&gt;maketext("[<b>quant</b>,_1,camel was,camels were] released.", 5);
+</PRE></td></tr></table>
+
+<p>
+利用Maketext的「方括號語法」,譯者可以在字串中取用各種文法函式。上面的例子示範了內建的複數形式與量詞支援;對於需要特別處理複數形式的語言,祗需實作相應的<tt>quant()</tt>函式即可。同樣的,也很容易加入轉換序數與時間格式的功能。
+
+<p>
+每個語言類別,還可以定義自己的<tt>-&gt;encoding</tt>方法,描述其中的詞典編碼;這可以用來傳給「<tt>Encode</tt>」模組,進行即時轉碼。語言類別間也可以相互繼承:<tt>fr_ca.pm</tt>(加拿大法語)裡缺少的字串,預設會由<tt>fr.pm</tt>(一般法語)補上。
+
+<p>
+內建的<tt>-&gt;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 =&gt; ['Auto'],              fr    =&gt; ['Tie' =&gt; 'DB_File', 'fr.db'],
+    de =&gt; ['Gettext' =&gt; \*DATA], zh_tw =&gt; ['Gettext' =&gt; '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__-&gt;get_handle; <i># 自動取得目前的地區設定</i>
+sub _ { $lh-&gt;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>-&gt;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-&gt;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-&gt;CurrentUser-&gt;loc(@_) }
+sub RT::CurrentUser::loc
+    { $self-&gt;LanguageHandle-&gt;maketext(@_) }
+sub RT::CurrentUser::LanguageHandle
+    { $self-&gt;{'LangHandle'} ||= RT::I18N-&gt;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-&gt;print(<b>loc(</b>"<b>Another line of text</b>", $args...<b>)</b>);
+&lt;&amp;<b>|/l</b>, $args...&amp;&gt;<b>Single line of text</b>&lt;/&amp;&gt;
+</PRE></td></tr></table>
+<p>
+第一列的方式,用在內嵌的Perl源碼與<tt>&lt;%PERL&gt;</tt>段落之中;它會自動叫用現行使用者的<tt>-&gt;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'}-&gt;LanguageHandle;
+% $m-&gt;print($hand-&gt;maketext($m-&gt;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>
+&lt;B&gt;from the [% story.dept %] dept.&lt;/B&gt;
+</PRE></td></tr>
+<tr><td bgcolor=black><font color=white>輸出</font></td><td><PRE>
+&lt;B&gt;[%<b>|loc(</b> story.dept <b>)</b>%]from the [<b>_1</b>] dept.[%END%]&lt;/B&gt;
+</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