view release on metacpan or search on metacpan
Translation Status:
en_US: [ ] 0% (0/8)
zh_TW: [====== ] 12% (1/8)
## Auto Translation
Auto translate via Google Translate REST API:
Default backend is google translate REST API, This will translate zh\_TW.po file and translate msgid (en\_US)
to msgstr (zh\_TW):
$ po auto zh_TW --from en_US
$ po auto zh_CN --from en_US --to zh_CN
$ po auto zh_CN --from en_US --overwrite --prompt
$ po auto --backend google-rest --from en\_US --to zh\_TW
/api/options
/api/entry/list[/{lang}]
/api/entry/unsetlist[/{lang}]
/api/entry/get/{id}
/api/entry/set/{id}/{msgstr}
/api/entry/insert/{lang}/{msgid}/{msgstr}
### For PHP Developers
If you are using gettext extension for your application,
You should use --locale option to generate locale structure.
And `maketext/l10n.php` file for l10n helper class and functions.
## **TODO**
# TODO
* Add Apply command to apply messages in file with po string.
use this command to generate static files.
* global po database by DBIx::Class (sqlite/mysql/pg backend).
* merge/update via global po database.
* provide a web service for managing po files.
* hook google translate plugin to web editor textarea.
* parse msgid from database
$ po parsedb -hlocalhost -uroot -p1234 dbname table_name column_name
* default charset option.
x mo file
x locale structure
lib/App/I18N.pm view on Meta::CPAN
Translation Status:
en_US: [ ] 0% (0/8)
zh_TW: [====== ] 12% (1/8)
=head2 Auto Translation
Auto translate via Google Translate REST API:
Default backend is google translate REST API, This will translate zh\_TW.po file and translate msgid (en\_US)
to msgstr (zh\_TW):
$ po auto zh_TW --from en_US
$ po auto zh_CN --from en_US --to zh_CN
$ po auto zh_CN --from en_US --overwrite --prompt
$ po auto --backend google-rest --from en\_US --to zh\_TW
lib/App/I18N/Command/Auto.pm view on Meta::CPAN
sub options {
(
'f|from=s' => 'from',
't|to=s' => 'to',
'backend=s' => 'backend',
'locale' => 'locale',
'verbose' => 'verbose',
'msgstr' => 'msgstr', # translate from existing msgstr instead of translating from msgid.
'overwrite' => 'overwrite', # overwrite existing msgstr
'p|prompt' => 'prompt',
)
}
sub run {
my ( $self , $lang ) = @_;
my $logger = $self->logger();
lib/App/I18N/Command/Auto.pm view on Meta::CPAN
$logger->info( "Translating: $from_lang_s => $to_lang_s" );
$logger->info( "Initialing REST::Google" );
REST::Google::Translate->http_referer('http://google.com');
binmode STDERR, ":utf8";
NEXT_MSGID:
for my $i ($ext->msgids()) {
my $msgid = $i;
my $msgstr = $ext->msgstr( $i );
# skip if no msgstr and no overwrite.
next if $msgstr && ! $self->{overwrite} && ! $self->{msgstr};
# translate from msgstr
$i = $msgstr if $msgstr && $self->{msgstr};
$logger->info( "********" );
$logger->info( " msgid: $msgid");
$logger->info( " msgstr: $msgstr" ) if $msgstr;
$logger->info( " tranlating from msgstr" ) if $self->{msgstr};
$logger->info( " tranlating from msgid" ) if ! $self->{msgstr};
my $retry = 1;
while($retry--) {
my $res;
eval {
$res = REST::Google::Translate->new(
q => $i,
langpair => $from_lang_s . '|' . $to_lang_s );
};
lib/App/I18N/Command/Gen.pm view on Meta::CPAN
Generate inline i18n dictionary dictionary:
For Perl:
# This is auto-generated by App::I18N
# ------------------------------
package {{app}}::dict::en;
our $DICT = {
"msgid" => "msgstr",
...
};
package {{app}}::dict::zh_tw;
our $DICT = {
"msgid" => "msgstr",
....
};
...
For PHP:
en.php:
<?php
$DICT = array(
"msgid" => "msgstr"
....
);
?>
....
For JSON
en.json:
{
"msgid..." : "msgstr"
...
};
zh_tw.json:
{
....
};
Static JS
lib/App/I18N/DB.pm view on Meta::CPAN
my $self = shift;
$self->dbh->disconnect();
}
sub init_schema {
my ($self) = shift;
$self->dbh->do( qq|
create table po_string (
id INTEGER PRIMARY KEY AUTOINCREMENT,
lang TEXT,
msgid TEXT,
msgstr TEXT,
updated_on timestamp,
updated_by varchar(120));
|);
}
# by {id}
sub get_entry {
my ( $self, $id ) = @_;
my $sth = $self->dbh->prepare(qq{ select * from po_string where id = ? });
lib/App/I18N/DB.pm view on Meta::CPAN
return $ret;
}
sub last_id {
my $self = shift;
}
sub insert {
my ( $self , $lang , $msgid, $msgstr ) = @_;
$msgstr = decode_utf8( $msgstr );
my $sth = $self->dbh->prepare(
qq| INSERT INTO po_string ( lang , msgid , msgstr ) VALUES ( ? , ? , ? ); |);
$sth->execute( $lang, $msgid, $msgstr );
}
sub find {
my ( $self, $lang , $msgid ) = @_;
my $sth = $self->dbh->prepare(qq| SELECT * FROM po_string WHERE lang = ? AND msgid = ? LIMIT 1;|);
$sth->execute( $lang, $msgid );
my $data = $sth->fetchrow_hashref();
$sth->finish;
return $data;
}
sub get_unset_entry_list {
my ($self, $lang ) = @_;
my $sth;
lib/App/I18N/DB.pm view on Meta::CPAN
}
sub _entry_sth_to_list {
my ($self , $sth) = @_;
my @result;
while( my $row = $sth->fetchrow_hashref ) {
push @result, {
id => $row->{id},
lang => $row->{lang},
msgid => $row->{msgid},
msgstr => $row->{msgstr},
};
}
return \@result;
}
sub get_langlist {
my $self = shift;
my $sth = $self->dbh->prepare("select distinct lang from po_string;");
lib/App/I18N/DB.pm view on Meta::CPAN
return keys %$hashref;
}
sub write_to_pofile {
# XXX:
}
sub import_lexicon {
my ( $self , $lang , $lex ) = @_;
while ( my ( $msgid, $msgstr ) = each %$lex ) {
$self->insert( $lang , $msgid , $msgstr );
}
}
sub import_po {
my ( $self, $lang, $pofile ) = @_;
my $lme = App::I18N->lm_extract;
$lme->read_po($pofile) if -f $pofile && $pofile !~ m/pot$/;
$self->import_lexicon( $lang , $lme->lexicon );
}
lib/App/I18N/DB.pm view on Meta::CPAN
my $lexicon;
return $lexicon;
}
sub export_po {
my ( $self , $lang , $pofile ) = @_;
my $list = $self->get_entry_list( $lang );
my $lexicon = {
map { $_->{msgid} => encode_utf8 $_->{msgstr}; } @$list
};
my $lme = App::I18N->lm_extract;
$lme->set_lexicon( $lexicon );
$lme->write_po($pofile);
}
=pod
package MsgEntry;
use Any::Moose;
use JSON::XS;
use overload
'""' => \&to_string,
'%{}' => \&to_hash;
has id => ( is => 'rw', isa => 'Int' );
has lang => ( is => 'rw' , isa => 'Str' );
has msgid => ( is => 'rw' , isa => 'Str' );
has msgstr => ( is => 'rw' , isa => 'Str' );
sub to_hash {
my $self = shift;
return (
id => $self->id,
lang => $self->lang,
msgid => $self->msgid,
msgstr => $self->msgstr,
);
}
sub to_string {
my $self = shift;
return encode_json( { $self->to_hash } );
}
=cut
lib/App/I18N/Web/Handler.pm view on Meta::CPAN
$lme->set_lexicon($o_lexicon);
$lme->write_po($pofile);
}
sub post {
my ($self,$path) = @_;
my $params = $self->request->parameters->mixed;
use List::MoreUtils qw(zip);
my $pofile = $params->{pofile};
my %lexicon = zip @{ $params->{'msgid[]'} }
,@{ $params->{'msgstr[]'} };
$self->update_po( $pofile , \%lexicon );
$self->finish({ success => 1 });
}
sub get {
my ( $self, $path ) = @_;
$path ||= "/";
$self->write( Template::Declare->show( $path, $self ) );
lib/App/I18N/Web/Handler.pm view on Meta::CPAN
/api/podata
/api/entry/list[/{lang}]
/api/entry/unsetlist[/{lang}]
/api/entry/get/{id}
/api/entry/set/{id}/{msgstr}
/api/entry/insert/{lang}/{msgid}/{msgstr}
=cut
sub post {
my ( $self, $path ) = @_;
my ( $p1, $p2, @parts ) = split /\//, $path;
my $pms = $self->request->parameters->mixed;
if ( $p1 eq 'entry' && $p2 eq 'set' ) {
my ( $id, $msgstr ) = ( $pms->{id}, $pms->{msgstr} );
lib/App/I18N/Web/Handler.pm view on Meta::CPAN
return $self->write($langdata);
}
elsif ( $p1 eq 'skip_session' ) {
$self->application->skip_session( 1 );
return $self->write({ success => 1 });
}
elsif ( $p1 eq 'options' ) {
return $self->write( $self->application->options );
}
elsif ( $p1 eq 'entry' && $p2 eq 'insert' ) {
my ( $lang, $msgid, $msgstr ) = @parts;
return $self->write( { error => 'Require language, msgid or msgstr' } ) unless $msgid and $msgstr and $lang;
$msgstr = decode_utf8($msgstr);
my $existed = $self->db->find( $lang, $msgid );
return $self->write( { error => 'MsgID Exists', record => $existed } ) if $existed;
$self->application->db->insert( $lang, $msgid, $msgstr );
return $self->write( { success => 1, recordid => $self->application->db->dbh->last_insert_id( undef, undef, 'po_string', 'msgid' ) } );
}
elsif ( $p1 eq 'entry' && $p2 eq 'unsetlist' ) {
my $lang = shift @parts;
my $entrylist;
$entrylist = $self->application->db->get_unset_entry_list($lang);
return $self->write( { entrylist => $entrylist } );
}
elsif ( $p1 eq 'entry' && $p2 eq 'list' ) {
my $lang = shift @parts;
my $entrylist;
lib/App/I18N/Web/View.pm view on Meta::CPAN
$logger->info( "$translation doesnt exist." );
}
my $LME = App::I18N->lm_extract();
$LME->read_po( $translation ) if -f $translation;
my $lex = $LME->lexicon;
h3 { "Po Web Server: " . $translation };
# load all po msgid and msgstr
form { { method is 'post' }
div {
outs "Editing po file: " . $translation;
}
input { { type is 'hidden', name is 'pofile' , value is $translation } };
div { { class is 'msgitem' }
div { { class is 'msgid column-header' } _("MsgID") }
div { { class is 'msgstr column-header' } _("MsgStr") }
};
# XXX: a better way to read po file ? not to parse every time.
while( my ($msgid,$msgstr) = each %$lex ) {
div { { class is 'msgitem' }
div { { class is 'msgid' }
textarea { { name is 'msgid[]' };
outs decode_utf8 $msgid;
};
}
div { { class is 'msgstr' }
textarea { { name is 'msgstr[]' };
outs decode_utf8 $msgstr;
};
}
}
lib/App/I18N/Web/View.pm view on Meta::CPAN
template '/entry_edit' => sub {
div { { id is 'current-message' }
div { { class is 'navbar' }
input { { type is 'button' , class is 'prev-message' , value is 'Previous' } };
input { { type is 'button' , class is 'skip-message' , value is 'Next' } };
input { { type is 'button' , class is 'next-message' , value is 'Save and Next' } };
}
div { { id is 'message-content' }
div { { id is 'current-lang' } }
div { { id is 'current-msgid' } }
textarea { { id is 'current-msgstr' , rows is 6 , cols is 60 , tabindex is 1 } }
};
div { { class is 'navbar' }
input { { type is 'button' , class is 'prev-message' , value is 'Previous' , tabindex is 4 } };
input { { type is 'button' , class is 'skip-message' , value is 'Next' , tabindex is 3 } };
input { { type is 'button' , class is 'next-message' , value is 'Save and Next' , tabindex is 2 } };
}
}
};
share/static/app.css view on Meta::CPAN
#langlist li { display: inline; margin: 5px; background: #36D986; color: #007439; padding: 6px; }
#langlist li:hover { text-decoration: underline; background: #eee; }
#current-lang { color: #999; margin: 10px; }
#current-message { margin: 10px 13%; margin-top: 20px; }
.navbar input { font-size: 16px; padding: 6px; margin-right: 10px; border: 1px solid #ddd; background: #fff; }
.navbar input:hover { background: #eee; }
#current-msgstr,
#current-msgid {
margin: 10px; padding: 8px;
width: 70%;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
border-left: 0px;
border-right: 0px;
color: #333;
font-family: Serif;
font-size: 18px;
}
share/static/app.css view on Meta::CPAN
textarea,input {
font-size: 22px;
padding: 3px;
background: #ded;
color: #666;
}
textarea:focus,
input:focus { background: #99CCD2;color: #333; font-weight: bold; }
.msgid { width: 300px; float: left; padding:6px; }
.msgstr { width: 400px; float: left; padding:6px; }
.msgid textarea,
.msgid input { width: 300px; }
.msgstr textarea,
.msgstr input { width: 400px; }
.savethis { float: left; padding: 6px; }
.column-header { color: #66A3D2; }
.column-header { color: #66A3D2; }
.msgitem { clear: both; }
share/static/app.js view on Meta::CPAN
function writeAll(e) {
var msgids = jQuery(e).parents("form").find( "*[name=msgid[]]" )
.map( function(i,n) {
return jQuery(n).val();
}).get();
var msgstrs = jQuery(e).parents("form").find( "textarea[name=msgstr[]]" )
.map( function(i,n) {
return jQuery(n).val();
}).get();
var pofile = jQuery(e).parents("form").find( "input[name=pofile]" ).val();
//console.log( pofile , msgids , msgstrs );
jQuery.ajax({
url: '/',
type: 'post',
data: {
'msgid[]': msgids,
'msgstr[]': msgstrs,
'pofile': pofile
},
success: function( ) {
jQuery.jGrowl( "Updated" );
}
});
return false;
}
share/static/app.js view on Meta::CPAN
});
},
update: function(entry) {
var lang = this.current_lang;
if(!entry)
return;
$('#current-lang').html( "Current Language: " + this.lg.name );
$('#current-msgid').html( entry.msgid );
$('#current-msgstr').val( entry.msgstr );
$('#current-id').val( entry.id );
var g_el = $('#google-translation');
var g_el_text = g_el.find('div.text');
if( g_el.get(0) ) {
g_el.show();
g_el_text.html( "Translating..." );
}
else {
g_el = $('<div/>').attr('id','google-translation').append( $('<div/>').addClass('text') );
$('#current-msgstr').after( g_el );
g_el = $('#google-translation');
g_el_text = g_el.find('div.text');
}
if( typeof google != "undefined" ) {
google.language.translate( entry.msgid , "", lang , function(result) {
if( result.error ) {
g_el_text.html( "Error:" + result.error.message );
$.jGrowl( result.error.message , { header: 'Google Translation' , theme: 'error' , sticky: 1 } );
} else {
g_el_text.html( result.translation );
var apply = $('<a/>').attr( { href: '#', tabindex: 5 } ).html( 'Apply' ).addClass('apply').click( function(e) {
$('#current-msgstr').val( result.translation );
g_el.fadeOut('slow');
});
g_el_text.append( apply );
share/static/jquery.jgrowl.css view on Meta::CPAN
/** Cross Browser Styling **/
div.center div.jGrowl-notification, div.center div.jGrowl-closer {
margin-left: auto;
margin-right: auto;
}
div.jGrowl div.jGrowl-notification, div.jGrowl div.jGrowl-closer {
background-color: #000;
opacity: .85;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=85)";
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=85);
zoom: 1;
width: 235px;
padding: 10px;
margin-top: 5px;
margin-bottom: 5px;
font-family: Tahoma, Arial, Helvetica, sans-serif;
font-size: 1em;
text-align: left;
display: none;
-moz-border-radius: 5px;
my $db = App::I18N::DB->new();
ok( $db );
$db->insert( 'zh-tw', 'test' , '測試' );
$entry = $db->find( 'zh-tw', 'test' );
ok( $entry );
ok( $entry->{id} );
ok( $entry->{msgid} );
ok( $entry->{lang} );
ok( $entry->{msgstr} );
is( $entry->{msgstr} , '測試' );
my $entries = $db->get_entry_list( 'zh-tw' );
ok( @$entries );
is( scalar(@$entries) , 1 );