File Coverage

blib/lib/Catalyst/Plugin/I18N/Request.pm
Criterion Covered Total %
statement 27 122 22.1
branch 0 58 0.0
condition n/a
subroutine 9 25 36.0
pod 15 15 100.0
total 51 220 23.1


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::I18N::Request;
2              
3 2     2   95178 use strict;
  2         6  
  2         83  
4 2     2   13 use warnings;
  2         3  
  2         65  
5              
6 2     2   2044 use MRO::Compat;
  2         36269  
  2         76  
7 2     2   12388 use URI;
  2         27416  
  2         79  
8 2     2   4547 use URI::QueryParam;
  2         4717  
  2         181  
9 2     2   4231 use utf8;
  2         72  
  2         13  
10 2     2   76 use Scalar::Util ();
  2         5  
  2         259  
11              
12             our $VERSION = '0.08';
13              
14             =head1 NAME
15              
16             Catalyst::Plugin::I18N::Request - A plugin for localizing/delocalizing
17             paths and parameters.
18              
19             =head1 SYNOPSIS
20            
21             package My::App;
22            
23             use Catalyst qw( ConfigLoader Static::Simple I18N I18N::Request );
24            
25             1;
26            
27             ...
28            
29             package My::App::Controller::Root;
30            
31             use base qw( Catalyst::Controller );
32            
33             sub search : Private {
34             my ($self, $c) = @_;
35             my $searchTerms = $c->req->param('searchTerms');
36             # yadda, yadda, yadda...
37             }
38            
39             ...
40            
41             French:
42            
43             Requested as:
44             GET /recherche?terms_de_recherche=Pirates HTTP/1.0
45             Accept-Language: fr
46            
47             Dispatched as:
48             GET /search?searchTerms=Pirates HTTP/1.0
49             Accept-Language: fr
50            
51             $c->uri_for('/search'):
52             http://localhost/recherche
53            
54             German:
55            
56             Requested as:
57             GET /suche?searchTerms=Pirates HTTP/1.0
58             Accept-Language: de
59            
60             Dispatched as:
61             GET /search?searchTerms=Pirates HTTP/1.0
62             Accept-Language: de
63            
64             $c->uri_for('/search'):
65             http://localhost/suche
66              
67             =head1 DESCRIPTION
68              
69             This plugin is designed to work alongside Catalyst::Plugin::I18N in
70             order to provide localization / delocalization of request paths and
71             request parameter names.
72              
73             =head1 DELOCALIZATION
74              
75             Delocalization occurs when a request is first received, before any
76             dispatching takes place. Delocalization assumes that there may exist
77             paths or parameter names within the request which do not correlate to
78             actual names used within the application itself. When functioning
79             properly, this plugin will allow users to activate an action called
80             'search' using:
81            
82             'recherche' (French requests)
83             'suche' (German requests)
84             etc...
85              
86             This relies on the localize method provided to the application by
87             Catalyst::Plugin::I18N. For the above examples to work, the following
88             localizations must occur:
89            
90             Key | Localized text | Language
91             ==========================================================
92             PATH_delocalize_recherche | search | French
93             PATH_delocalize_suche | search | German
94              
95             That is, $c->localize('PATH_delocalize_recherche') must return 'search'.
96             A very similar behaviour applies to parameter names within the query
97             string. The keys for these delocalizations begin with
98             'PARAMETER_delocalize_' instead of 'PATH_delocalize_'.
99              
100             =head1 LOCALIZATION
101              
102             Localization involves taking paths and parameter names and replacing
103             them with values which make more sense to users speaking the requested
104             language. In the above example, 'search' may not look intuitive to
105             German users. Out of the box, this plugin allows you to localize these
106             values transparently via the standard $c->uri_for and
107             $c->request->uri_with methods which are already standard features of
108             the Catalyst framework.
109              
110             Like delocalization, this functionality depends upon the $c->localize
111             method. However, PATH_delocalize_ is replaced with PATH_localize and
112             PARAMETER_delocalize_ is replaced with PARAMETER_localize_.
113            
114             Key | Localized text | Language
115             ==========================================================
116             PATH_localize_search | recherche | French
117             PATH_localize_search | suche | German
118              
119             =head1 METHODS
120              
121             =head2 setup ( )
122              
123             Allows Catalyst::Request to localize the results of calls to uri_with.
124              
125             =cut
126              
127             sub setup {
128 0     0 1   my $self = shift;
129 0           $self->next::method( @_ );
130            
131 2     2   12 no strict 'refs';
  2         6  
  2         69  
132 2     2   11 no warnings 'redefine';
  2         4  
  2         6101  
133            
134 0           my $uri_with = \&Catalyst::Request::uri_with;
135            
136             *Catalyst::Request::uri_with = sub {
137 0     0     my ($request) = @_;
138 0           my $uri = $uri_with->( @_ );
139            
140 0           return $request->{_context}->localize_uri( $uri );
141 0           };
142             }
143              
144             =head2 prepare ( )
145              
146             Overrides Catalyst's C<prepare> method to push the context object to the request
147             object.
148              
149             =cut
150              
151             sub prepare {
152 0     0 1   my $c = shift;
153 0           $c = $c->next::method( @_ );
154              
155 0 0         unless( $c->request->{ _context } ) {
156 0           Scalar::Util::weaken( $c->request->{ _context } = $c );
157             }
158              
159 0           return $c;
160             }
161              
162             =head2 uri_for ( $path [, @args ] [, \%query_values ] )
163              
164             Calls the native uri_for, but proceeds to localize the resulting path
165             and query values.
166              
167             =cut
168              
169             sub uri_for {
170 0     0 1   my $c = shift;
171 0           $c->localize_uri( $c->next::method( @_ ) );
172             }
173              
174             =head2 localize_uri ( $uri )
175              
176             Localizes a URI using the current context.
177              
178             =cut
179              
180             sub localize_uri {
181 0     0 1   my ($c, $uri) = @_;
182 0 0         return undef unless defined $uri;
183            
184 0 0         $uri = URI->new( $uri ) unless Scalar::Util::blessed( $uri );
185            
186             # parameters
187 0           my $query_form = $uri->query_form_hash;
188            
189             # decode all strings for character logic rather than byte logic
190 0           for my $value ( values %$query_form ) {
191 0 0         for ( ref $value eq 'ARRAY' ? @$value : $value ) {
192 0           $_ = "$_";
193 0           utf8::decode( $_ );
194             }
195             }
196            
197             # localize the parameters
198 0           my $parameters = $c->localize_parameters( $query_form );
199            
200             # encode all strings for byte logic rather than character logic
201 0           for my $value ( values %$parameters ) {
202 0 0         for ( ref $value eq 'ARRAY' ? @$value : $value ) {
203 0           $_ = "$_";
204 0           utf8::encode( $_ );
205             }
206             }
207            
208 0           $uri->query_form_hash( $parameters );
209            
210             # path
211 0           $uri->path( $c->localize_path( $uri->path ) );
212            
213 0           return $uri;
214             }
215              
216             =head2 localize_path ( $path )
217              
218             Localizes all components of the provided path.
219              
220             =cut
221              
222             sub localize_path {
223 0     0 1   my ($c, $path) = @_;
224 0 0         return undef unless defined $path;
225 0           return join '/', map { $c->localize_path_component( $_ ) } split m!/!, $path;
  0            
226             }
227              
228             =head2 delocalize_path ( $path )
229              
230             Delocalizes all components of the provided path.
231              
232             =cut
233              
234             sub delocalize_path {
235 0     0 1   my ($c, $path) = @_;
236 0 0         return undef unless defined $path;
237 0           return join '/', map { $c->delocalize_path_component( $_ ) } split m!/!, $path;
  0            
238             }
239              
240             =head2 transform_parameters ( \%parameters, $transformer )
241              
242             Transforms the given parameter names using the given transformer. The
243             transformer may be one of the following:
244              
245             =over 4
246              
247             =item * A CODE reference which accepts the context object as the first
248             argument and a parameter name as the second argument.
249              
250             =item * The name of a particular accessor that can be called on the
251             context object, accepting a parameter name as the argument.
252              
253             =back
254              
255             =cut
256              
257             sub transform_parameters {
258 0     0 1   my ($c, $parameters, $transformer) = @_;
259 0 0         my %parameters = ref $parameters eq 'HASH' ? %$parameters : ();
260            
261 0           my %transformed;
262 0           for ( keys %parameters ) {
263 0 0         my $name = ref $transformer eq 'CODE' ? $transformer->( $c, $_ )
    0          
264             : $c->can($transformer) ? $c->$transformer( $_ )
265             : $_;
266            
267 0           my $value = $parameters{ $_ };
268            
269 0 0         if ( exists $transformed{$name} ) {
270 0 0         if ( ref $transformed{$name} eq 'ARRAY' ) {
271 0 0         push @{ $transformed{$name} }, ref $value eq 'ARRAY' ? @$value : $value;
  0            
272             }
273             else {
274 0 0         $transformed{$name} = [ $transformed{$name}, ref $value eq 'ARRAY' ? @$value : $value ];
275             }
276             }
277             else {
278 0           $transformed{$name} = $value;
279             }
280             }
281            
282 0 0         return wantarray ? %transformed : \%transformed;
283             }
284              
285             =head2 localize_parameters ( \%parameters )
286              
287             Localizes the keys within a hash of parameters.
288              
289             =cut
290              
291             sub localize_parameters {
292 0     0 1   my $c = shift;
293 0 0         my %parameters = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0            
294 0           $c->transform_parameters( \%parameters, 'localize_parameter_name' );
295             }
296              
297              
298             =head2 delocalize_parameters ( \%parameters )
299              
300             Delocalizes the keys within a hash of parameters.
301              
302             =cut
303              
304             sub delocalize_parameters {
305 0     0 1   my $c = shift;
306 0 0         my %parameters = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
  0            
307 0           $c->transform_parameters( \%parameters, 'delocalize_parameter_name' );
308             }
309              
310              
311             =head2 prepare_path ( )
312              
313             Delocalizes the requested path.
314              
315             =cut
316              
317             sub prepare_path {
318 0     0 1   my $c = shift;
319 0           $c->next::method( @_ );
320 0           $c->req->path( $c->delocalize_path( $c->req->path ) );
321             }
322              
323             =head2 prepare_parameters ( )
324              
325             Delocalizes the requested parameter names.
326              
327             =cut
328              
329             sub prepare_parameters {
330 0     0 1   my $c = shift;
331 0           $c->next::method( @_ );
332            
333 0           my %parameters = $c->delocalize_parameters( $c->request->params );
334            
335 0           $c->request->uri->query_form( \%parameters );
336 0           $c->request->params( \%parameters );
337             }
338              
339             =head2 localize_path_component ( $delocalized )
340              
341             Localizes a component of a path.
342              
343             =cut
344              
345             sub localize_path_component {
346 0     0 1   my ($c, $delocalized) = @_;
347 0 0         return undef unless defined $delocalized;
348            
349 0 0         if ( $c->can('localize') ) {
350 0           my $key = "PATH_localize_$delocalized";
351 0           my $localized = $c->localize($key);
352 0 0         return $localized unless $localized eq $key;
353             }
354            
355 0           return $delocalized;
356             }
357              
358             =head2 delocalize_path_component ( $localized )
359              
360             Delocalizes a component of a path.
361              
362             =cut
363              
364             sub delocalize_path_component {
365 0     0 1   my ($c, $localized) = @_;
366 0 0         return undef unless defined $localized;
367            
368 0 0         if ( $c->can('localize') ) {
369 0           my $key = "PATH_delocalize_$localized";
370 0           my $delocalized = $c->localize($key);
371 0 0         return $delocalized unless $delocalized eq $key;
372             }
373            
374 0           return $localized;
375             }
376              
377             =head2 localize_parameter_name ( $delocalized )
378              
379             Localizes a parameter name.
380              
381             =cut
382              
383             sub localize_parameter_name {
384 0     0 1   my ($c, $delocalized) = @_;
385 0 0         return undef unless defined $delocalized;
386            
387 0 0         if ( $c->can('localize') ) {
388 0           my $key = "PARAMETER_localize_$delocalized";
389 0           my $localized = $c->localize($key);
390 0 0         return $localized unless $localized eq $key;
391             }
392            
393 0           return $delocalized;
394             }
395              
396             =head2 delocalize_parameter_name ( $localized )
397              
398             Delocalizes a parameter name.
399              
400             =cut
401              
402             sub delocalize_parameter_name {
403 0     0 1   my ($c, $localized) = @_;
404 0 0         return undef unless defined $localized;
405            
406 0 0         if ( $c->can('localize') ) {
407 0           my $key = "PARAMETER_delocalize_$localized";
408 0           my $delocalized = $c->localize($key);
409 0 0         return $delocalized unless $delocalized eq $key;
410             }
411            
412 0           return $localized;
413             }
414              
415             =head1 SEE ALSO
416              
417             =over 4
418              
419             =item * L<Catalyst::Plugin::I18N>
420              
421             =item * L<Catalyst>
422              
423             =back
424              
425             =head1 AUTHORS
426              
427             Adam Paynter E<lt>adapay@cpan.orgE<gt>
428              
429             Brian Cassidy E<lt>bricas@cpan.orgE<gt>
430              
431             =head1 COPYRIGHT AND LICENSE
432              
433             Copyright 2006-2012 by Adam Paynter, Brian Cassidy
434              
435             This library is free software; you can redistribute it and/or modify
436             it under the same terms as Perl itself.
437              
438             =cut
439              
440             1;