package JWebmail::Plugin::I18N2; use Mojo::Base 'Mojolicious::Plugin'; use List::Util 'any'; package JWebmail::Plugin::I18N2::Maketext { use JWebmail::I18N; use File::Basename 'fileparse'; sub new { my $class = shift; my $conf = @_ == 1 ? shift : {@_}; my $lexica = $conf->{directory} || [fileparse(__FILE__)]->[1] . '../I18N'; my @languages = keys %{$conf->{languages} // {}}; unless (@languages) { use autodie; opendir(my $dh, $lexica); my @res = grep { /\.pm$/ && -f "$lexica/$_" } readdir $dh; closedir($dh); @languages = map { scalar fileparse $_, '.pm' } @res; @languages = map { my ($l, $c) = split '_', $_, 2; $c ? "$l-\U$c" : $l } @languages; } if (my $dl = $conf->{default_language}) { push @languages, $dl; }; my $self = {}; for (@languages) { $self->{$_} = JWebmail::I18N->get_handle($_) || die "unable to load language $_"; } if (my $l = $conf->{log}) { $_->logger($l) for values %$self; } return bless $self, $class; } sub languages { my $self = shift; if (@_) { return exists $self->{$_[0] || ''}; } return wantarray ? sort keys $self->%* : scalar keys $self->%*; } sub translate { my $self = shift; my $lang = shift; my $phrase = shift; return $self->{$lang}->maketext($phrase, @_); } } package JWebmail::Plugin::I18N2::Translator { use Mojo::File; use Config::Tiny; sub new { my $cls = shift; my $conf = @_ == 1 ? shift : {@_}; my $self = bless {}, $cls; $self->_log($conf->{log} || sub { @_ = $_[0]->() if ref $_[0] eq 'CODE'; local $" = ' '; warn "@_" }); my @languages = keys %{$conf->{languages} // {}}; unless (@languages) { @languages = map { s|^.*/(..)\.lang$|$1|r } glob("'$conf->{directory}/*.lang'"); } # load languages for my $l (@languages) { if (my $dict = __loadi18n($conf->{directory}, $l)) { $self->{$l} = $dict; } } return $self; } sub languages { my $self = shift; if (@_) { return exists $self->{$_[0] || ''}; } return wantarray ? sort keys $self->%* : scalar keys $self->%*; } sub translate { my $self = shift; my $lang = shift; my $word = shift; my $res = $self->{$lang}{$word}; unless ($res) { local $" = ' '; $self->_log->("missing translation for $lang:'$word' @{[ caller(1) ]}[0..2]"); } if (@_) { $res = sprintf($res, @_); } return $res; } sub _log { my $self = shift; if (@_) { $self->{_log} = $_[0]; return $self; } else { return $self->{_log}; } } sub __loadi18n { my $langsubdir = shift; my $lang = shift; my $langFile = "$langsubdir/$lang.lang"; my $TXT; if ( -f $langFile ) { $TXT = Config::Tiny->read($langFile, 'utf8')->{'_'}; if ($@) { die "error reading file $langFile: $@"; } } return $TXT; } } package JWebmail::Plugin::I18N2::Match::Role { use Mojo::Base -role; has '_i18n2_stash'; # cases: ($) (\%) ($, \%) (%) ($, %) sub __i18n2_add_option_no_override { my $key = shift; my $value = shift; if (@_ == 1 && ref $_[0] eq 'HASH') { # handles (\%) $_[0]->{$key} ||= $value; } elsif (@_ == 2 && ref $_[1] eq 'HASH') { # handles ($, \%) $_[1]->{$key} ||= $value; } elsif (@_ % 2 == 0) { # handles (%) my %opts = @_; $opts{$key} ||= $value; @_ = %opts; } else { # handles ($, %) my ($primary, %opts) = @_; $opts{$key} ||= $value; @_ = ($primary, %opts); } return @_; } around 'path_for' => sub { my $orig = shift; my $self = shift; if (my $lang = $self->_i18n2_stash->{lang}) { @_ = __i18n2_add_option_no_override(lang => $lang, @_); } $orig->($self, @_) }; } sub register { my ($self, $app, $conf) = @_; $conf //= {}; my $i18n_log = $app->log->context('[' . __PACKAGE__ . ']'); my $translator = $conf->{translator} || sub { JWebmail::Plugin::I18N2::Maketext->new(@_) }; my $defaultLang = $conf->{default_language} || 'en'; my $fileLocation = $conf->{directory}; # ? Mojo::File->new($conf->{directory}) : $app->home->child('lang'); my $t = $translator->( default_language => $defaultLang, directory => $fileLocation, log => sub { $i18n_log->warn(@_) }, %{$conf->{rest} // {}} ); { local $" = ','; $i18n_log->info("loaded languages (@{[$t->languages]})"); unless (any { $defaultLang eq $_ } $t->languages) { die "default language '$defaultLang' not loaded"; } if (keys $conf->{languages}->%* > $t->languages) { $i18n_log->warn("missing languages"); } } $app->defaults( default_language => $defaultLang, languages => [$t->languages], ); # add translator as helper $app->helper(l => sub { my $c = shift; my $word = shift; my $lang = $c->stash('lang'); return $t->translate($lang, $word, @_); }); # modify incoming and generated urls $app->hook(before_routes => sub { my $c = shift; unshift @{ $c->req->url->path->parts }, '' unless $t->languages($c->req->url->path->parts->[0]); my $ext_match = $c->match->with_roles('JWebmail::Plugin::I18N2::Match::Role'); $ext_match->_i18n2_stash($c->stash); $c->match($ext_match); }); return $app->routes->any('/:lang' => {lang => $defaultLang}); } 1 __END__ =encoding utf8 =head1 NAME JWebmail::Plugin::I18N2 - Custom Made I18N Support an alternative to JWebmail::Plugin::I18N =head1 SYNOPSIS $app->plugin('I18N2', { languages => [qw(en de es)], default_language => 'de', directory => 'path/to/language/files/', }) # in your controller $c->l('hello') # in your templates <%= l 'hello' %> @@ de.lang login = anmelden userid = nuzerkennung passwd = passwort failed = fehlgeschlagen about = über example.com/de/myroute # $c->stash('lang') eq 'de' example.com/myroute # $c->stash('lang') eq $defaultLanguage # on example.com/de/myroute url_for('my_other_route') #=> example.com/de/my_other_route url_for('my_other_route', lang => 'es') #=> example.com/es/my_other_route =head1 DESCRIPTION L provides I18N support. The language will be taken from the first path segment of the url. Be carefult with colliding routes. Mojolicious::Controller::url_for is patched so that the current language will be kept for router named urls. This Plugin only works with Mojolicious version 8.64 or higher. =head1 OPTIONS =head2 default_language The default language when no other information is provided. =head2 directory Directory to look for language files. =head2 languages List of allowed languages. As a default, files of the pattern "$lang.lang" will be looked for. =head1 HELPERS =head2 l This is used for your translations. $c->l('hello') =cut