use warnings; use strict; package Jifty::Script::Po; use base qw(Jifty::Script Class::Accessor::Fast); use Pod::Usage; use File::Copy (); use File::Path 'mkpath'; use Jifty::Config (); use Jifty::YAML (); use Locale::Maketext::Extract (); use File::Find::Rule (); use MIME::Types (); our $MIME = MIME::Types->new(); our $LMExtract = Locale::Maketext::Extract->new( # Specify which parser plugins to use plugins => { # Use Perl parser, process files with extension .pl .pm .cgi 'Locale::Maketext::Extract::Plugin::PPI' => ['pm','pl'], 'tt2' => [ ], 'perl' => ['pl','pm','js','json'], 'mason' => [ ] , }, verbose => 1, ); use constant USE_GETTEXT_STYLE => 1; __PACKAGE__->mk_accessors(qw/language/); ### Help is below in __DATA__ section =head2 options Returns a hash of all the options this script takes. (See the usage message for details) =cut sub options { my $self = shift; return ( $self->SUPER::options, 'l|language=s' => 'language', 'dir=s@' => 'directories', 'podir=s' => 'podir', 'js' => 'js', 'template_name=s' => 'template_name', ) } =head2 run Runs the "update_catalogs" method. =cut sub run { my $self = shift; return if $self->print_help; Jifty->new(no_handle => 1); return $self->_js_gen if $self->{js}; $self->update_catalogs; } sub _js_gen { my $self = shift; my $static_handler = Jifty::View::Static::Handler->new; my $logger =Log::Log4perl->get_logger("main"); for my $file ( @{ Jifty::Web->javascript_libs } ) { next if $file =~ m/^ext/; next if $file =~ m/^yui/; next if $file =~ m/^rico/; my $path = $static_handler->file_path( File::Spec->catdir( 'js', $file ) ) or next; $logger->info("Extracting messages from '$path'"); $LMExtract->extract_file( $path ); } $LMExtract->set_compiled_entries; $LMExtract->compile(USE_GETTEXT_STYLE); Jifty::I18N->new; mkpath ['share/web/static/js/dict']; for my $lang (Jifty::I18N->available_languages) { my $file = "share/web/static/js/dict/$lang.json"; $logger->info("Generating $file"); open my $fh, '>', $file or die "$file: $!"; no strict 'refs'; print $fh Jifty::JSON::objToJson( { map { my $text = ${"Jifty::I18N::".$lang."::Lexicon"}{$_}; defined $text ? ( $_ => $text ) : () } keys %{$LMExtract->lexicon} }, { singlequote => 1 } ); } } =head2 _check_mime_type FILENAME This routine returns a mimetype for the file C. =cut sub _check_mime_type { my $self = shift; my $local_path = shift; my $mimeobj = $MIME->mimeTypeOf($local_path); my $mime_type = ($mimeobj ? $mimeobj->type : "unknown"); return if ( $mime_type =~ /^image/ ); return 1; } =head2 update_catalogs Extracts localizable messages from all files in your application, finds all your message catalogs and updates them with new and changed messages. =cut sub update_catalogs { my $self = shift; my $podir = $self->{'podir'} || Jifty->config->framework('L10N')->{'PoDir'}; $self->extract_messages; $self->update_catalog( File::Spec->catfile( $podir, $self->pot_name . ".pot" ) ); if ($self->{'language'}) { $self->update_catalog( File::Spec->catfile( $podir, $self->{'language'} . ".po" ) ); return; } my @catalogs = grep !m{(^|/)\.svn/}, File::Find::Rule->file->name('*.po')->in( $podir ); unless ( @catalogs ) { $self->log->error("You have no existing message catalogs."); $self->log->error("Run `jifty po --language ` to create a new one."); $self->log->error("Read `jifty po --help` to get more info."); return } foreach my $catalog (@catalogs) { $self->update_catalog( $catalog ); } } =head2 update_catalog FILENAME Reads C, a message catalog and integrates new or changed translations. =cut sub update_catalog { my $self = shift; my $translation = shift; my $logger =Log::Log4perl->get_logger("main"); $logger->info( "Updating message catalog '$translation'"); $LMExtract->read_po($translation) if ( -f $translation && $translation !~ m/pot$/ ); my $orig_lexicon; # Reset previously compiled entries before a new compilation $LMExtract->set_compiled_entries; $LMExtract->compile(USE_GETTEXT_STYLE); if ($self->_is_core && !$self->{'template_name'}) { $LMExtract->write_po($translation); } else { $orig_lexicon = $LMExtract->lexicon; my $lexicon = { %$orig_lexicon }; # XXX: cache core_lm my $core_lm = Locale::Maketext::Extract->new(); Locale::Maketext::Lexicon::set_option('allow_empty' => 1); $core_lm->read_po( File::Spec->catfile( Jifty->config->framework('L10N')->{'DefaultPoDir'}, 'jifty.pot' )); Locale::Maketext::Lexicon::set_option('allow_empty' => 0); for (keys %{ $core_lm->lexicon }) { next unless exists $lexicon->{$_}; # keep the local entry overriding core if it exists delete $lexicon->{$_} unless length $lexicon->{$_}; } $LMExtract->set_lexicon($lexicon); $LMExtract->write_po($translation); $LMExtract->set_lexicon($orig_lexicon); } } =head2 extract_messages Find all translatable messages in your application, using L. =cut sub extract_messages { my $self = shift; # find all the .pm files in @INC my @files = File::Find::Rule->file->in( @{ $self->{directories} || [ Jifty->config->framework('Web')->{'TemplateRoot'}, 'lib', 'bin'] } ); my $logger =Log::Log4perl->get_logger("main"); foreach my $file (@files) { next if $file =~ m{(^|/)[\._]svn/}; next if $file =~ m{\~$}; next if $file =~ m{\.pod$}; next unless $self->_check_mime_type($file ); $logger->info("Extracting messages from '$file'"); $LMExtract->extract_file($file); } } =head2 print_help Prints out help for the package using pod2usage. If the user specified --help, prints a brief usage message If the user specified --man, prints out a manpage =cut sub print_help { my $self = shift; return 0 unless $self->{help} || $self->{man}; # Option handling my $docs = \*DATA; pod2usage( -exitval => 1, -input => $docs ) if $self->{help}; pod2usage( -exitval => 0, -verbose => 2, -input => $docs ) if $self->{man}; return 1; } sub _is_core { return 1 if Jifty->config->framework('ApplicationName') eq 'JiftyApp'; } =head2 pot_name Returns the name of the po template. =cut sub pot_name { my $self = shift; return $self->{'template_name'} if $self->{'template_name'}; return 'jifty' if $self->_is_core; return lc Jifty->config->framework('ApplicationName'); } 1; __DATA__ =head1 NAME Jifty::Script::Po - Extract translatable strings from your application =head1 SYNOPSIS jifty po --language Creates a .po file for translation jifty po Updates all existing po files Options: --language Language to deal with --dir Additionl dirs to extract from --js Generate json files from the current po files --help brief help message --man full documentation =head1 OPTIONS =over 8 =item B<--language> This script an option, C<--language>, which is optional; it is the name of a message catalog to create. =item B<--dir> Specify explicit directories to extract from. Can be used multiple times. The default directores will not be extracted if you use this option. =item B<--template_name> Specify the name of the po template. Default to the lower-cased application name. =item B<--podir> Specify the directory of the po templates. =item B<--js> If C<--js> is given, other options are ignored and the script will generate json files for each language under F from the current po files. Before doing so, you might want to run C with C<--dir share/web/static/js> to include messages from javascript in your po files. =item B<--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =back =head1 DESCRIPTION Extracts message catalogs for your Jifty app. When run, Jifty will update all existing message catalogs, as well as create a new one if you specify a --language option. =cut