Initialized from version 1.28 (2009/6/27).
This commit is contained in:
814
lib/Locale/Maketext/Gettext.pm
Normal file
814
lib/Locale/Maketext/Gettext.pm
Normal file
@ -0,0 +1,814 @@
|
||||
# Locale::Maketext::Gettext - Joins the gettext and Maketext frameworks
|
||||
|
||||
# Copyright (c) 2003-2009 imacat. All rights reserved. This program is free
|
||||
# software; you can redistribute it and/or modify it under the same terms
|
||||
# as Perl itself.
|
||||
# First written: 2003-04-23
|
||||
|
||||
package Locale::Maketext::Gettext;
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
use base qw(Locale::Maketext Exporter);
|
||||
use vars qw($VERSION @ISA %Lexicon @EXPORT @EXPORT_OK);
|
||||
$VERSION = 1.28;
|
||||
@EXPORT = qw(read_mo);
|
||||
@EXPORT_OK = @EXPORT;
|
||||
# Prototype declaration
|
||||
sub read_mo($);
|
||||
|
||||
use Encode qw(encode decode FB_DEFAULT);
|
||||
use File::Spec::Functions qw(catfile);
|
||||
no strict qw(refs);
|
||||
|
||||
use vars qw(%Lexicons %ENCODINGS $REREAD_MO $MOFILE);
|
||||
$REREAD_MO = 0;
|
||||
$MOFILE = "";
|
||||
use vars qw(@SYSTEM_LOCALEDIRS);
|
||||
@SYSTEM_LOCALEDIRS = qw(/usr/share/locale /usr/lib/locale
|
||||
/usr/local/share/locale /usr/local/lib/locale);
|
||||
|
||||
# encoding: Set or retrieve the output encoding
|
||||
sub encoding : method {
|
||||
local ($_, %_);
|
||||
my $self;
|
||||
($self, $_) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Set the output encoding
|
||||
if (@_ > 1) {
|
||||
if (defined $_) {
|
||||
$self->{"ENCODING"} = $_;
|
||||
} else {
|
||||
delete $self->{"ENCODING"};
|
||||
}
|
||||
$self->{"USERSET_ENCODING"} = $_;
|
||||
}
|
||||
|
||||
# Return the encoding
|
||||
return exists $self->{"ENCODING"}? $self->{"ENCODING"}: undef;
|
||||
}
|
||||
|
||||
# key_encoding: Specify the encoding used in the keys
|
||||
sub key_encoding : method {
|
||||
local ($_, %_);
|
||||
my $self;
|
||||
($self, $_) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Set the encoding used in the keys
|
||||
if (@_ > 1) {
|
||||
if (defined $_) {
|
||||
$self->{"KEY_ENCODING"} = $_;
|
||||
} else {
|
||||
delete $self->{"KEY_ENCODING"};
|
||||
}
|
||||
}
|
||||
|
||||
# Return the encoding
|
||||
return exists $self->{"KEY_ENCODING"}? $self->{"KEY_ENCODING"}: undef;
|
||||
}
|
||||
|
||||
# new: Initialize the language handler
|
||||
sub new : method {
|
||||
local ($_, %_);
|
||||
my ($self, $class);
|
||||
$class = ref($_[0]) || $_[0];
|
||||
$self = bless {}, $class;
|
||||
$self->subclass_init;
|
||||
$self->init;
|
||||
return $self;
|
||||
}
|
||||
|
||||
# subclass_init: Initialize at the subclass level, so that it can be
|
||||
# inherited by calling $self->SUPER:subclass_init
|
||||
sub subclass_init : method {
|
||||
local ($_, %_);
|
||||
my ($self, $class);
|
||||
$self = $_[0];
|
||||
$class = ref($self);
|
||||
# Initialize the instance lexicon
|
||||
$self->{"Lexicon"} = {};
|
||||
# Initialize the LOCALEDIRS registry
|
||||
$self->{"LOCALEDIRS"} = {};
|
||||
# Initialize the MO timestamp
|
||||
$self->{"REREAD_MO"} = $REREAD_MO;
|
||||
# Initialize the DIE_FOR_LOOKUP_FAILURES setting
|
||||
$self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
|
||||
$self->SUPER::fail_with($self->can("failure_handler_auto"));
|
||||
# Initialize the ENCODE_FAILURE setting
|
||||
$self->{"ENCODE_FAILURE"} = FB_DEFAULT;
|
||||
# Initialize the MOFILE value of this instance
|
||||
$self->{"MOFILE"} = "";
|
||||
${"$class\::MOFILE"} = "" if !defined ${"$class\::MOFILE"};
|
||||
# Find the locale name, for this subclass
|
||||
$self->{"LOCALE"} = $class;
|
||||
$self->{"LOCALE"} =~ s/^.*:://;
|
||||
$self->{"LOCALE"} =~ s/(_)(.*)$/$1 . uc $2/e;
|
||||
# Map i_default to C
|
||||
$self->{"LOCALE"} = "C" if $self->{"LOCALE"} eq "i_default";
|
||||
# Set the category. Currently this is always LC_MESSAGES
|
||||
$self->{"CATEGORY"} = "LC_MESSAGES";
|
||||
# Default key encoding is US-ASCII
|
||||
$self->{"KEY_ENCODING"} = "US-ASCII";
|
||||
return;
|
||||
}
|
||||
|
||||
# bindtextdomain: Bind a text domain to a locale directory
|
||||
sub bindtextdomain : method {
|
||||
local ($_, %_);
|
||||
my ($self, $DOMAIN, $LOCALEDIR);
|
||||
($self, $DOMAIN, $LOCALEDIR) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Return null for this rare case
|
||||
return if !defined $LOCALEDIR
|
||||
&& !exists ${$self->{"LOCALEDIRS"}}{$DOMAIN};
|
||||
|
||||
# Register the DOMAIN and its LOCALEDIR
|
||||
${$self->{"LOCALEDIRS"}}{$DOMAIN} = $LOCALEDIR if defined $LOCALEDIR;
|
||||
|
||||
# Return the registry
|
||||
return ${$self->{"LOCALEDIRS"}}{$DOMAIN};
|
||||
}
|
||||
|
||||
# textdomain: Set the current text domain
|
||||
sub textdomain : method {
|
||||
local ($_, %_);
|
||||
my ($self, $class, $DOMAIN, $LOCALEDIR, $MOfile);
|
||||
($self, $DOMAIN) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
# Find the class name
|
||||
$class = ref($self);
|
||||
|
||||
# Return the current domain
|
||||
return $self->{"DOMAIN"} if !defined $DOMAIN;
|
||||
|
||||
# Set the timestamp of this read in this instance
|
||||
$self->{"REREAD_MO"} = $REREAD_MO;
|
||||
# Set the current domain
|
||||
$self->{"DOMAIN"} = $DOMAIN;
|
||||
|
||||
# Clear it
|
||||
$self->{"Lexicon"} = {};
|
||||
%{"$class\::Lexicon"} = qw();
|
||||
$self->{"MOFILE"} = "";
|
||||
${"$class\::MOFILE"} = "";
|
||||
|
||||
# The format is "{LOCALEDIR}/{LOCALE}/{CATEGORY}/{DOMAIN}.mo"
|
||||
# Search the system locale directories if the domain was not
|
||||
# registered yet
|
||||
if (!exists ${$self->{"LOCALEDIRS"}}{$DOMAIN}) {
|
||||
undef $MOfile;
|
||||
foreach $LOCALEDIR (@SYSTEM_LOCALEDIRS) {
|
||||
$_ = catfile($LOCALEDIR, $self->{"LOCALE"},
|
||||
$self->{"CATEGORY"}, "$DOMAIN.mo");
|
||||
if (-f $_ && -r $_) {
|
||||
$MOfile = $_;
|
||||
last;
|
||||
}
|
||||
}
|
||||
# Not found at last
|
||||
return $DOMAIN if !defined $MOfile;
|
||||
|
||||
# This domain was registered
|
||||
} else {
|
||||
$MOfile = catfile(${$self->{"LOCALEDIRS"}}{$DOMAIN},
|
||||
$self->{"LOCALE"}, $self->{"CATEGORY"}, "$DOMAIN.mo");
|
||||
}
|
||||
|
||||
# Record it
|
||||
${"$class\::MOFILE"} = $MOfile;
|
||||
$self->{"MOFILE"} = $MOfile;
|
||||
|
||||
# Read the MO file
|
||||
# Cached
|
||||
if (!exists $ENCODINGS{$MOfile} || !exists $Lexicons{$MOfile}) {
|
||||
my $enc;
|
||||
# Read it
|
||||
%_ = read_mo($MOfile);
|
||||
|
||||
# Successfully read
|
||||
if (scalar(keys %_) > 0) {
|
||||
# Decode it
|
||||
# Find the encoding of that MO file
|
||||
if ($_{""} =~ /^Content-Type: text\/plain; charset=(.*)$/im) {
|
||||
$enc = $1;
|
||||
# Default to US-ASCII
|
||||
} else {
|
||||
$enc = "US-ASCII";
|
||||
}
|
||||
# Set the current encoding to the encoding of the MO file
|
||||
$_{$_} = decode($enc, $_{$_}) foreach keys %_;
|
||||
}
|
||||
|
||||
# Cache them
|
||||
$Lexicons{$MOfile} = \%_;
|
||||
$ENCODINGS{$MOfile} = $enc;
|
||||
}
|
||||
|
||||
# Respect the existing output encoding
|
||||
if (defined $ENCODINGS{$MOfile}) {
|
||||
$self->{"MO_ENCODING"} = $ENCODINGS{$MOfile};
|
||||
} else {
|
||||
delete $self->{"MO_ENCODING"};
|
||||
}
|
||||
# Respect the MO file encoding unless there is a user preferrence
|
||||
if (!exists $self->{"USERSET_ENCODING"}) {
|
||||
if (exists $self->{"MO_ENCODING"}) {
|
||||
$self->{"ENCODING"} = $self->{"MO_ENCODING"};
|
||||
} else {
|
||||
delete $self->{"ENCODING"};
|
||||
}
|
||||
}
|
||||
$self->{"Lexicon"} = $Lexicons{$MOfile};
|
||||
%{"$class\::Lexicon"} = %{$Lexicons{$MOfile}};
|
||||
$self->clear_isa_scan;
|
||||
|
||||
return $DOMAIN;
|
||||
}
|
||||
|
||||
# maketext: Encode after maketext
|
||||
sub maketext : method {
|
||||
local ($_, %_);
|
||||
my ($self, $key, @param, $class, $keyd);
|
||||
($self, $key, @param) = @_;
|
||||
|
||||
# This is not a static method - NOW
|
||||
return if ref($self) eq "";
|
||||
# Find the class name
|
||||
$class = ref($self);
|
||||
|
||||
# MO file should be re-read
|
||||
if ($self->{"REREAD_MO"} < $REREAD_MO) {
|
||||
$self->{"REREAD_MO"} = $REREAD_MO;
|
||||
defined($_ = $self->textdomain) and $self->textdomain($_);
|
||||
}
|
||||
|
||||
# If the instance lexicon is changed.
|
||||
# Maketext uses a class lexicon. We have to copy the instance
|
||||
# lexicon into the class lexicon. This is slow. Mass memory
|
||||
# copy sucks. Avoid create several language handles for a
|
||||
# single localization subclass whenever possible.
|
||||
# Maketext uses class lexicon in order to track the inheritance.
|
||||
# It is hard to change it.
|
||||
if (${"$class\::MOFILE"} ne $self->{"MOFILE"}) {
|
||||
${"$class\::MOFILE"} = $self->{"MOFILE"};
|
||||
%{"$class\::Lexicon"} = %{$self->{"Lexicon"}};
|
||||
}
|
||||
|
||||
# Decode the source text
|
||||
$keyd = $key;
|
||||
$keyd = decode($self->{"KEY_ENCODING"}, $keyd, $self->{"ENCODE_FAILURE"})
|
||||
if exists $self->{"KEY_ENCODING"} && !Encode::is_utf8($key);
|
||||
# Maketext
|
||||
$_ = $self->SUPER::maketext($keyd, @param);
|
||||
# Output to the requested encoding
|
||||
if (exists $self->{"ENCODING"}) {
|
||||
$_ = encode($self->{"ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
|
||||
# Pass through the empty/invalid lexicon
|
||||
} elsif ( scalar(keys %{$self->{"Lexicon"}}) == 0
|
||||
&& exists $self->{"KEY_ENCODING"}
|
||||
&& !Encode::is_utf8($key)) {
|
||||
$_ = encode($self->{"KEY_ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
|
||||
}
|
||||
|
||||
return $_;
|
||||
}
|
||||
|
||||
# pmaketext: Maketext with context
|
||||
sub pmaketext : method {
|
||||
local ($_, %_);
|
||||
my ($self, $ctxt, $key, @param);
|
||||
($self, $ctxt, $key, @param) = @_;
|
||||
# This is not a static method - NOW
|
||||
return if ref($self) eq "";
|
||||
# This is actually a wrapper to the maketext() method
|
||||
return $self->maketext("$ctxt\x04$key", @param);
|
||||
}
|
||||
|
||||
# read_mo: Subroutine to read and parse the MO file
|
||||
# Refer to gettext documentation section 8.3
|
||||
sub read_mo($) {
|
||||
local ($_, %_);
|
||||
my ($MOfile, $len, $FH, $content, $tmpl);
|
||||
$MOfile = $_[0];
|
||||
|
||||
# Avild being stupid
|
||||
return unless -f $MOfile && -r $MOfile;
|
||||
# Read the MO file
|
||||
$len = (stat $MOfile)[7];
|
||||
open $FH, $MOfile or return; # GNU gettext never fails!
|
||||
binmode $FH;
|
||||
defined($_ = read $FH, $content, $len)
|
||||
or return;
|
||||
close $FH or return;
|
||||
|
||||
# Find the byte order of the MO file creator
|
||||
$_ = substr($content, 0, 4);
|
||||
# Little endian
|
||||
if ($_ eq "\xde\x12\x04\x95") {
|
||||
$tmpl = "V";
|
||||
# Big endian
|
||||
} elsif ($_ eq "\x95\x04\x12\xde") {
|
||||
$tmpl = "N";
|
||||
# Wrong magic number. Not a valid MO file.
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
# Check the MO format revision number
|
||||
$_ = unpack $tmpl, substr($content, 4, 4);
|
||||
# There is only one revision now: revision 0.
|
||||
return if $_ > 0;
|
||||
|
||||
my ($num, $offo, $offt);
|
||||
# Number of messages
|
||||
$num = unpack $tmpl, substr($content, 8, 4);
|
||||
# Offset to the beginning of the original messages
|
||||
$offo = unpack $tmpl, substr($content, 12, 4);
|
||||
# Offset to the beginning of the translated messages
|
||||
$offt = unpack $tmpl, substr($content, 16, 4);
|
||||
%_ = qw();
|
||||
for ($_ = 0; $_ < $num; $_++) {
|
||||
my ($len, $off, $stro, $strt);
|
||||
# The first word is the length of the message
|
||||
$len = unpack $tmpl, substr($content, $offo+$_*8, 4);
|
||||
# The second word is the offset of the message
|
||||
$off = unpack $tmpl, substr($content, $offo+$_*8+4, 4);
|
||||
# Original message
|
||||
$stro = substr($content, $off, $len);
|
||||
|
||||
# The first word is the length of the message
|
||||
$len = unpack $tmpl, substr($content, $offt+$_*8, 4);
|
||||
# The second word is the offset of the message
|
||||
$off = unpack $tmpl, substr($content, $offt+$_*8+4, 4);
|
||||
# Translated message
|
||||
$strt = substr($content, $off, $len);
|
||||
|
||||
# Hash it
|
||||
$_{$stro} = $strt;
|
||||
}
|
||||
|
||||
return %_;
|
||||
}
|
||||
|
||||
# reload_text: Method to purge the lexicon cache
|
||||
sub reload_text : method {
|
||||
local ($_, %_);
|
||||
|
||||
# Purge the text cache
|
||||
%Lexicons = qw();
|
||||
%ENCODINGS = qw();
|
||||
$REREAD_MO = time;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# fail_with: A wrapper to the fail_with() of Locale::Maketext, in order
|
||||
# to record the preferred failure handler of the user, so that
|
||||
# die_for_lookup_failures() knows where to return to.
|
||||
sub fail_with : method {
|
||||
local ($_, %_);
|
||||
my $self;
|
||||
($self, $_) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Set the current setting
|
||||
if (@_ > 1) {
|
||||
if (defined $_) {
|
||||
$self->{"USERSET_FAIL"} = $_;
|
||||
$self->SUPER::fail_with($_) if $self->{"DIE_FOR_LOOKUP_FAILURES"};
|
||||
} else {
|
||||
delete $self->{"USERSET_FAIL"};
|
||||
delete $self->{"fail"} if $self->{"DIE_FOR_LOOKUP_FAILURES"};
|
||||
}
|
||||
}
|
||||
|
||||
# Return the current setting
|
||||
return exists $self->{"USERSET_FAIL"}? $self->{"USERSET_FAIL"}: undef;
|
||||
}
|
||||
|
||||
# die_for_lookup_failures: Whether we should die for lookup failure
|
||||
# The default is no. GNU gettext never fails.
|
||||
sub die_for_lookup_failures : method {
|
||||
local ($_, %_);
|
||||
my $self;
|
||||
($self, $_) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Set the current setting
|
||||
if (@_ > 1) {
|
||||
if ($_) {
|
||||
$self->{"DIE_FOR_LOOKUP_FAILURES"} = 1;
|
||||
if (exists $self->{"USERSET_FAIL"}) {
|
||||
$self->{"fail"} = $self->{"USERSET_FAIL"};
|
||||
} else {
|
||||
delete $self->{"fail"};
|
||||
}
|
||||
} else {
|
||||
$self->SUPER::fail_with($self->can("failure_handler_auto"));
|
||||
$self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
|
||||
}
|
||||
}
|
||||
|
||||
# Return the current setting
|
||||
return exists $self->{"DIE_FOR_LOOKUP_FAILURES"}?
|
||||
$self->{"DIE_FOR_LOOKUP_FAILURES"}: undef;
|
||||
}
|
||||
|
||||
# encode_failure: What to do if the text is out of your output encoding
|
||||
# Refer to Encode on possible values of this check
|
||||
sub encode_failure : method {
|
||||
local ($_, %_);
|
||||
my $self;
|
||||
($self, $_) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Specify the action used in the keys
|
||||
$self->{"ENCODE_FAILURE"} = $_ if @_ > 1;
|
||||
|
||||
# Return the encoding
|
||||
return $self->{"ENCODE_FAILURE"} if exists $self->{"ENCODE_FAILURE"};
|
||||
return undef;
|
||||
}
|
||||
|
||||
# failure_handler_auto: Our local version of failure_handler_auto(),
|
||||
# Copied and rewritten from Locale::Maketext, with bug#33938 patch applied.
|
||||
# See http://rt.perl.org/rt3//Public/Bug/Display.html?id=33938
|
||||
sub failure_handler_auto : method {
|
||||
local ($_, %_);
|
||||
my ($self, $key, @param, $r);
|
||||
($self, $key, @param) = @_;
|
||||
|
||||
# This is not a static method
|
||||
return if ref($self) eq "";
|
||||
|
||||
# Remove the context
|
||||
# We assume there is no one using EOF either in the context or message.
|
||||
# That does not work in GNU gettext, anyway.
|
||||
$key =~ s/^[^\x04]*\x04//;
|
||||
|
||||
$self->{"failure_lex"} = {} if !exists $self->{"failure_lex"};
|
||||
${$self->{"failure_lex"}}{$key} = $self->_compile($key)
|
||||
if !exists ${$self->{"failure_lex"}}{$key};
|
||||
$_ = ${$self->{"failure_lex"}}{$key};
|
||||
|
||||
# A scalar result
|
||||
return $$_ if ref($_) eq "SCALAR";
|
||||
return $_ unless ref($_) eq "CODE";
|
||||
# A compiled subroutine
|
||||
{
|
||||
local $SIG{"__DIE__"};
|
||||
$r = eval {
|
||||
$_ = &$_($self, @param);
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
# If we make it here, there was an exception thrown in the
|
||||
# call to $value, and so scream:
|
||||
if (!defined $r) {
|
||||
$_ = $@;
|
||||
# pretty up the error message
|
||||
s<\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?>
|
||||
<\n in bracket code [compiled line $1],>s;
|
||||
Carp::croak "Error in maketexting \"$key\":\n$_ as used";
|
||||
return;
|
||||
}
|
||||
|
||||
# OK
|
||||
return $_;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Locale::Maketext::Gettext - Joins the gettext and Maketext frameworks
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
In your localization class:
|
||||
|
||||
package MyPackage::L10N;
|
||||
use base qw(Locale::Maketext::Gettext);
|
||||
return 1;
|
||||
|
||||
In your application:
|
||||
|
||||
use MyPackage::L10N;
|
||||
$LH = MyPackage::L10N->get_handle or die "What language?";
|
||||
$LH->bindtextdomain("mypackage", "/home/user/locale");
|
||||
$LH->textdomain("mypackage");
|
||||
$LH->maketext("Hello, world!!");
|
||||
|
||||
If you want to have more control to the detail:
|
||||
|
||||
# Change the output encoding
|
||||
$LH->encoding("UTF-8");
|
||||
# Stick with the Maketext behavior on lookup failures
|
||||
$LH->die_for_lookup_failures(1);
|
||||
# Flush the MO file cache and re-read your updated MO files
|
||||
$LH->reload_text;
|
||||
# Set the encoding of your maketext keys, if not in English
|
||||
$LH->key_encoding("Big5");
|
||||
# Set the action when encode fails
|
||||
$LH->encode_failure(Encode::FB_HTMLCREF);
|
||||
|
||||
Use Locale::Maketext::Gettext to read and parse the MO file:
|
||||
|
||||
use Locale::Maketext::Gettext;
|
||||
%Lexicon = read_mo($MOfile);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Locale::Maketext::Gettext joins the GNU gettext and Maketext
|
||||
frameworks. It is a subclass of L<Locale::Maketext(3)|Locale::Maketext/3>
|
||||
that follows the way GNU gettext works. It works seamlessly, I<both
|
||||
in the sense of GNU gettext and Maketext>. As a result, you I<enjoy
|
||||
both their advantages, and get rid of both their problems, too.>
|
||||
|
||||
You start as an usual GNU gettext localization project: Work on
|
||||
PO files with the help of translators, reviewers and Emacs. Turn
|
||||
them into MO files with F<msgfmt>. Copy them into the appropriate
|
||||
locale directory, such as
|
||||
F</usr/share/locale/de/LC_MESSAGES/myapp.mo>.
|
||||
|
||||
Then, build your Maketext localization class, with your base class
|
||||
changed from L<Locale::Maketext(3)|Locale::Maketext/3> to
|
||||
Locale::Maketext::Gettext. That is all.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item $LH->bindtextdomain(DOMAIN, LOCALEDIR)
|
||||
|
||||
Register a text domain with a locale directory. Returns C<LOCALEDIR>
|
||||
itself. If C<LOCALEDIR> is omitted, the registered locale directory
|
||||
of C<DOMAIN> is returned. This method always success.
|
||||
|
||||
=item $LH->textdomain(DOMAIN)
|
||||
|
||||
Set the current text domain. Returns the C<DOMAIN> itself. If
|
||||
C<DOMAIN> is omitted, the current text domain is returned. This
|
||||
method always success.
|
||||
|
||||
=item $text = $LH->maketext($key, @param...)
|
||||
|
||||
Lookup the $key in the current lexicon and return a translated
|
||||
message in the language of the user. This is the same method in
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>, with a wrapper that
|
||||
returns the text message C<encode>d according to the current
|
||||
C<encoding>. Refer to L<Locale::Maketext(3)|Locale::Maketext/3> for
|
||||
the maketext plural notation.
|
||||
|
||||
=item $text = $LH->pmaketext($ctxt, $key, @param...)
|
||||
|
||||
Lookup the $key in a particular context in the current lexicon and
|
||||
return a translated message in the language of the user. Use
|
||||
"--keyword=pmaketext:1c,2" for the xgettext utility.
|
||||
|
||||
=item $LH->language_tag
|
||||
|
||||
Retrieve the language tag. This is the same method in
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>. It is readonly.
|
||||
|
||||
=item $LH->encoding(ENCODING)
|
||||
|
||||
Set or retrieve the output encoding. The default is the same
|
||||
encoding as the gettext MO file. You can specify C<undef>, to return
|
||||
the result in unencoded UTF-8.
|
||||
|
||||
=item $LH->key_encoding(ENCODING)
|
||||
|
||||
Specify the encoding used in your original text. The C<maketext>
|
||||
method itself is not multibyte-safe to the _AUTO lexicon. If you are
|
||||
using your native non-English language as your original text and you
|
||||
are having troubles like:
|
||||
|
||||
Unterminated bracket group, in:
|
||||
|
||||
Then, specify the C<key_encoding> to the encoding of your original
|
||||
text. Returns the current setting.
|
||||
|
||||
B<WARNING:> You should always use US-ASCII text keys. Using
|
||||
non-US-ASCII keys is always discouraged and is not guaranteed to
|
||||
be working.
|
||||
|
||||
=item $LH->encode_failure(CHECK)
|
||||
|
||||
Set the action when encode fails. This happens when the output text
|
||||
is out of the scope of your output encoding. For exmaple, output
|
||||
Chinese into US-ASCII. Refer to L<Encode(3)|Encode/3> for the
|
||||
possible values of this C<CHECK>. The default is C<FB_DEFAULT>,
|
||||
which is a safe choice that never fails. But part of your text may
|
||||
be lost, since that is what C<FB_DEFAULT> does. Returns the current
|
||||
setting.
|
||||
|
||||
=item $LH->die_for_lookup_failures(SHOULD_I_DIE)
|
||||
|
||||
Maketext dies for lookup failures, but GNU gettext never fails.
|
||||
By default Lexicon::Maketext::Gettext follows the GNU gettext
|
||||
behavior. But if you are Maketext-styled, or if you need a better
|
||||
control over the failures (like me :p), set this to 1. Returns the
|
||||
current setting.
|
||||
|
||||
Note that lookup failure handler you registered with fail_with() only
|
||||
work when die_for_lookup_failures() is enabled. if you disable
|
||||
die_for_lookup_failures(), maketext() never fails and lookup failure
|
||||
handler will be ignored.
|
||||
|
||||
=item $LH->reload_text
|
||||
|
||||
Purge the MO text cache. It purges the MO text cache from the base
|
||||
class Locale::Maketext::Gettext. The next time C<maketext> is
|
||||
called, the MO file will be read and parse from the disk again. This
|
||||
is used when your MO file is updated, but you cannot shutdown and
|
||||
restart the application. For example, when you are a co-hoster on a
|
||||
mod_perl-enabled Apache, or when your mod_perl-enabled Apache is too
|
||||
vital to be restarted for every update of your MO file, or if you
|
||||
are running a vital daemon, such as an X display server.
|
||||
|
||||
=back
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item %Lexicon = read_mo($MOfile);
|
||||
|
||||
Read and parse the MO file. Returns the read %Lexicon. The returned
|
||||
lexicon is in its original encoding.
|
||||
|
||||
If you need the meta infomation of your MO file, parse the entry
|
||||
C<$Lexicon{""}>. For example:
|
||||
|
||||
/^Content-Type: text\/plain; charset=(.*)$/im;
|
||||
$encoding = $1;
|
||||
|
||||
C<read_mo()> is exported by default, but you need to C<use
|
||||
Locale::Maketext::Gettext> in order to use it. It is not exported
|
||||
from your localization class, but from the Locale::Maketext::Gettext
|
||||
package.
|
||||
|
||||
=back
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
B<WARNING:> do not try to put any lexicon in your language subclass.
|
||||
When the C<textdomain> method is called, the current lexicon will be
|
||||
B<replaced>, but not appended. This is to accommodate the way
|
||||
C<textdomain> works. Messages from the previous text domain should
|
||||
not stay in the current text domain.
|
||||
|
||||
An essential benefit of this Locale::Maketext::Gettext over the
|
||||
original L<Locale::Maketext(3)|Locale::Maketext/3> is that:
|
||||
I<GNU gettext is multibyte safe,> but Perl source is not. GNU gettext
|
||||
is safe to Big5 characters like \xa5\x5c (Gong1). But if you follow
|
||||
the current L<Locale::Maketext(3)|Locale::Maketext/3> document and
|
||||
put your lexicon as a hash in the source of a localization subclass,
|
||||
you have to escape bytes like \x5c, \x40, \x5b, etc., in the middle
|
||||
of some natural multibyte characters. This breaks these characters
|
||||
in halves. Your non-technical translators and reviewers will be
|
||||
presented with unreadable mess, "Luan4Ma3". Sorry to say this, but
|
||||
it is weird for a localization framework to be not multibyte-safe.
|
||||
But, well, here comes Locale::Maketext::Gettext to rescue. With
|
||||
Locale::Maketext::Gettext, you can sit back and relax now, leaving
|
||||
all this mess to the excellent GNU gettext framework.
|
||||
|
||||
The idea of Locale::Maketext::Getttext came from
|
||||
L<Locale::Maketext::Lexicon(3)|Locale::Maketext::Lexicon/3>, a great
|
||||
work by Autrijus. But it has several problems at that time (version
|
||||
0.16). I was first trying to write a wrapper to fix it, but finally
|
||||
I dropped it and decided to make a solution towards
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3> itself.
|
||||
L<Locale::Maketext::Lexicon(3)|Locale::Maketext::Lexicon/3> should be
|
||||
fine now if you obtain a version newer than 0.16.
|
||||
|
||||
Locale::Maketext::Gettext also solved the problem of lack of the
|
||||
ability to handle the encoding in
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>. I implement this since
|
||||
this is what GNU gettext does. When %Lexicon is read from MO files
|
||||
by C<read_mo()>, the encoding tagged in gettext MO files is used to
|
||||
C<decode> the text into the internal encoding of Perl. Then, when
|
||||
extracted by C<maketext>, it is C<encode>d by the current
|
||||
C<encoding> value. The C<encoding> can be set at run time, so
|
||||
that you can run a daemon and output to different encoding
|
||||
according to the language settings of individual users, without
|
||||
having to restart the application. This is an improvement to the
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>, and is essential to
|
||||
daemons and C<mod_perl> applications.
|
||||
|
||||
You should trust the encoding of your gettext MO file. GNU gettext
|
||||
C<msgfmt> checks the illegal characters for you when you compile your
|
||||
MO file from your PO file. The encoding form your MO files are
|
||||
always good. If you try to output to a wrong encoding, part of your
|
||||
text may be lost, as C<FB_DEFAULT> does. If you do not like this
|
||||
C<FB_DEFAULT>, change the failure behavior with the method
|
||||
C<encode_failure>.
|
||||
|
||||
If you need the behavior of auto Traditional Chinese/Simplfied
|
||||
Chinese conversion, as GNU gettext smartly does, do it yourself with
|
||||
L<Encode::HanExtra(3)|Encode::HanExtra/3>, too. There may be a
|
||||
solution for this in the future, but not now.
|
||||
|
||||
If you set C<textdomain> to a domain that is not C<bindtextdomain> to
|
||||
specific a locale directory yet, it will try search system locale
|
||||
directories. The current system locale directory search order is:
|
||||
/usr/share/locale, /usr/lib/locale, /usr/local/share/locale,
|
||||
/usr/local/lib/locale. Suggestions for this search order are
|
||||
welcome.
|
||||
|
||||
B<NOTICE:> I<MyPackage::L10N::en-E<gt>maketext(...) is not available
|
||||
anymore,> as the C<maketext> method is no more static. That is a
|
||||
sure result, as %Lexicon is imported from foreign sources
|
||||
dynamically, but not statically hardcoded in Perl sources. But the
|
||||
documentation of L<Locale::Maketext(3)|Locale::Maketext/3> does not
|
||||
say that you can use it as a static method anyway. Maybe you were
|
||||
practicing this before. You had better check your existing code for
|
||||
this. If you try to invoke it statically, it returns C<undef>.
|
||||
|
||||
C<dgettext> and C<dcgettext> in GNU gettext are not implemented.
|
||||
It is not possible to temporarily change the current text domain in
|
||||
the current design of Locale::Maketext::Gettext. Besides, it is
|
||||
meaningless. Locale::Maketext is object-oriented. You can always
|
||||
raise a new language handle for another text domain. This is
|
||||
different from the situation of GNU gettext. Also, the category
|
||||
is always C<LC_MESSAGES>. Of course it is. We are gettext and
|
||||
Maketext.
|
||||
|
||||
Avoid creating different language handles with different
|
||||
textdomain on the same localization subclass. This currently
|
||||
works, but it violates the basic design of
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>. In
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>, %Lexicon is saved as a
|
||||
class variable, in order for the lexicon inheritance system to work.
|
||||
So, multiple language handles to a same localization subclass shares
|
||||
a same lexicon space. Their lexicon space clash. I tried to avoid
|
||||
this problem by saving a copy of the current lexicon as an instance
|
||||
variable, and replacing the class lexicon with the current instance
|
||||
lexicon whenever it is changed by another language handle instance.
|
||||
But this involves large scaled memory copy, which affects the
|
||||
proformance seriously. This is discouraged. You are adviced to use
|
||||
a single textdomain for a single localization class.
|
||||
|
||||
The C<key_encoding> is a workaround, not a solution. There is no
|
||||
solution to this problem yet. You should avoid using non-English
|
||||
language as your original text. You will get yourself into trouble
|
||||
if you mix several original text encodings, for example, joining
|
||||
several pieces of code from programmers all around the world, with
|
||||
their messages written in their own language and encodings. Solution
|
||||
suggestions are welcome.
|
||||
|
||||
C<pgettext> in GNU gettext is implemented as C<pmaketext>, in order
|
||||
to look up the text message translation in a particular context.
|
||||
Thanks to the suggestion from Chris Travers.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
GNU gettext never fails. I tries to achieve it as long as possible.
|
||||
The only reason that maketext may die unexpectedly now is
|
||||
"Unterminated bracket group". I cannot get a better solution to it
|
||||
currently. Suggestions are welcome.
|
||||
|
||||
You are welcome to fix my English. I have done my best to this
|
||||
documentation, but I am not a native English speaker after all. ^^;
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>,
|
||||
L<Locale::Maketext::TPJ13(3)|Locale::Maketext::TPJ13/3>,
|
||||
L<Locale::Maketext::Lexicon(3)|Locale::Maketext::Lexicon/3>,
|
||||
L<Encode(3)|Encode/3>, L<bindtextdomain(3)|bindtextdomain/3>,
|
||||
L<textdomain(3)|textdomain/3>. Also, please refer to the official GNU
|
||||
gettext manual at L<http://www.gnu.org/software/gettext/manual/>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
imacat <imacat@mail.imacat.idv.tw>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2003-2008 imacat. All rights reserved. This program is free
|
||||
software; you can redistribute it and/or modify it under the same terms
|
||||
as Perl itself.
|
||||
|
||||
=cut
|
781
lib/Locale/Maketext/Gettext/Functions.pm
Normal file
781
lib/Locale/Maketext/Gettext/Functions.pm
Normal file
@ -0,0 +1,781 @@
|
||||
# Locale::Maketext::Gettext::Functions - Functional interface to Locale::Maketext::Gettext
|
||||
|
||||
# Copyright (c) 2003-2008 imacat. All rights reserved. This program is free
|
||||
# software; you can redistribute it and/or modify it under the same terms
|
||||
# as Perl itself.
|
||||
# First written: 2003-04-28
|
||||
|
||||
package Locale::Maketext::Gettext::Functions;
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
use base qw(Exporter);
|
||||
use vars qw($VERSION @EXPORT @EXPORT_OK);
|
||||
$VERSION = 0.13;
|
||||
@EXPORT = qw();
|
||||
push @EXPORT, qw(bindtextdomain textdomain get_handle maketext __ N_);
|
||||
push @EXPORT, qw(dmaketext pmaketext dpmaketext);
|
||||
push @EXPORT, qw(reload_text read_mo encoding key_encoding encode_failure);
|
||||
push @EXPORT, qw(die_for_lookup_failures);
|
||||
@EXPORT_OK = @EXPORT;
|
||||
# Prototype declaration
|
||||
sub bindtextdomain($;$);
|
||||
sub textdomain(;$);
|
||||
sub get_handle(@);
|
||||
sub maketext(@);
|
||||
sub __(@);
|
||||
sub N_(@);
|
||||
sub dmaketext($$@);
|
||||
sub pmaketext($$@);
|
||||
sub dpmaketext($$$@);
|
||||
sub reload_text();
|
||||
sub encoding(;$);
|
||||
sub key_encoding(;$);
|
||||
sub encode_failure(;$);
|
||||
sub die_for_lookup_failures(;$);
|
||||
sub _declare_class($);
|
||||
sub _catclass(@);
|
||||
sub _init_textdomain($);
|
||||
sub _get_langs($$);
|
||||
sub _get_handle();
|
||||
sub _get_empty_handle();
|
||||
sub _reset();
|
||||
sub _new_rid();
|
||||
sub _k($);
|
||||
sub _lang($);
|
||||
|
||||
use Encode qw(encode decode from_to FB_DEFAULT);
|
||||
use File::Spec::Functions qw(catdir catfile);
|
||||
use Locale::Maketext::Gettext qw(read_mo);
|
||||
use vars qw(%LOCALEDIRS %RIDS %CLASSES %LANGS);
|
||||
use vars qw(%LHS $_EMPTY $LH $DOMAIN $CATEGORY $CLASSBASE @LANGS %PARAMS);
|
||||
use vars qw(@SYSTEM_LOCALEDIRS);
|
||||
%LHS = qw();
|
||||
# The category is always LC_MESSAGES
|
||||
$CATEGORY = "LC_MESSAGES";
|
||||
$CLASSBASE = "Locale::Maketext::Gettext::_runtime";
|
||||
# Current language parameters
|
||||
@LANGS = qw();
|
||||
@SYSTEM_LOCALEDIRS = @Locale::Maketext::Gettext::SYSTEM_LOCALEDIRS;
|
||||
%PARAMS = qw();
|
||||
$PARAMS{"KEY_ENCODING"} = "US-ASCII";
|
||||
$PARAMS{"ENCODE_FAILURE"} = FB_DEFAULT;
|
||||
$PARAMS{"DIE_FOR_LOOKUP_FAILURES"} = 0;
|
||||
# Parameters for random class IDs
|
||||
use vars qw($RID_LEN @RID_CHARS);
|
||||
$RID_LEN = 8;
|
||||
@RID_CHARS = split //,
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
# bindtextdomain: Bind a text domain to a locale directory
|
||||
sub bindtextdomain($;$) {
|
||||
local ($_, %_);
|
||||
my ($domain, $LOCALEDIR);
|
||||
($domain, $LOCALEDIR) = @_;
|
||||
# Return the current registry
|
||||
return (exists $LOCALEDIRS{$domain}? $LOCALEDIRS{$domain}: undef)
|
||||
if !defined $LOCALEDIR;
|
||||
# Register the locale directory
|
||||
$LOCALEDIRS{$domain} = $LOCALEDIR;
|
||||
# Reinitialize the text domain
|
||||
_init_textdomain($domain);
|
||||
# Reset the current language handle
|
||||
_get_handle() if defined $DOMAIN && $domain eq $DOMAIN;
|
||||
# Return the locale directory
|
||||
return $LOCALEDIR;
|
||||
}
|
||||
|
||||
# textdomain: Set the current text domain
|
||||
sub textdomain(;$) {
|
||||
local ($_, %_);
|
||||
my ($new_domain);
|
||||
$new_domain = $_[0];
|
||||
# Return the current text domain
|
||||
return $DOMAIN if !defined $new_domain;
|
||||
# Set the current text domain
|
||||
$DOMAIN = $new_domain;
|
||||
# Reinitialize the text domain
|
||||
_init_textdomain($DOMAIN);
|
||||
# Reset the current language handle
|
||||
_get_handle();
|
||||
return $DOMAIN;
|
||||
}
|
||||
|
||||
# get_handle: Get a language handle
|
||||
sub get_handle(@) {
|
||||
local ($_, %_);
|
||||
# Register the current get_handle arguments
|
||||
@LANGS = @_;
|
||||
# Reset and return the current language handle
|
||||
return _get_handle();
|
||||
}
|
||||
|
||||
# maketext: Maketext, in its long name
|
||||
# Use @ instead of $@ in prototype, so that we can pass @_ to it.
|
||||
sub maketext(@) {
|
||||
return __($_[0], @_[1..$#_]);
|
||||
}
|
||||
|
||||
# __: Maketext, in its shortcut name
|
||||
# Use @ instead of $@ in prototype, so that we can pass @_ to it.
|
||||
sub __(@) {
|
||||
local ($_, %_);
|
||||
my ($key, @param, $keyd);
|
||||
($key, @param) = @_;
|
||||
# Reset the current language handle if it is not set yet
|
||||
_get_handle() if !defined $LH;
|
||||
|
||||
# Decode the source text
|
||||
$keyd = $key;
|
||||
$keyd = decode($PARAMS{"KEY_ENCODING"}, $keyd, $PARAMS{"ENCODE_FAILURE"})
|
||||
if exists $PARAMS{"KEY_ENCODING"} && !Encode::is_utf8($key);
|
||||
# Maketext
|
||||
$_ = $LH->maketext($keyd, @param);
|
||||
# Output to the requested encoding
|
||||
if (exists $PARAMS{"ENCODING"}) {
|
||||
$_ = encode($PARAMS{"ENCODING"}, $_, $PARAMS{"ENCODE_FAILURE"});
|
||||
# Pass through the empty/invalid lexicon
|
||||
} elsif ( scalar(keys %{$LH->{"Lexicon"}}) == 0
|
||||
&& exists $PARAMS{"KEY_ENCODING"}
|
||||
&& !Encode::is_utf8($key)) {
|
||||
$_ = encode($PARAMS{"KEY_ENCODING"}, $_, $PARAMS{"ENCODE_FAILURE"});
|
||||
}
|
||||
|
||||
return $_;
|
||||
}
|
||||
|
||||
# N_: Return the original text untouched, so that it can be catched
|
||||
# with xgettext
|
||||
# Use @ instead of $@ in prototype, so that we can pass @_ to it.
|
||||
sub N_(@) {
|
||||
# Watch out for this Perl magic! :p
|
||||
return $_[0] unless wantarray;
|
||||
return @_;
|
||||
}
|
||||
|
||||
# dmaketext: Maketext in another text domain temporarily,
|
||||
# an equivalent to dgettext().
|
||||
sub dmaketext($$@) {
|
||||
local ($_, %_);
|
||||
my ($domain, $key, @param, $lh0, $domain0, $text);
|
||||
($domain, $key, @param) = @_;
|
||||
# Preserve the current status
|
||||
($lh0, $domain0) = ($LH, $DOMAIN);
|
||||
# Reinitialize the text domain
|
||||
textdomain($domain);
|
||||
# Maketext
|
||||
$text = maketext($key, @param);
|
||||
# Return the current status
|
||||
($LH, $DOMAIN) = ($lh0, $domain0);
|
||||
# Return the "made text"
|
||||
return $text;
|
||||
}
|
||||
|
||||
# pmaketext: Maketext with context,
|
||||
# an equivalent to pgettext().
|
||||
sub pmaketext($$@) {
|
||||
local ($_, %_);
|
||||
my ($ctxt, $key, @param);
|
||||
($ctxt, $key, @param) = @_;
|
||||
# This is actually a wrapper to the maketext() function
|
||||
return maketext("$ctxt\x04$key", @param);
|
||||
}
|
||||
|
||||
# dpmaketext: Maketext with context in another text domain temporarily,
|
||||
# an equivalent to dpgettext().
|
||||
sub dpmaketext($$$@) {
|
||||
local ($_, %_);
|
||||
my ($domain, $ctxt, $key, @param);
|
||||
($domain, $ctxt, $key, @param) = @_;
|
||||
# This is actually a wrapper to the dmaketext() function
|
||||
return dmaketext($domain, "$ctxt\x04$key", @param);
|
||||
}
|
||||
|
||||
# reload_text: Purge the lexicon cache
|
||||
sub reload_text() {
|
||||
# reload_text is static.
|
||||
Locale::Maketext::Gettext->reload_text;
|
||||
}
|
||||
|
||||
# encoding: Set the output encoding
|
||||
sub encoding(;$) {
|
||||
local ($_, %_);
|
||||
$_ = $_[0];
|
||||
|
||||
# Set the output encoding
|
||||
if (@_ > 0) {
|
||||
if (defined $_) {
|
||||
$PARAMS{"ENCODING"} = $_;
|
||||
} else {
|
||||
delete $PARAMS{"ENCODING"};
|
||||
}
|
||||
$PARAMS{"USERSET_ENCODING"} = $_;
|
||||
}
|
||||
|
||||
# Return the encoding
|
||||
return exists $PARAMS{"ENCODING"}? $PARAMS{"ENCODING"}: undef;
|
||||
}
|
||||
|
||||
# key_encoding: Set the encoding of the original text
|
||||
sub key_encoding(;$) {
|
||||
local ($_, %_);
|
||||
$_ = $_[0];
|
||||
|
||||
# Set the encoding used in the keys
|
||||
if (@_ > 0) {
|
||||
if (defined $_) {
|
||||
$PARAMS{"KEY_ENCODING"} = $_;
|
||||
} else {
|
||||
delete $PARAMS{"KEY_ENCODING"};
|
||||
}
|
||||
}
|
||||
|
||||
# Return the encoding
|
||||
return exists $PARAMS{"KEY_ENCODING"}? $PARAMS{"KEY_ENCODING"}: undef;
|
||||
}
|
||||
|
||||
# encode_failure: What to do if the text is out of your output encoding
|
||||
# Refer to Encode on possible values of this check
|
||||
sub encode_failure(;$) {
|
||||
local ($_, %_);
|
||||
$_ = $_[0];
|
||||
# Set and return the current setting
|
||||
$PARAMS{"ENCODE_FAILURE"} = $_ if @_ > 0;
|
||||
# Return the current setting
|
||||
return $PARAMS{"ENCODE_FAILURE"};
|
||||
}
|
||||
|
||||
# die_for_lookup_failures: Whether we should die for lookup failure
|
||||
# The default is no. GNU gettext never fails.
|
||||
sub die_for_lookup_failures(;$) {
|
||||
local ($_, %_);
|
||||
$_ = $_[0];
|
||||
# Set the current setting
|
||||
if (@_ > 0) {
|
||||
$PARAMS{"DIE_FOR_LOOKUP_FAILURES"} = $_? 1: 0;
|
||||
$LH->die_for_lookup_failures($PARAMS{"DIE_FOR_LOOKUP_FAILURES"});
|
||||
}
|
||||
# Return the current setting
|
||||
# Resetting the current language handle is not required
|
||||
# Lookup failures are handled by the fail handler directly
|
||||
return $PARAMS{"DIE_FOR_LOOKUP_FAILURES"};
|
||||
}
|
||||
|
||||
# _declare_class: Declare a class
|
||||
sub _declare_class($) {
|
||||
local ($_, %_);
|
||||
$_ = $_[0];
|
||||
eval << "EOT";
|
||||
package $_[0];
|
||||
use base qw(Locale::Maketext::Gettext);
|
||||
use vars qw(\@ISA %Lexicon);
|
||||
EOT
|
||||
}
|
||||
|
||||
# _catclass: Catenate the class name
|
||||
sub _catclass(@) {
|
||||
return join("::", @_);;
|
||||
}
|
||||
|
||||
# _init_textdomain: Initialize a text domain
|
||||
sub _init_textdomain($) {
|
||||
local ($_, %_);
|
||||
my ($domain, $k, @langs, $langs);
|
||||
$domain = $_[0];
|
||||
|
||||
# Return if text domain not specified yet
|
||||
return if !defined $domain;
|
||||
|
||||
# Obtain the available locales
|
||||
# A binded domain
|
||||
if (exists $LOCALEDIRS{$domain}) {
|
||||
@langs = _get_langs($LOCALEDIRS{$domain}, $domain);
|
||||
# Not binded
|
||||
} else {
|
||||
@langs = qw();
|
||||
# Search the system locale directories
|
||||
foreach (@SYSTEM_LOCALEDIRS) {
|
||||
@langs = _get_langs($_, $domain);
|
||||
# Domain not found in this directory
|
||||
next if @langs == 0;
|
||||
$LOCALEDIRS{$domain} = $_;
|
||||
last;
|
||||
}
|
||||
# Not found at last
|
||||
return if !exists $LOCALEDIRS{$domain};
|
||||
}
|
||||
$langs = join ",", sort @langs;
|
||||
|
||||
# Obtain the registry key
|
||||
$k = _k($domain);
|
||||
|
||||
# Available language list remains for this domain
|
||||
return if exists $LANGS{$k} && $LANGS{$k} eq $langs;
|
||||
# Register this new language list
|
||||
$LANGS{$k} = $langs;
|
||||
|
||||
my ($rid, $class);
|
||||
# Garbage collection - drop abandoned language handles
|
||||
if (exists $CLASSES{$k}) {
|
||||
delete $LHS{$_} foreach grep /^$CLASSES{$k}/, keys %LHS;
|
||||
}
|
||||
# Get a new class ID
|
||||
$rid = _new_rid();
|
||||
# Obtain the class name
|
||||
$class = _catclass($CLASSBASE, $rid);
|
||||
# Register the domain with this class
|
||||
$CLASSES{$k} = $class;
|
||||
# Declare this class
|
||||
_declare_class($class);
|
||||
# Declare its language subclasses
|
||||
_declare_class(_catclass($class, $_))
|
||||
foreach @langs;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# _get_langs: Search a locale directory and return the available languages
|
||||
sub _get_langs($$) {
|
||||
local ($_, %_);
|
||||
my ($dir, $domain, $DH, $entry, $MOfile);
|
||||
($dir, $domain) = @_;
|
||||
|
||||
@_ = qw();
|
||||
{
|
||||
opendir $DH, $dir or last;
|
||||
while (defined($entry = readdir $DH)) {
|
||||
# Skip hidden entries
|
||||
next if $entry =~ /^\./;
|
||||
# Skip non-directories
|
||||
next unless -d catdir($dir, $entry);
|
||||
# Skip locales with dot "." (trailing encoding)
|
||||
next if $entry =~ /\./;
|
||||
# Get the MO file name
|
||||
$MOfile = catfile($dir, $entry, $CATEGORY, "$domain.mo");
|
||||
# Skip if MO file is not available for this locale
|
||||
next if ! -f $MOfile && ! -r $MOfile;
|
||||
# Map C to i_default
|
||||
$entry = "i_default" if $entry eq "C";
|
||||
# Add this language
|
||||
push @_, lc $entry;
|
||||
}
|
||||
close $DH or last;
|
||||
}
|
||||
return @_;
|
||||
}
|
||||
|
||||
# _get_handle: Set the language handle with the current DOMAIN and @LANGS
|
||||
sub _get_handle() {
|
||||
local ($_, %_);
|
||||
my ($k, $class, $subclass);
|
||||
|
||||
# Lexicon empty if text domain not specified, or not binded yet
|
||||
return _get_empty_handle if !defined $DOMAIN || !exists $LOCALEDIRS{$DOMAIN};
|
||||
# Obtain the registry key
|
||||
$k = _k($DOMAIN);
|
||||
# Lexicon empty if text domain was not properly set yet
|
||||
return _get_empty_handle if !exists $CLASSES{$k};
|
||||
|
||||
# Get the localization class name
|
||||
$class = $CLASSES{$k};
|
||||
# Get the language handle
|
||||
$LH = $class->get_handle(@LANGS);
|
||||
# Lexicon empty if failed get_handle()
|
||||
return _get_empty_handle if !defined $LH;
|
||||
|
||||
# Obtain the subclass name of the got language handle
|
||||
$subclass = ref($LH);
|
||||
# Use the existing language handle whenever possible, to reduce
|
||||
# the initialization overhead
|
||||
if (exists $LHS{$subclass}) {
|
||||
$LH = $LHS{$subclass};
|
||||
if (!exists $PARAMS{"USERSET_ENCODING"}) {
|
||||
if (exists $LH->{"MO_ENCODING"}) {
|
||||
$PARAMS{"ENCODING"} = $LH->{"MO_ENCODING"};
|
||||
} else {
|
||||
delete $PARAMS{"ENCODING"};
|
||||
}
|
||||
}
|
||||
return _lang($LH)
|
||||
}
|
||||
|
||||
# Initialize it
|
||||
$LH->bindtextdomain($DOMAIN, $LOCALEDIRS{$DOMAIN});
|
||||
$LH->textdomain($DOMAIN);
|
||||
# Respect the MO file encoding unless there is a user preferrence
|
||||
if (!exists $PARAMS{"USERSET_ENCODING"}) {
|
||||
if (exists $LH->{"MO_ENCODING"}) {
|
||||
$PARAMS{"ENCODING"} = $LH->{"MO_ENCODING"};
|
||||
} else {
|
||||
delete $PARAMS{"ENCODING"};
|
||||
}
|
||||
}
|
||||
# We handle the encoding() and key_encoding() ourselves.
|
||||
$LH->key_encoding(undef);
|
||||
$LH->encoding(undef);
|
||||
# Register it
|
||||
$LHS{$subclass} = $LH;
|
||||
|
||||
return _lang($LH);
|
||||
}
|
||||
|
||||
# _get_empty_handle: Obtain the empty language handle
|
||||
sub _get_empty_handle() {
|
||||
local ($_, %_);
|
||||
if (!defined $_EMPTY) {
|
||||
$_EMPTY = Locale::Maketext::Gettext::Functions::_EMPTY->get_handle;
|
||||
$_EMPTY->key_encoding(undef);
|
||||
$_EMPTY->encoding(undef);
|
||||
}
|
||||
$LH = $_EMPTY;
|
||||
$LH->die_for_lookup_failures($PARAMS{"DIE_FOR_LOOKUP_FAILURES"});
|
||||
return _lang($LH);
|
||||
}
|
||||
|
||||
# _reset: Initialize everything
|
||||
sub _reset() {
|
||||
local ($_, %_);
|
||||
|
||||
%LOCALEDIRS = qw();
|
||||
undef $LH;
|
||||
undef $DOMAIN;
|
||||
@LANGS = qw();
|
||||
%PARAMS = qw();
|
||||
$PARAMS{"KEY_ENCODING"} = "US-ASCII";
|
||||
$PARAMS{"ENCODE_FAILURE"} = FB_DEFAULT;
|
||||
$PARAMS{"DIE_FOR_LOOKUP_FAILURES"} = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# _new_rid: Generate a new random ID
|
||||
sub _new_rid() {
|
||||
local ($_, %_);
|
||||
my ($id);
|
||||
|
||||
do {
|
||||
for ($id = "", $_ = 0; $_ < $RID_LEN; $_++) {
|
||||
$id .= $RID_CHARS[int rand scalar @RID_CHARS];
|
||||
}
|
||||
} while exists $RIDS{$id};
|
||||
$RIDS{$id} = 1;
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
# _k: Build the key for the domain registry
|
||||
sub _k($) {
|
||||
return join "\n", $LOCALEDIRS{$_[0]}, $CATEGORY, $_[0];
|
||||
}
|
||||
|
||||
# _lang: The langage from a language handle. language_tag is not quite sane.
|
||||
sub _lang($) {
|
||||
local ($_, %_);
|
||||
$_ = $_[0];
|
||||
$_ = ref($_);
|
||||
s/^.+:://;
|
||||
s/_/-/g;
|
||||
return $_;
|
||||
}
|
||||
|
||||
# Public empty lexicon
|
||||
package Locale::Maketext::Gettext::Functions::_EMPTY;
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
use base qw(Locale::Maketext::Gettext);
|
||||
use vars qw($VERSION @ISA %Lexicon);
|
||||
$VERSION = 0.01;
|
||||
|
||||
package Locale::Maketext::Gettext::Functions::_EMPTY::i_default;
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
use base qw(Locale::Maketext::Gettext);
|
||||
use vars qw($VERSION @ISA %Lexicon);
|
||||
$VERSION = 0.01;
|
||||
|
||||
return 1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Locale::Maketext::Gettext::Functions - Functional interface to Locale::Maketext::Gettext
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Locale::Maketext::Gettext::Functions;
|
||||
bindtextdomain(DOMAIN, LOCALEDIR);
|
||||
textdomain(DOMAIN);
|
||||
get_handle("de");
|
||||
print __("Hello, world!\n");
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Locale::Maketext::Gettext::Functions is a functional
|
||||
interface to
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3> (and
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>). It works exactly the GNU
|
||||
gettext way. It plays magic to
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3> for you. No more
|
||||
localization class/subclasses and language handles are required at
|
||||
all.
|
||||
|
||||
The C<maketext>, C<dmaketext>, C<pmaketext> and C<dpmaketext>
|
||||
functions attempt to translate a text message into the native
|
||||
language of the user, by looking up the translation in an MO lexicon
|
||||
file.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item bindtextdomain(DOMAIN, LOCALEDIR)
|
||||
|
||||
Register a text domain with a locale directory. Returns C<LOCALEDIR>
|
||||
itself. If C<LOCALEDIR> is omitted, the registered locale directory
|
||||
of C<DOMAIN> is returned. This method always success.
|
||||
|
||||
=item textdomain(DOMAIN)
|
||||
|
||||
Set the current text domain. Returns the C<DOMAIN> itself. if
|
||||
C<DOMAIN> is omitted, the current text domain is returned. This
|
||||
method always success.
|
||||
|
||||
=item get_handle(@languages)
|
||||
|
||||
Set the language of the user. It searches for an available language
|
||||
in the provided @languages list. If @languages was not provided, it
|
||||
looks checks environment variable LANG, and HTTP_ACCEPT_LANGUAGE
|
||||
when running as CGI. Refer to
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3> for the magic of the
|
||||
C<get_handle>.
|
||||
|
||||
=item $message = maketext($key, @param...)
|
||||
|
||||
Attempts to translate a text message into the native language of the
|
||||
user, by looking up the translation in an MO lexicon file. Refer to
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3> for the C<maketext> plural
|
||||
grammer.
|
||||
|
||||
=item $message = __($key, @param...)
|
||||
|
||||
A synonym to C<maketext()>. This is a shortcut to C<maketext()> so
|
||||
that it is cleaner when you employ maketext to your existing project.
|
||||
|
||||
=item ($key, @param...) = N_($key, @param...)
|
||||
|
||||
Returns the original text untouched. This is to enable the text be
|
||||
catched with xgettext.
|
||||
|
||||
=item $message = dmaketext($domain, $key, @param...)
|
||||
|
||||
Temporarily switch to another text domain and attempts to translate
|
||||
a text message into the native language of the user in that text
|
||||
domain. Use "--keyword=dmaketext:2" for the xgettext utility.
|
||||
|
||||
=item $message = pmaketext($ctxt, $key, @param...)
|
||||
|
||||
Attempts to translate a text message in a particular context into the
|
||||
native language of the user. Use "--keyword=pmaketext:1c,2" for
|
||||
the xgettext utility.
|
||||
|
||||
=item $message = dpmaketext($domain, $ctxt, $key, @param...)
|
||||
|
||||
Temporarily switch to another text domain and attempts to translate
|
||||
a text message in a particular context into the native language of
|
||||
the user in that text domain. Use "--keyword=dpmaketext:2c,3" for
|
||||
the xgettext utility.
|
||||
|
||||
=item encoding(ENCODING)
|
||||
|
||||
Set or retrieve the output encoding. The default is the same
|
||||
encoding as the gettext MO file. You can specify C<undef>, to return
|
||||
the result in unencoded UTF-8.
|
||||
|
||||
=item key_encoding(ENCODING)
|
||||
|
||||
Specify the encoding used in your original text. The C<maketext>
|
||||
method itself is not multibyte-safe to the _AUTO lexicon. If you are
|
||||
using your native non-English language as your original text and you
|
||||
are having troubles like:
|
||||
|
||||
Unterminated bracket group, in:
|
||||
|
||||
Then, specify the C<key_encoding> to the encoding of your original
|
||||
text. Returns the current setting.
|
||||
|
||||
B<WARNING:> You should always use US-ASCII text keys. Using
|
||||
non-US-ASCII keys is always discouraged and is not guaranteed to
|
||||
be working.
|
||||
|
||||
=item encode_failure(CHECK)
|
||||
|
||||
Set the action when encode fails. This happens when the output text
|
||||
is out of the scope of your output encoding. For exmaple, output
|
||||
Chinese into US-ASCII. Refer to L<Encode(3)|Encode/3> for the
|
||||
possible values of this C<CHECK>. The default is C<FB_DEFAULT>,
|
||||
which is a safe choice that never fails. But part of your text may
|
||||
be lost, since that is what C<FB_DEFAULT> does. Returns the current
|
||||
setting.
|
||||
|
||||
=item die_for_lookup_failures(SHOULD_I_DIE)
|
||||
|
||||
Maketext dies for lookup failures, but GNU gettext never fails.
|
||||
By default Lexicon::Maketext::Gettext follows the GNU gettext
|
||||
behavior. But if you are Maketext-styled, or if you need a better
|
||||
control over the failures (like me :p), set this to 1. Returns the
|
||||
current setting.
|
||||
|
||||
=item reload_text()
|
||||
|
||||
Purges the MO text cache. By default MO files are cached after they
|
||||
are read and parsed from the disk, to reduce I/O and parsing overhead
|
||||
on busy sites. reload_text() purges this cache, so that updated MO
|
||||
files can take effect at run-time. This is used when your MO file is
|
||||
updated, but you cannot shutdown and restart the application. for
|
||||
example, when you are a co-hoster on a mod_perl-enabled Apache, or
|
||||
when your mod_perl-enabled Apache is too vital to be restarted for
|
||||
every update of your MO file, or if you are running a vital daemon,
|
||||
such as an X display server.
|
||||
|
||||
=item %Lexicon = read_mo($MOfile)
|
||||
|
||||
Read and parse the MO file. Returns the read %Lexicon. The returned
|
||||
lexicon is in its original encoding.
|
||||
|
||||
If you need the meta infomation of your MO file, parse the entry
|
||||
C<$Lexicon{""}>. For example:
|
||||
|
||||
/^Content-Type: text\/plain; charset=(.*)$/im;
|
||||
$encoding = $1;
|
||||
|
||||
=back
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
B<NOTE:> Since localization classes are generated at run-time, it is
|
||||
not possible to override the Maketext language functions, like
|
||||
C<quant> or C<numerate>. If that is your concern, use
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3> instead.
|
||||
Suggestions are welcome.
|
||||
|
||||
You can now add/remove languages/MO files at run-time. This is a
|
||||
major improvement over the original
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3> (and
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>). This is done by
|
||||
registering localization classes with random IDs, so that the same
|
||||
text domain can be re-declared infinitely, whenever needed (language
|
||||
list changes, LOCALEDIR changes, etc.) This is not possible to the
|
||||
object-interface of
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3> (and
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>).
|
||||
|
||||
Language addition/removal takes effect only after C<bindtextdomain>
|
||||
or C<textdomain> is called. It has no effect on C<maketext> calls.
|
||||
This keeps a basic sanity in the lifetime of a running script.
|
||||
|
||||
If you set C<textdomain> to a domain that is not C<bindtextdomain> to
|
||||
specific a locale directory yet, it will try search system locale
|
||||
directories. The current system locale directory search order is:
|
||||
/usr/share/locale, /usr/lib/locale, /usr/local/share/locale,
|
||||
/usr/local/lib/locale. Suggestions are welcome.
|
||||
|
||||
=head1 STORY
|
||||
|
||||
The idea is that: I finally realized that, no matter how hard I try,
|
||||
I<I can never get a never-failure C<maketext>.> A common wrapper
|
||||
like:
|
||||
|
||||
sub __ { return $LH->maketext(@_) };
|
||||
|
||||
always fails if $LH is not initialized yet. For this reason,
|
||||
C<maketext> can hardly be employed in error handlers to output
|
||||
graceful error messages in the natural language of the user. So,
|
||||
I have to write something like this:
|
||||
|
||||
sub __ {
|
||||
$LH = MyPkg::L10N->get_handle if !defined $LH;
|
||||
return $LH->maketext(@_);
|
||||
}
|
||||
|
||||
But what if C<get_handle> itself fails? So, this becomes:
|
||||
|
||||
sub __ {
|
||||
$LH = MyPkg::L10N->get_handle if !defined $LH;
|
||||
$LH = _AUTO->get_handle if !defined $LH;
|
||||
return $LH->maketext(@_);
|
||||
}
|
||||
package _AUTO;
|
||||
use base qw(Locale::Maketext);
|
||||
package _AUTO::i_default;
|
||||
use base qw(Locale::Maketext);
|
||||
%Lexicon = ( "_AUTO" => 1 );
|
||||
|
||||
Ya, this works. But, if I always have to do this in my every
|
||||
application, why should I not make a solution to the localization
|
||||
framework itself? This is a common problem to every localization
|
||||
projects. It should be solved at the localization framework level,
|
||||
but not at the application level.
|
||||
|
||||
Another reason is that: I<Programmers should be able to use
|
||||
C<maketext> without the knowledge of object-oriented programming.>
|
||||
A localization framework should be neat and simple. It should lower
|
||||
down its barrier, be friendly to the beginners, in order to
|
||||
encourage the use of localization and globalization. Apparently
|
||||
the current practice of L<Locale::Maketext(3)|Locale::Maketext/3>
|
||||
does not satisfy this request.
|
||||
|
||||
The third reason is: Since
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3> imports
|
||||
the lexicon from foreign sources, the class source file is left
|
||||
empty. It exists only to help the C<get_handle> method looking for
|
||||
a proper language handle. Then, why not make it disappear, and be
|
||||
generated whenever needed? Why bother the programmers to put
|
||||
an empty class source file there?
|
||||
|
||||
How neat can we be?
|
||||
|
||||
imacat, 2003-04-29
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Since maketext localization classes are generated at run time,
|
||||
Maketext language function override, like C<quant> or C<numerate>, is
|
||||
not available here. Suggestions are welcome.
|
||||
|
||||
C<encoding>, C<key_encoding>, C<encode_failure> and
|
||||
C<die_for_lookup_failures> are not mod_perl-safe. These settings
|
||||
affect the whole process, including the following scripts it is
|
||||
going to run. This is the same as C<setlocale> in
|
||||
L<POSIX(3)|POSIX/3>. Always set them at the very beginning of your
|
||||
script if you are running under mod_perl. If you do not like it,
|
||||
use the object-oriented
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3> instead.
|
||||
Suggestions are welcome.
|
||||
|
||||
Smart translation between Traditional Chinese/Simplified Chinese,
|
||||
like what GNU gettext does, is not available yet. Suggestions are
|
||||
welcome.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Locale::Maketext(3)|Locale::Maketext/3>,
|
||||
L<Locale::Maketext::TPJ13(3)|Locale::Maketext::TPJ13/3>,
|
||||
L<Locale::Maketext::Gettext(3)|Locale::Maketext::Gettext/3>,
|
||||
L<bindtextdomain(3)|bindtextdomain/3>, L<textdomain(3)|textdomain/3>.
|
||||
Also, please refer to the official GNU gettext manual at
|
||||
L<http://www.gnu.org/software/gettext/manual/>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
imacat <imacat@mail.imacat.idv.tw>
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright (c) 2003-2008 imacat. All rights reserved. This program is free
|
||||
software; you can redistribute it and/or modify it under the same terms
|
||||
as Perl itself.
|
||||
|
||||
=cut
|
Reference in New Issue
Block a user