File Coverage

blib/lib/Catalyst/Plugin/I18N/DBI.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::I18N::DBI;
2              
3 1     1   63311 use strict;
  1         4  
  1         1144  
4 1     1   9 use warnings;
  1         2  
  1         43  
5              
6 1     1   6 use base qw(Locale::Maketext);
  1         7  
  1         2481  
7              
8 1     1   64214 use DBI;
  1         93664  
  1         619  
9 1     1   1034 use Moose::Role;
  0            
  0            
10             use I18N::LangTags ();
11             use I18N::LangTags::Detect;
12             use Scalar::Util ();
13              
14             use Locale::Maketext::Lexicon v0.2.0;
15              
16             use version; our $VERSION = qv("0.2.5");
17              
18             =head1 NAME
19              
20             Catalyst::Plugin::I18N::DBI - DBI based I18N for Catalyst
21              
22             =head1 SYNOPSIS
23              
24             use Catalyst 'I18N::DBI';
25              
26             print $c->loc('Hello Catalyst');
27              
28             Or in your Mason code:
29              
30             <% $c->loc('Hello [_1]', 'Catalyst') %>
31              
32             Or in your TT code (with macro):
33              
34             [% MACRO l(text, args) BLOCK;
35             c.loc(text, args);
36             END; %]
37              
38             [% l('Hello Catalyst') %]
39             [% l('Hello [_1]', 'Catalyst') %]
40             [% l('lalala[_1]lalala[_2]', ['test', 'foo']) %]
41              
42             =head1 DESCRIPTION
43              
44             Unlike L<Catalyst::Plugin::I18N::DBIC> this plugin isn't based on any other Catalyst plugin.
45             It makes direct use of L<Locale::Maketext::Lexicon> and L<Locale::Maketext::Lexicon::DBI>.
46              
47             Lexicon texts are held in a database table where you can have several lexicons
48             which are separated by the 'lex' column. See L<Locale::Maketext::Lexicon::DBI> for more
49             information about the table definition. All specified lexicons are loaded into memory
50             at startup, so we don't need to fetch the lexicon entries every time we need them.
51              
52             Please read this document and L<Catalyst::Plugin::I18N::DBIC>'s POD carefully before
53             deciding which module to use in your case.
54              
55             =head2 CONFIGURATION
56              
57             In order to be able to connect to the database, this plugin needs some configuration,
58             for example:
59              
60             __PACKAGE__->config(
61             'I18N::DBI' => {
62             dsn => 'dbi:Pg:dbname=postgres',
63             user => 'pgsql',
64             password => '',
65             languages => [qw(de en)],
66             lexicons => [qw(*)],
67             lex_class => 'DB::Lexicon',
68             default_lang => 'de',
69             },
70             );
71              
72             =over
73              
74             =item dsn
75              
76             This is the Data Source Name which will be passed to the C<connect> method of L<DBI>.
77             See L<DBI> for more information about DSN syntax.
78              
79             =item user
80              
81             Name of a database user with read B<and> write access to the lexicon table
82             and dependent sequences. (When C<fail_with> is set to C<0>, the user doesn't
83             need to have write access.)
84              
85             =item password
86              
87             The password for the database user.
88              
89             =item languages
90              
91             An array reference with language names that shall be loaded into memory. Basically,
92             this is the content of the C<lang> column.
93              
94             =item lex_class
95              
96             Defines the model for the lexicon table.
97              
98             =item fail_with
99              
100             Boolean indicating whether to use the C<fail_with> function or not. Defaults to true.
101             See L</FAQ> for details.
102              
103             =item default_lang
104              
105             Default language which is chosen when no browser accepted language is available.
106              
107             =back
108              
109             =head1 METHODS
110              
111             =head2 loc
112              
113             Localize text:
114              
115             print $c->loc('Welcome to Catalyst, [_1]', 'Matt');
116              
117             =cut
118              
119             sub loc {
120             my $c = shift;
121             my $text = shift;
122             my $args = shift;
123              
124             my $lang_handle;
125             my $handles = $c->config->{'I18N::DBI'}->{handles};
126             foreach (@{ $c->languages }) {
127             if ($lang_handle = $handles->{$_}) {
128             last;
129             }
130             }
131              
132             unless ($lang_handle) {
133             unless ($lang_handle = $handles->{ $c->config->{'I18N::DBI'}->{default_lang} }) {
134             $c->log->fatal("No default language '" . $c->config->{'I18N::DBI'}->{default_lang} . "' available!");
135             return $text;
136             }
137             }
138              
139             my $value;
140             if (ref $args eq 'ARRAY') {
141             $value = $lang_handle->maketext($text, @$args);
142             } else {
143             $value = $lang_handle->maketext($text, $args, @_);
144             }
145              
146             utf8::decode($value);
147             return $value;
148             }
149              
150             =head2 localize
151              
152             Alias method to L</loc>.
153              
154             =cut
155              
156             *localize = \&loc;
157              
158             =head2 languages
159              
160             Contains languages.
161              
162             $c->languages(['de_DE']);
163             print join '', @{ $c->languages };
164              
165             =cut
166              
167             sub languages {
168             my ($c, $languages) = @_;
169              
170             if ($languages) {
171             $c->{languages} = $languages;
172             } else {
173             $c->{languages} ||= [I18N::LangTags::implicate_supers(I18N::LangTags::Detect->http_accept_langs($c->request->header('Accept-Language')))];
174             }
175              
176             return $c->{languages};
177             }
178              
179             =head1 EXTENDED AND INTERNAL METHODS
180              
181             =head2 setup
182              
183             =cut
184              
185             after 'setup_finalize' => sub {
186             my $c = shift;
187              
188             $c->_init_i18n;
189             $c->log->debug("I18N Initialized");
190             };
191              
192             sub _init_i18n {
193             my $c = shift;
194              
195             my $cfg = $c->config->{'I18N::DBI'};
196             my $dbh = DBI->connect($cfg->{dsn}, $cfg->{user}, $cfg->{password}, $cfg->{attr});
197              
198             my $default_lex = $cfg->{lexicons}->[0];
199              
200             my (%handles, %initialized);
201             foreach my $lang (@{ $cfg->{languages} }) {
202             $lang =~ y/_/-/;
203              
204             foreach my $lex (@{ $cfg->{lexicons} }) {
205              
206             unless ($initialized{$lang}) {
207             eval <<"";
208             package ${c}::${lang};
209             no strict;
210             use base 'Locale::Maketext';
211             # Need a dummy key to overlive the optimizer (or similar)!
212             %Lexicon = (dummy => '1');
213              
214             $initialized{$lang} = 1;
215             }
216              
217             eval <<"";
218             package $c;
219             use base 'Locale::Maketext';
220             Locale::Maketext::Lexicon->import(
221             { \$lang => ['DBI' => ['lang' => \$lang, 'lex' => \$lex, dbh => \$dbh]] });
222              
223             if ($@) {
224             $c->log->error(qq|Couldn't initialize I18N for lexicon $lang/$lex, "$@"|);
225             } else {
226             $c->log->debug(qq|Lexicon $lang/$lex loaded|);
227             }
228             }
229              
230             $handles{$lang} = $c->get_handle($lang);
231              
232             if (!defined $cfg->{fail_with} || $cfg->{fail_with}) {
233             $handles{$lang}->fail_with(
234             sub {
235             my ($flh, $key, @params) = @_;
236             my $value;
237             eval {
238             my $res = $c->model($cfg->{lex_class})->search({ lex_key => $key, lang => $lang, lex => $default_lex })->first;
239             unless ($res) {
240             my $rec = $c->model($cfg->{lex_class})->create(
241             {
242             lex => $default_lex,
243             lex_key => $key,
244             lex_value => '? ' . $key,
245             lang => $lang
246             }
247             );
248             $value = $rec->lex_value;
249             } else {
250             $value = $res->lex_value;
251             }
252             };
253             $c->log->error("Failed within fail_with(): $@") if $@;
254              
255             return $value;
256             }
257             );
258             }
259             }
260              
261             $cfg->{handles} = \%handles;
262              
263             $dbh->disconnect;
264             }
265              
266             =head1 FAQ
267              
268             =head2 Why use C<C::P::I18N::DBI> instead of C<C::P::I18N::DBIC>?
269              
270             Sometimes you don't want to select and parse the data from the database each
271             time you access your lexicon. Then C<C::P::I18N::DBI> is for you! It loads the
272             lexicon into memory at startup instead of fetching it over and over again.
273             But be careful, as this approach can waste a lot of memory and may slow your
274             system down (depending of the amount of data in your lexicon).
275              
276             I recommend to test both modules and decide which one is more suitable
277             depending on your production environment.
278              
279             =head2 Why does the database user needs write access? Or: What's the C<fail_with> function?
280              
281             C<C::P::I18N::DBI> implements a C<fail_with> method that attempts to create a new
282             database entry whenever a lexicon lookup fails. The value is set to the lexicon
283             key prefixed with the string C<? >.
284              
285             Example: you look up C<FooBar>, which doesn't exist. A new database entry will be
286             created with that key, the value will be C<? FooBar>.
287              
288             You can disable this behavior by setting the config key C<fail_with> to zero.
289              
290             =head1 SEE ALSO
291              
292             L<Calatyst>, L<Locale::Maketext>, L<Locale::Maketext::Lexicon>, L<Locale::Maketext::Lexicon::DBI>, L<DBI>,
293             L<Catalyst::Plugin::I18N::DBIC>
294              
295             =head1 AUTHOR
296              
297             Matthias Dietrich, C<< <perl@rainboxx.de> >>, http://www.rainboxx.de
298              
299             =head1 THANKS TO
300              
301             Rafael Kitover and Octavian RaÌ‚şniţă for Bugfixes
302              
303             =head1 COPYRIGHT AND LICENSE
304              
305             Copyright 2008 - 2009 rainboxx Matthias Dietrich. All Rights Reserved.
306              
307             This program is free software, you can redistribute it and/or modify it under
308             the same terms as Perl itself.
309              
310             =cut
311              
312             1;