1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
|
package JWebmail::Plugin::I18N2;
use Mojo::Base 'Mojolicious::Plugin';
use List::Util 'any';
use JWebmail::Plugin::I18N2::Role;
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 { require JWebmail::Plugin::I18N2::Maketext; 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} // {}}
);
die "translator does not consume role JWebmail::Plugin::I18N2::Role"
unless $t->DOES('JWebmail::Plugin::I18N2::Role');
{
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 for Mojolicious
=head1 SYNOPSIS
my $router = $app->plugin('I18N2', {
languages => [qw(en de es)],
default_language => 'de',
});
# in your controller
$c->l('hello') # 'el'
# in your templates
<%= l 'hello' %>
# reads the language of the first url component
example.com/de/myroute # $c->stash('lang') eq 'de'
example.com/myroute # $c->stash('lang') eq $defaultLanguage
# adjusts url depending on the currently selected language
# on example.com/de/myroute
url_for('my_other_route') #=> example.com/de/my_other_route
# you can pass the language explicitly as well
url_for('my_other_route', lang => 'es') #=> example.com/es/my_other_route
=head1 DESCRIPTION
L<JWebmail::Plugin::I18N2> provides I18N support.
This is a complete re-implementation of JWebmail::Plugin::I18N that allows for
a composable matcher and custom translator.
The language will be taken from the first path segment of the url.
Be careful with colliding routes.
This Plugin only works with Mojolicious version 8.64 or higher.
=head1 RETURNS
The plugin returns an initial route that is meant to be used as the root
for all endpoints that shall be translatable.
=head1 OPTIONS
=head2 default_language
The default language when no other information is provided.
=head2 languages
List of allowed languages.
As a default, files of the pattern "$lang.lang" will be looked for.
=head2 translator
This is a sub that returns an object that C<DOES> C<JWebmail::Plugin::I18N2::Role>
when given a HASH or HASHREF containing a 'log' and a 'default_language'.
A custom implementation that uses simple files of key-value pairs is provided
as well as one that uses L<Locale::Maketext>.
An implementation for gettext is planned.
Default is Maketext for now.
=head1 STASH
=head2 lang
This value dictates what languages is actually used.
You may change this before rendering the view.
=head2 default_language
The set default language.
=head2 languages
A list of all loaded languages.
=head1 HELPERS
=head2 l
This is used for your translations.
$c->l('hello')
=head1 EXTENDS
=head2 Mojolicious::Routes::Match->path_for
This plugin creates a new dynamic class for every Match class that is used
in the Mojolicious routing mechanism that extends the C<path_for> method with
one that injects the 'lang' option.
=cut
|