summaryrefslogtreecommitdiff
path: root/lib/JWebmail/Plugin/I18N2.pm
blob: e9e485fb1c57a0c326c2a1b21c9eedcc8931007c (plain)
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};

    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->debug("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