1use warnings; 2use strict; 3 4package Jifty::Script::Po; 5use base qw(Jifty::Script Class::Accessor::Fast); 6 7use Pod::Usage; 8use File::Copy (); 9use File::Path 'mkpath'; 10use Jifty::Config (); 11use Jifty::YAML (); 12use Locale::Maketext::Extract (); 13use File::Find::Rule (); 14use MIME::Types (); 15our $MIME = MIME::Types->new(); 16our $LMExtract = Locale::Maketext::Extract->new( 17 # Specify which parser plugins to use 18 plugins => { 19 # Use Perl parser, process files with extension .pl .pm .cgi 20 'Locale::Maketext::Extract::Plugin::PPI' => ['pm','pl'], 21 'tt2' => [ ], 22 'perl' => ['pl','pm','js','json'], 23 'mason' => [ ] , 24 }, 25 verbose => 1, 26); 27 28use constant USE_GETTEXT_STYLE => 1; 29 30__PACKAGE__->mk_accessors(qw/language/); 31 32### Help is below in __DATA__ section 33 34=head2 options 35 36Returns a hash of all the options this script takes. (See the usage message for details) 37 38=cut 39 40sub options { 41 my $self = shift; 42 return ( 43 $self->SUPER::options, 44 'l|language=s' => 'language', 45 'dir=s@' => 'directories', 46 'podir=s' => 'podir', 47 'js' => 'js', 48 'template_name=s' => 'template_name', 49 ) 50} 51 52 53=head2 run 54 55Runs the "update_catalogs" method. 56 57=cut 58 59 60sub run { 61 my $self = shift; 62 return if $self->print_help; 63 64 Jifty->new(no_handle => 1); 65 66 return $self->_js_gen if $self->{js}; 67 68 $self->update_catalogs; 69} 70 71sub _js_gen { 72 my $self = shift; 73 my $static_handler = Jifty::View::Static::Handler->new; 74 my $logger =Log::Log4perl->get_logger("main"); 75 for my $file ( @{ Jifty::Web->javascript_libs } ) { 76 next if $file =~ m/^ext/; 77 next if $file =~ m/^yui/; 78 next if $file =~ m/^rico/; 79 my $path = $static_handler->file_path( File::Spec->catdir( 'js', $file ) ) or next; 80 81 $logger->info("Extracting messages from '$path'"); 82 83 $LMExtract->extract_file( $path ); 84 } 85 86 $LMExtract->set_compiled_entries; 87 $LMExtract->compile(USE_GETTEXT_STYLE); 88 89 Jifty::I18N->new; 90 mkpath ['share/web/static/js/dict']; 91 for my $lang (Jifty::I18N->available_languages) { 92 my $file = "share/web/static/js/dict/$lang.json"; 93 $logger->info("Generating $file"); 94 open my $fh, '>', $file or die "$file: $!"; 95 96 no strict 'refs'; 97 print $fh 98 Jifty::JSON::encode_json( { map { my $text = ${"Jifty::I18N::".$lang."::Lexicon"}{$_}; 99 defined $text ? ( $_ => $text ) : () } 100 keys %{$LMExtract->lexicon} } ); 101 } 102} 103 104=head2 _check_mime_type FILENAME 105 106This routine returns a mimetype for the file C<FILENAME>. 107 108=cut 109 110sub _check_mime_type { 111 my $self = shift; 112 my $local_path = shift; 113 my $mimeobj = $MIME->mimeTypeOf($local_path); 114 my $mime_type = ($mimeobj ? $mimeobj->type : "unknown"); 115 return if ( $mime_type =~ /^image/ ); 116 return 1; 117} 118 119=head2 update_catalogs 120 121Extracts localizable messages from all files in your application, finds 122all your message catalogs and updates them with new and changed messages. 123 124=cut 125 126sub update_catalogs { 127 my $self = shift; 128 my $podir = $self->{'podir'} || Jifty->config->framework('L10N')->{'PoDir'}; 129 130 $self->extract_messages; 131 $self->update_catalog( File::Spec->catfile( 132 $podir, $self->pot_name . ".pot" 133 ) ); 134 135 if ($self->{'language'}) { 136 $self->update_catalog( File::Spec->catfile( 137 $podir, $self->{'language'} . ".po" 138 ) ); 139 return; 140 } 141 142 my @catalogs = grep !m{(^|/)\.svn/}, File::Find::Rule->file->name('*.po')->in( 143 $podir 144 ); 145 146 unless ( @catalogs ) { 147 $self->log->error("You have no existing message catalogs."); 148 $self->log->error("Run `jifty po --language <lang>` to create a new one."); 149 $self->log->error("Read `jifty po --help` to get more info."); 150 return 151 } 152 153 foreach my $catalog (@catalogs) { 154 $self->update_catalog( $catalog ); 155 } 156} 157 158=head2 update_catalog FILENAME 159 160Reads C<FILENAME>, a message catalog and integrates new or changed 161translations. 162 163=cut 164 165sub update_catalog { 166 my $self = shift; 167 my $translation = shift; 168 my $logger =Log::Log4perl->get_logger("main"); 169 $logger->info( "Updating message catalog '$translation'"); 170 171 $LMExtract->read_po($translation) if ( -f $translation && $translation !~ m/pot$/ ); 172 173 my $orig_lexicon; 174 175 # Reset previously compiled entries before a new compilation 176 $LMExtract->set_compiled_entries; 177 $LMExtract->compile(USE_GETTEXT_STYLE); 178 179 if ($self->_is_core && !$self->{'template_name'}) { 180 $LMExtract->write_po($translation); 181 } 182 else { 183 $orig_lexicon = $LMExtract->lexicon; 184 my $lexicon = { %$orig_lexicon }; 185 186 # XXX: cache core_lm 187 my $core_lm = Locale::Maketext::Extract->new(); 188 Locale::Maketext::Lexicon::set_option('allow_empty' => 1); 189 $core_lm->read_po( File::Spec->catfile( 190 Jifty->config->framework('L10N')->{'DefaultPoDir'}, 'jifty.pot' 191 )); 192 Locale::Maketext::Lexicon::set_option('allow_empty' => 0); 193 for (keys %{ $core_lm->lexicon }) { 194 next unless exists $lexicon->{$_}; 195 # keep the local entry overriding core if it exists 196 delete $lexicon->{$_} unless length $lexicon->{$_}; 197 } 198 $LMExtract->set_lexicon($lexicon); 199 200 $LMExtract->write_po($translation); 201 202 $LMExtract->set_lexicon($orig_lexicon); 203 } 204} 205 206 207=head2 extract_messages 208 209Find all translatable messages in your application, using 210L<Locale::Maketext::Extract>. 211 212=cut 213 214sub extract_messages { 215 my $self = shift; 216 # find all the .pm files in @INC 217 my @files = File::Find::Rule->file->in( @{ $self->{directories} || 218 [ Jifty->config->framework('Web')->{'TemplateRoot'}, 219 'lib', 'bin'] } ); 220 221 my $logger =Log::Log4perl->get_logger("main"); 222 foreach my $file (@files) { 223 next if $file =~ m{(^|/)[\._]svn/}; 224 next if $file =~ m{\~$}; 225 next if $file =~ m{\.pod$}; 226 next unless $self->_check_mime_type($file ); 227 $logger->info("Extracting messages from '$file'"); 228 $LMExtract->extract_file($file); 229 } 230 231} 232 233=head2 print_help 234 235Prints out help for the package using pod2usage. 236 237If the user specified --help, prints a brief usage message 238 239If the user specified --man, prints out a manpage 240 241=cut 242 243sub print_help { 244 my $self = shift; 245 return 0 unless $self->{help} || $self->{man}; 246 247 # Option handling 248 my $docs = \*DATA; 249 pod2usage( -exitval => 1, -input => $docs ) if $self->{help}; 250 pod2usage( -exitval => 0, -verbose => 2, -input => $docs ) 251 if $self->{man}; 252 return 1; 253} 254 255 256sub _is_core { 257 return 1 if Jifty->config->framework('ApplicationName') eq 'JiftyApp'; 258} 259 260=head2 pot_name 261 262Returns the name of the po template. 263 264=cut 265 266sub pot_name { 267 my $self = shift; 268 return $self->{'template_name'} if $self->{'template_name'}; 269 return 'jifty' if $self->_is_core; 270 return lc Jifty->config->framework('ApplicationName'); 271} 272 2731; 274 275__DATA__ 276 277=head1 NAME 278 279Jifty::Script::Po - Extract translatable strings from your application 280 281=head1 SYNOPSIS 282 283 jifty po --language <lang> Creates a <lang>.po file for translation 284 jifty po Updates all existing po files 285 286 Options: 287 --language Language to deal with 288 --dir Additional directories to extract from 289 --js Generate json files from the current po files 290 291 --help brief help message 292 --man full documentation 293 294=head1 OPTIONS 295 296=over 8 297 298=item B<--language> 299 300This script an option, C<--language>, which is optional; it is the 301name of a message catalog to create. 302 303=item B<--dir> 304 305Specify explicit directories to extract from. Can be used multiple 306times. The default directories will not be extracted if you use this option. 307 308=item B<--template_name> 309 310Specify the name of the po template. Default to the lower-cased application name. 311 312=item B<--podir> 313 314Specify the directory of the po templates. 315 316=item B<--js> 317 318If C<--js> is given, other options are ignored and the script will 319generate json files for each language under 320F<share/web/static/js/dict> from the current po files. Before doing 321so, you might want to run C<jifty po> with C<--dir share/web/static/js> 322to include messages from javascript in your po files. 323 324=item B<--help> 325 326Print a brief help message and exits. 327 328=item B<--man> 329 330Prints the manual page and exits. 331 332=back 333 334=head1 DESCRIPTION 335 336Extracts message catalogs for your Jifty app. When run, Jifty will update 337all existing message catalogs, as well as create a new one if you specify 338a --language option. 339 340=cut 341 342