File Coverage

blib/lib/CGI/Lingua.pm
Criterion Covered Total %
statement 207 595 34.7
branch 109 446 24.4
condition 23 112 20.5
subroutine 21 28 75.0
pod 11 11 100.0
total 371 1192 31.1


line stmt bran cond sub pod time code
1             package CGI::Lingua;
2              
3 15     15   1757017 use warnings;
  15         168  
  15         581  
4 15     15   89 use strict;
  15         32  
  15         314  
5 15     15   9737 use Storable; # RT117983
  15         53048  
  15         1113  
6 15     15   9486 use Class::Autouse qw{Carp Locale::Language Locale::Object::Country Locale::Object::DB I18N::AcceptLanguage I18N::LangTags::Detect};
  15         97323  
  15         144  
7              
8 15     15   16608 use vars qw($VERSION);
  15         34  
  15         102107  
9             our $VERSION = '0.63';
10              
11             =head1 NAME
12              
13             CGI::Lingua - Create a multilingual web page
14              
15             =head1 VERSION
16              
17             Version 0.63
18              
19             =cut
20              
21             =head1 SYNOPSIS
22              
23             No longer does your website need to be in English only.
24             CGI::Lingua provides a simple basis to determine which language to display a
25             website. The website tells CGI::Lingua which languages it supports. Based on
26             that list CGI::Lingua tells the application which language the user would like
27             to use.
28              
29             use CGI::Lingua;
30             # ...
31             my $l = CGI::Lingua->new(supported => ['en', 'fr', 'en-gb', 'en-us']);
32             my $language = $l->language();
33             if ($language eq 'English') {
34             print '<P>Hello</P>';
35             } elsif($language eq 'French') {
36             print '<P>Bonjour</P>';
37             } else { # $language eq 'Unknown'
38             my $rl = $l->requested_language();
39             print "<P>Sorry for now this page is not available in $rl.</P>";
40             }
41             my $c = $l->country();
42             if ($c eq 'us') {
43             # print contact details in the US
44             } elsif ($c eq 'ca') {
45             # print contact details in Canada
46             } else {
47             # print worldwide contact details
48             }
49              
50             # ...
51              
52             use CHI;
53             use CGI::Lingua;
54             # ...
55             my $cache = CHI->new(driver => 'File', root_dir => '/tmp/cache', namespace => 'CGI::Lingua-countries');
56             my $l = CGI::Lingua->new({ supported => ['en', 'fr'], cache => $cache });
57              
58             =head1 SUBROUTINES/METHODS
59              
60             =head2 new
61              
62             Creates a CGI::Lingua object.
63              
64             Takes one mandatory parameter: a list of languages, in RFC-1766 format,
65             that the website supports.
66             Language codes are of the form primary-code [ - country-code ] e.g.
67             'en', 'en-gb' for English and British English respectively.
68              
69             For a list of primary-codes refer to ISO-639 (e.g. 'en' for English).
70             For a list of country-codes refer to ISO-3166 (e.g. 'gb' for United Kingdom).
71              
72             # We support English, French, British and American English, in that order
73             my $l = CGI::Lingua(supported => ['en', 'fr', 'en-gb', 'en-us']);
74              
75             Takes optional parameter cache, an object which is used to cache country
76             lookups.
77             This cache object is an object that understands get() and set() messages,
78             such as a L<CHI> object.
79              
80             Takes an optional parameter syslog, to log messages to
81             L<Sys::Syslog>.
82             It can be a boolean to enable/disable logging to syslog, or a reference
83             to a hash to be given to Sys::Syslog::setlogsock.
84              
85             Takes optional parameter logger, an object which is used for warnings
86             and traces.
87             This logger object is an object that understands warn() and trace()
88             messages, such as a L<Log::Log4perl> object.
89              
90             Takes optional parameter info, an object which can be used to see if a CGI
91             parameter is set, for example an L<CGI::Info> object.
92              
93             Since emitting warnings from a CGI class can result in messages being lost (you
94             may forget to look in your server's log), or appearing to the client in
95             amongst HTML causing invalid HTML, it is recommended either either syslog
96             or logger (or both) are set.
97             If neither is given, L<Carp> will be used.
98              
99             Takes an optional parameter dont_use_ip. By default, if none of the
100             requested languages is supported, CGI::Lingua->language() looks in the IP
101             address for the language to use. This may be not what you want, so use this
102             option to disable the feature.
103              
104             The optional parameter debug is passed on to L<I18N::AcceptLanguage>.
105              
106             =cut
107              
108             sub new {
109 31     31 1 13391 my $proto = shift;
110              
111 31   66     247 my $class = ref($proto) || $proto;
112 31 100       197 my %params = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_;
  2         9  
113              
114             # TODO: check that the number of supported languages is > 0
115             # unless($params{supported} && ($#params{supported} > 0)) {
116             # croak('You must give a list of supported languages');
117             # }
118 31 100       128 unless($params{supported}) {
119 1         22 Carp::croak('You must give a list of supported languages');
120             }
121              
122 30         68 my $cache = $params{cache};
123 30         64 my $logger = $params{logger};
124 30         67 my $info = $params{info};
125              
126 30 0 33     97 if($cache && $ENV{'REMOTE_ADDR'}) {
127 0         0 my $key = "$ENV{REMOTE_ADDR}/";
128 0         0 my $l;
129 0 0 0     0 if($info && ($l = $info->lang())) {
    0          
130 0         0 $key .= "$l/";
131             } elsif($l = $class->_what_language()) {
132 0         0 $key .= "$l/";
133             }
134 0         0 $key .= join('/', @{$params{supported}});
  0         0  
135 0 0       0 if($logger) {
136 0         0 $logger->debug("Looking in cache for $key");
137             }
138 0 0       0 if(my $rc = $cache->get($key)) {
139 0 0       0 if($logger) {
140 0         0 $logger->debug('Found - thawing');
141             }
142 0         0 $rc = Storable::thaw($rc);
143 0         0 $rc->{_logger} = $logger;
144 0         0 $rc->{_syslog} = $params{syslog};
145 0         0 $rc->{_cache} = $cache;
146 0         0 $rc->{_supported} = $params{supported};
147 0         0 $rc->{_info} = $info;
148              
149 0 0 0     0 if(($rc->{_what_language} || $rc->{_rlanguage}) && $info && $info->lang()) {
      0        
      0        
150 0         0 delete $rc->{_what_language};
151 0         0 delete $rc->{_rlanguage};
152 0         0 delete $rc->{_country};
153             }
154 0         0 return $rc;
155             }
156             }
157              
158             return bless {
159             _supported => $params{supported}, # List of languages (two letters) that the application
160             _cache => $cache, # CHI
161             _info => $info,
162             # _rlanguage => undef, # Requested language
163             # _slanguage => undef, # Language that the website should display
164             # _sublanguage => undef, # E.g. United States for en-US if you want American English
165             # _slanguage_code_alpha2 => undef, # E.g en, fr
166             # _sublanguage_code_alpha2 => undef, # E.g. us, gb
167             # _country => undef, # Two letters, e.g. gb
168             # _locale => undef, # Locale::Object::Country
169             _syslog => $params{syslog},
170             _dont_use_ip => $params{dont_use_ip} || 0,
171             _logger => $logger,
172             _have_ipcountry => -1, # -1 = don't know
173             _have_geoip => -1, # -1 = don't know
174             _have_geoipfree => -1, # -1 = don't know
175 30   50     531 _debug => $params{debug} || 0,
      50        
176             }, $class;
177             }
178              
179             # Some of the information takes a long time to work out, so cache what we can
180             sub DESTROY {
181 30 50 33 30   38440 if(defined($^V) && ($^V ge 'v5.14.0')) {
182 30 50       212 return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; # >= 5.14.0 only
183             }
184 30 100       164 unless($ENV{'REMOTE_ADDR'}) {
185 10         199 return;
186             }
187 20         55 my $self = shift;
188 20 50       68 return unless(ref($self));
189              
190 20         78 my $cache = $self->{_cache};
191 20 50       1367 return unless($cache);
192              
193 0         0 my $key = "$ENV{REMOTE_ADDR}/";
194 0 0       0 if(my $l = $self->_what_language()) {
195 0         0 $key .= "$l/";
196             }
197 0         0 $key .= join('/', @{$self->{_supported}});
  0         0  
198 0 0       0 return if($cache->get($key));
199              
200 0 0       0 if(my $logger = $self->{_logger}) {
201 0         0 $logger->trace("Storing self in cache as $key");
202             }
203              
204             my $copy = bless {
205             _slanguage => $self->{_slanguage},
206             _slanguage_code_alpha2 => $self->{_slanguage_code_alpha2},
207             _sublanguage_code_alpha2 => $self->{_sublanguage_code_alpha2},
208             _country => $self->{_country},
209             _rlanguage => $self->{_rlanguage},
210             _dont_use_ip => $self->{_dont_use_ip},
211             _have_ipcountry => $self->{_have_ipcountry},
212             _have_geoip => $self->{_have_geoip},
213             _have_geoipfree => $self->{_have_geoipfree},
214 0         0 }, ref($self);
215              
216             # All of these crash, presumably something recursive is going on
217             # my $copy = Clone::clone($self);
218             # my $storable = Storable::nfreeze(Storable::dclone($self));
219             # my $storable = Storable::dclone($self);
220              
221 0         0 $cache->set($key, Storable::nfreeze($copy), '1 month');
222             }
223              
224             # Emit a warning message somewhere
225             sub _warn {
226 0     0   0 my ($self, $params) = @_;
227              
228 0         0 my $warning = $$params{'warning'};
229              
230 0 0       0 return unless($warning);
231              
232 0 0       0 if(my $syslog = $self->{_syslog}) {
233 0         0 require Sys::Syslog;
234 0         0 require CGI::Info;
235              
236 0         0 Sys::Syslog->import();
237 0         0 CGI::Info->import();
238 0 0       0 if(ref($syslog) eq 'HASH') {
239 0         0 Sys::Syslog::setlogsock($syslog);
240             }
241 0 0       0 if(my $info = $self->{_info}) {
242 0         0 openlog($info->script_name(), 'cons,pid', 'user');
243             } else {
244 0         0 openlog(CGI::Info->new(syslog => $syslog)->script_name(), 'cons,pid', 'user');
245             }
246 0         0 syslog('warning', $warning);
247 0         0 closelog();
248             }
249              
250 0 0       0 if($self->{_logger}) {
    0          
251 0         0 $self->{_logger}->warn($warning);
252             } elsif(!defined($self->{_syslog})) {
253 0         0 Carp::carp($warning);
254             }
255             }
256              
257             =head2 language
258              
259             Tells the CGI application what language to display its messages in.
260             The language is the natural name e.g. 'English' or 'Japanese'.
261              
262             Sublanguages are handled sensibly, so that if a client requests U.S. English
263             on a site that only serves British English, language() will return 'English'.
264              
265             If none of the requested languages is included within the supported lists,
266             language() returns 'Unknown'.
267              
268             use CGI::Lingua;
269             # Site supports English and British English
270             my $l = CGI::Lingua->new(supported => ['en', 'fr', 'en-gb']);
271              
272             # If the browser requests 'en-us' , then language will be 'English' and
273             # sublanguage will be undefined because we weren't able to satisfy the
274             # request
275              
276             # Site supports British English only
277             my $l = CGI::Lingua->new({supported => ['fr', 'en-gb']});
278              
279             # If the browser requests 'en-us' , then language will be 'English' and
280             # sublanguage will also be undefined, which may seem strange, but it
281             # ensures that sites behave sensibly.
282              
283             If the script is not being run in a CGI environment, perhaps to debug it, the
284             locale is used via the LANG environment variable.
285             =cut
286              
287             sub language {
288 17     17 1 3546 my $self = shift;
289              
290 17 100       68 unless($self->{_slanguage}) {
291 10         44 $self->_find_language();
292             }
293 17         114 return $self->{_slanguage};
294             }
295              
296             =head2 name
297              
298             Synonym for language, for compatibility with Local::Object::Language
299              
300             =cut
301              
302             sub name {
303 0     0 1 0 my $self = shift;
304              
305 0         0 return $self->language();
306             }
307              
308             =head2 sublanguage
309              
310             Tells the CGI what variant to use e.g. 'United Kingdom', or 'Unknown' if
311             it can't be determined.
312              
313             Sublanguages are handled sensibly, so that if a client requests U.S. English
314             on a site that only serves British English, sublanguage() will return undef.
315              
316             =cut
317              
318             sub sublanguage {
319 16     16 1 81 my $self = shift;
320              
321 16 100       62 unless($self->{_slanguage}) {
322 1         6 $self->_find_language();
323             }
324 16         81 return $self->{_sublanguage};
325             }
326              
327             =head2 language_code_alpha2
328              
329             Gives the two character representation of the supported language, e.g. 'en'
330             when you've asked for en-gb.
331              
332             If none of the requested languages is included within the supported lists,
333             language_code_alpha2() returns undef.
334              
335             =cut
336              
337             sub language_code_alpha2 {
338 8     8 1 36 my $self = shift;
339              
340 8 50       38 if($self->{_logger}) {
341 0         0 $self->{_logger}->trace('Entered language_code_alpha2');
342             }
343 8 100       31 unless($self->{_slanguage}) {
344 1         17 $self->_find_language();
345             }
346 8 50       30 if($self->{_logger}) {
347 0         0 $self->{_logger}->trace('language_code_alpha2 returns ', $self->{_slanguage_code_alpha2});
348             }
349 8         51 return $self->{_slanguage_code_alpha2};
350             }
351              
352              
353             =head2 code_alpha2
354              
355             Synonym for language_code_alpha2, kept for historical reasons.
356              
357             =cut
358              
359             sub code_alpha2 {
360 8     8 1 1205 my $self = shift;
361              
362 8         31 return $self->language_code_alpha2();
363             }
364              
365             =head2 sublanguage_code_alpha2
366              
367             Gives the two character representation of the supported language, e.g. 'gb'
368             when you've asked for en-gb, or undef.
369              
370             =cut
371              
372             sub sublanguage_code_alpha2 {
373 0     0 1 0 my $self = shift;
374              
375 0 0       0 unless($self->{_slanguage}) {
376 0         0 $self->_find_language();
377             }
378 0         0 return $self->{_sublanguage_code_alpha2};
379             }
380              
381              
382             =head2 requested_language
383              
384             Gives a human readable rendition of what language the user asked for whether
385             or not it is supported.
386              
387             =cut
388              
389             sub requested_language {
390 39     39 1 17652 my $self = shift;
391              
392 39 100       149 unless($self->{_rlanguage}) {
393 10         40 $self->_find_language();
394             }
395 39         211 return $self->{_rlanguage};
396             }
397              
398             # The language cache is stored as country_2_letter -> $language_human_readable_name=$language_2_letter
399             # The IP cache is stored as ip -> country_human_readable_name
400              
401             # Returns the human readable language, such as 'English'
402              
403             sub _find_language {
404 22     22   55 my $self = shift;
405              
406 22 50       86 if($self->{_logger}) {
407 0         0 $self->{_logger}->trace('Entered _find_language');
408             }
409 22         88 $self->{_rlanguage} = 'Unknown';
410 22         54 $self->{_slanguage} = 'Unknown';
411              
412             # Use what the client has said
413 22         99 my $http_accept_language = $self->_what_language();
414 22 100       65 if(defined($http_accept_language)) {
415 21 50       87 if($self->{_logger}) {
416 0         0 $self->{_logger}->debug("language wanted: $http_accept_language");
417             }
418              
419             # Workaround for RT 74338
420             local $SIG{__WARN__} = sub {
421 0 0   0   0 if($_[0] !~ /^Use of uninitialized value/) {
422 0         0 warn $_[0];
423             }
424 21         198 };
425 21         239 my $i18n = I18N::AcceptLanguage->new(debug => $self->{_debug});
426 21         14041 my $l = $i18n->accepts($http_accept_language, $self->{_supported});
427 21         4103 local $SIG{__WARN__} = 'DEFAULT';
428 21 100 100     127 if((!$l) && ($http_accept_language =~ /(.+)-.+/)) {
429             # Fall back position, e,g. we want US English on a site
430             # only giving British English, so allow it as English.
431             # The calling program can detect that it's not the
432             # wanted flavour of English by looking at
433             # requested_language
434 3 100       14 if($i18n->accepts($1, $self->{_supported})) {
435 2         144 $l = $1;
436             }
437             }
438              
439 21 100       157 if($l) {
440 19 50       72 if($self->{_logger}) {
441 0         0 $self->{_logger}->debug("l: $l");
442             }
443              
444 19 100       84 unless($l =~ /^..-..$/) {
445 6         30 $self->{_slanguage} = $self->_code2language($l);
446 6 50       494625 if($self->{_slanguage}) {
447 6 50       23 if($self->{_logger}) {
448 0         0 $self->{_logger}->debug("_slanguage: $self->{_slanguage}");
449             }
450             # We have the language, but not the right
451             # sublanguage, e.g. they want US English but we
452             # only support British English or English
453             # wanted: en-us, got en-gb and en
454 6         19 $self->{_slanguage_code_alpha2} = $l;
455 6         14 $self->{_rlanguage} = $self->{_slanguage};
456              
457 6         14 my $sl;
458 6 100       61 if($http_accept_language =~ /..-(..)$/) {
    50          
459 3         53 $sl = $self->_code2country($1);
460             } elsif($http_accept_language =~ /..-([a-z]{2,3})$/i) {
461 0         0 $sl = Locale::Object::Country->new(code_alpha3 => $1);
462             }
463 6 100       24 if($sl) {
464 3         22 $self->{_rlanguage} .= ' (' . $sl->name() . ')';
465             # The requested sublanguage
466             # isn't supported so don't
467             # define that
468             }
469 6         103 return;
470             }
471             }
472             # TODO: Handle es-419 "Spanish (Latin America)"
473 13 50       85 if($l =~ /(.+)-(..)$/) {
474 13         40 my $alpha2 = $1;
475 13         39 my $variety = $2;
476             # my $accepts = $i18n->accepts($l, $self->{_supported});
477 13         28 my $accepts = $l;
478              
479 13 50       40 if($accepts) {
480 13 50       72 if($self->{_logger}) {
481 0         0 $self->{_logger}->debug("accepts: $accepts");
482             }
483 13 50       53 if($accepts =~ /\-/) {
484 13         37 delete $self->{_slanguage};
485             } else {
486 0         0 my $from_cache;
487 0 0       0 if($self->{_cache}) {
488 0         0 $from_cache = $self->{_cache}->get($accepts);
489             }
490 0 0       0 if($from_cache) {
491 0 0       0 if($self->{_logger}) {
492 0         0 $self->{_logger}->debug("$accepts is in cache as $from_cache");
493             }
494 0         0 $self->{_slanguage} = (split(/=/, $from_cache))[0];
495             } else {
496 0         0 $self->{_slanguage} = $self->_code2language($accepts);
497             }
498 0 0       0 if($self->{_slanguage}) {
499 0 0       0 if($variety eq 'uk') {
500             # ???
501 0         0 $self->_warn({
502             warning => "Resetting country code to GB for $http_accept_language"
503             });
504 0         0 $variety = 'gb';
505             }
506 0         0 my $c = $self->_code2countryname($variety);
507 0 0       0 if(defined($c)) {
508 0         0 $self->{_sublanguage} = $c;
509             }
510 0         0 $self->{_slanguage_code_alpha2} = $accepts;
511 0 0       0 if($self->{_sublanguage}) {
512 0         0 $self->{_rlanguage} = "$self->{_slanguage} ($self->{_sublanguage})";
513 0 0       0 if($self->{_logger}) {
514 0         0 $self->{_logger}->debug("_rlanguage: $self->{_rlanguage}");
515             }
516             }
517 0         0 $self->{_sublanguage_code_alpha2} = $variety;
518 0 0       0 unless($from_cache) {
519 0 0       0 if($self->{_logger}) {
520 0         0 $self->{_logger}->debug("Set $variety to $self->{_slanguage}=$self->{_slanguage_code_alpha2}");
521             }
522 0         0 $self->{_cache}->set($variety, "$self->{_slanguage}=$self->{_slanguage_code_alpha2}", '1 month');
523             }
524 0         0 return;
525             }
526             }
527             }
528 13         56 $self->{_rlanguage} = $self->_code2language($alpha2);
529 13 50       880733 if($self->{_logger}) {
530 0         0 $self->{_logger}->debug("_rlanguage: $self->{_rlanguage}");
531             }
532 13 50       50 if($accepts) {
533 13         101 $http_accept_language =~ /(.{2})-(..)/;
534 13         49 $variety = lc($2);
535             # Ignore en-029 etc (Carribean English)
536 13 50 33     207 if(($variety =~ /[a-z]{2,3}/) && !defined($self->{_sublanguage})) {
537 13         67 $self->_get_closest($alpha2, $alpha2);
538 13 50       42 if($self->{_logger}) {
539 0         0 $self->{_logger}->debug("Find the country code for $variety");
540             }
541 13 50       69 if($variety eq 'uk') {
542             # ???
543 0         0 $self->_warn({
544             warning => "Resetting country code to GB for $http_accept_language"
545             });
546 0         0 $variety = 'gb';
547             }
548 13         32 my $from_cache;
549             my $language_name;
550 13 50       40 if($self->{_cache}) {
551 0         0 $from_cache = $self->{_cache}->get($variety);
552             }
553 13 50       35 if(defined($from_cache)) {
554 0 0       0 if($self->{_logger}) {
555 0         0 $self->{_logger}->debug("$variety is in cache as $from_cache");
556             }
557 0         0 my $language_code2;
558 0         0 ($language_name, $language_code2) = split(/=/, $from_cache);
559 0         0 $language_name = $self->_code2countryname($variety);
560             } else {
561 13         164 my $db = Locale::Object::DB->new();
562 13         156310 my @results = @{$db->lookup(
  13         77  
563             table => 'country',
564             result_column => 'name',
565             search_column => 'code_alpha2',
566             value => $variety
567             )};
568 13 50       6878 if(defined($results[0])) {
569 13         29 eval {
570 13         67 $language_name = $self->_code2countryname($variety);
571             };
572             }
573             }
574 13 50 33     288 if($@ || !defined($language_name)) {
575 0         0 $self->{_sublanguage} = 'Unknown';
576 0         0 $self->_warn({
577             warning => "Can't determine values for $http_accept_language"
578             });
579             } else {
580 13         53 $self->{_sublanguage} = $language_name;
581 13 50       47 if($self->{_logger}) {
582 0         0 $self->{_logger}->debug('variety name ', $self->{_sublanguage});
583             }
584 13 50 33     64 if($self->{_cache} && !defined($from_cache)) {
585 0 0       0 if($self->{_logger}) {
586 0         0 $self->{_logger}->debug("Set $variety to $self->{_slanguage}=$self->{_slanguage_code_alpha2}");
587             }
588 0         0 $self->{_cache}->set($variety, "$self->{_slanguage}=$self->{_slanguage_code_alpha2}", '1 month');
589             }
590             }
591             }
592 13 50       43 if(defined($self->{_sublanguage})) {
593 13         77 $self->{_rlanguage} = "$self->{_slanguage} ($self->{_sublanguage})";
594 13         61 $self->{_sublanguage_code_alpha2} = $variety;
595 13         154 return;
596             }
597             }
598             }
599             }
600 2 50 33     13 if($self->{_slanguage} && ($self->{_slanguage} ne 'Unknown')) {
601 0 0       0 if($self->{_rlanguage} eq 'Unknown') {
602 0         0 $self->{_rlanguage} = I18N::LangTags::Detect::detect();
603             }
604 0 0       0 if($self->{_rlanguage}) {
605 0 0       0 if($l = $self->_code2language($self->{_rlanguage})) {
606 0         0 $self->{_rlanguage} = $l;
607             # } else {
608             # We have the language, but not the right
609             # sublanguage, e.g. they want US English but we
610             # only support British English
611             # wanted: en-us, got en-gb and not en
612             }
613 0         0 return;
614             }
615             }
616 2 100 33     23 if(((!$self->{_rlanguage}) || ($self->{_rlanguage} eq 'Unknown')) &&
      66        
      33        
617             ((length($http_accept_language) == 2) || ($http_accept_language =~ /^..-..$/))) {
618 1         7 $self->{_rlanguage} = $self->_code2language($http_accept_language);
619              
620 1 50       106 unless($self->{_rlanguage}) {
621 1         2 $self->{_rlanguage} = 'Unknown';
622             }
623             }
624 2         20 $self->{_slanguage} = 'Unknown';
625             }
626              
627 3 50       8 if($self->{_dont_use_ip}) {
628 0         0 return;
629             }
630              
631             # The client hasn't said which to use, guess from their IP address,
632             # or the requested language(s) isn't/aren't supported so use the IP
633             # address for an alternative
634 3         11 my $country = $self->country();
635              
636 3 100 66     12 if((!defined($country)) && (my $c = $self->_what_language())) {
637 2 50       16 if($c =~ /^(..)_(..)/) {
    100          
638 0         0 $country = $2; # Best guess
639             } elsif($c =~ /^(..)$/) {
640 1         4 $country = $1; # Wrong, but maybe something will drop out
641             }
642             }
643              
644 3 100       11 if(defined($country)) {
645 1 50       4 if($self->{_logger}) {
646 0         0 $self->{_logger}->debug("country: $country");
647             }
648             # Determine the first official language of the country
649              
650 1         2 my $from_cache;
651 1 50       36 if($self->{_cache}) {
652 0         0 $from_cache = $self->{_cache}->get($country);
653             }
654 1         5 my $language_name;
655             my $language_code2;
656 1 50       4 if($from_cache) {
657 0 0       0 if($self->{_logger}) {
658 0         0 $self->{_logger}->debug("$country is in cache as $from_cache");
659             }
660 0         0 ($language_name, $language_code2) = split(/=/, $from_cache);
661             } else {
662 1         29 my $l = $self->_code2country(uc($country));
663 1 50       5 if($l) {
664 0         0 $l = ($l->languages_official)[0];
665 0 0       0 if(defined($l)) {
666 0         0 $language_name = $l->name;
667 0         0 $language_code2 = $l->code_alpha2;
668 0 0 0     0 if($self->{_logger} && $language_name) {
669 0         0 $self->{_logger}->debug("Official language: $language_name");
670             }
671             }
672             }
673             }
674 1         3 my $ip = $ENV{'REMOTE_ADDR'};
675 1 50       9 if($language_name) {
    50          
676 0 0 0     0 if((!defined($self->{_rlanguage})) || ($self->{_rlanguage} eq 'Unknown')) {
677 0         0 $self->{_rlanguage} = $language_name;
678             }
679 0 0 0     0 unless((exists($self->{_slanguage})) && ($self->{_slanguage} ne 'Unknown')) {
680             # Check if the language is one that we support
681             # Don't bother with secondary language
682 0         0 my $code;
683              
684 0 0 0     0 if($language_name && $language_code2 && !defined($http_accept_language)) {
      0        
685             # This sort of thing speeds up search engine access a lot
686 0 0       0 if($self->{_logger}) {
687 0         0 $self->{_logger}->debug("Fast assign to $language_code2");
688             }
689 0         0 $code = $language_code2;
690             } else {
691 0 0       0 if($self->{_logger}) {
692 0         0 $self->{_logger}->debug("Call language2code on $self->{_rlanguage}");
693             }
694 0         0 $code = Locale::Language::language2code($self->{_rlanguage});
695 0 0       0 unless($code) {
696 0 0 0     0 if($http_accept_language && ($http_accept_language ne $self->{_rlanguage})) {
697 0 0       0 if($self->{_logger}) {
698 0         0 $self->{_logger}->debug("Call language2code on $http_accept_language");
699             }
700 0         0 $code = Locale::Language::language2code($http_accept_language);
701             }
702 0 0       0 unless($code) {
703             # If language is Norwegian (Nynorsk)
704             # lookup Norwegian
705 0 0       0 if($self->{_rlanguage} =~ /(.+)\s\(.+/) {
706 0 0 0     0 if((!defined($http_accept_language)) || ($1 ne $self->{_rlanguage})) {
707 0 0       0 if($self->{_logger}) {
708 0         0 $self->{_logger}->debug("Call language2code on $1");
709             }
710 0         0 $code = Locale::Language::language2code($1);
711             }
712             }
713 0 0       0 unless($code) {
714 0         0 $self->_warn({
715             warning => "Can't determine code from IP $ip for requested language $self->{_rlanguage}"
716             });
717             }
718             }
719             }
720             }
721 0 0       0 if($code) {
722 0         0 $self->_get_closest($code, $language_code2);
723 0 0 0     0 unless($self->{_slanguage}) {
724 0         0 $self->_warn({
725             warning => "Couldn't determine closest language for $language_name in $self->{_supported}"
726             });
727             } elsif($self->{_logger}) {
728             $self->{_logger}->debug("language set to $self->{_slanguage}, code set to $code");
729             }
730             }
731             }
732 0 0 0     0 if(!defined($self->{_slanguage_code_alpha2})) {
    0 0        
733 0 0       0 if($self->{_logger}) {
734 0         0 $self->{_logger}->debug("Can't determine slanguage_code_alpha2");
735             }
736             } elsif(!defined($from_cache) && $self->{_cache} &&
737             defined($self->{_slanguage_code_alpha2})) {
738 0 0       0 if($self->{_logger}) {
739 0         0 $self->{_logger}->debug("Set $country to $language_name=$self->{_slanguage_code_alpha2}");
740             }
741 0         0 $self->{_cache}->set($country, "$language_name=$self->{_slanguage_code_alpha2}", '1 month');
742             }
743             } elsif(defined($ip)) {
744 0         0 $self->_warn({
745             warning => "Can't determine language from IP $ip, country $country"
746             });
747             }
748             }
749             }
750              
751             # Try our very best to give the right country - if they ask for en-us and
752             # we only have en-gb then give it to them
753              
754             sub _get_closest {
755 13     13   46 my ($self, $language_string, $alpha2) = @_;
756              
757 13         27 foreach (@{$self->{_supported}}) {
  13         52  
758 13         25 my $s;
759 13 100       61 if(/^(.+)-.+/) {
760 10         34 $s = $1;
761             } else {
762 3         6 $s = $_;
763             }
764 13 50       94 if($language_string eq $s) {
765 13         43 $self->{_slanguage} = $self->{_rlanguage};
766 13         40 $self->{_slanguage_code_alpha2} = $alpha2;
767 13         32 last;
768             }
769             }
770             }
771              
772             # What's the language being requested? Can be used in both a class and an object context
773             sub _what_language {
774 25     25   93 my $self = shift;
775              
776 25 50       108 if(ref($self)) {
777 25 50       95 if($self->{_logger}) {
778 0         0 $self->{_logger}->trace('Entered _what_language');
779             }
780 25 100       150 if($self->{_what_language}) {
781 2 50       6 if($self->{_logger}) {
782 0         0 $self->{_logger}->trace('_what_language: returning cached value: ', $self->{_what_language});
783             }
784 2         8 return $self->{_what_language}; # Useful in case something changes the $info hash
785             }
786 23 50       83 if(my $info = $self->{_info}) {
787 0 0       0 if(my $rc = $info->lang()) {
788             # E.g. cgi-bin/script.cgi?lang=de
789 0 0       0 if($self->{_logger}) {
790 0         0 $self->{_logger}->trace("_what_language set language to $rc from the lang argument");
791             }
792 0         0 return $self->{_what_language} = $rc;
793             }
794             }
795             }
796              
797 23 50       77 if(defined($ENV{'LANG'})) {
798             # Running the script locally, presumably to debug, so set the language
799             # from the Locale
800 0 0       0 if(ref($self)) {
801 0         0 return $self->{_what_language} = $ENV{'LANG'};
802             }
803 0         0 return $ENV{'LANG'};
804             }
805              
806 23 100       100 if($ENV{'HTTP_ACCEPT_LANGUAGE'}) {
807 21 50       67 if(ref($self)) {
808 21         94 return $self->{_what_language} = $ENV{'HTTP_ACCEPT_LANGUAGE'};
809             }
810 0         0 return $ENV{'HTTP_ACCEPT_LANGUAGE'};
811             }
812             }
813              
814             =head2 country
815              
816             Returns the two character country code of the remote end in lower case.
817              
818             If L<IP::Country>, L<Geo::IPfree> or L<Geo::IP> is installed,
819             CGI::Lingua will make use of that, otherwise it will do a Whois lookup.
820             If you do not have any of those installed I recommend you make use of the
821             caching capability of CGI::Lingua.
822              
823             =cut
824              
825             sub country {
826 12     12 1 2254 my $self = shift;
827              
828 12 50       50 if($self->{_logger}) {
829 0         0 $self->{_logger}->trace('Entered country');
830             }
831              
832             # FIXME: If previous calls to country() return undef, we'll
833             # waste time going through again and no doubt returning undef
834             # again.
835 12 50       46 if($self->{_country}) {
836 0 0       0 if($self->{_logger}) {
837 0         0 $self->{_logger}->trace('quick return: ', $self->{_country});
838             }
839 0         0 return $self->{_country};
840             }
841              
842             # mod_geoip
843 12 50       47 if(defined($ENV{'GEOIP_COUNTRY_CODE'})) {
844 0         0 $self->{_country} = lc($ENV{'GEOIP_COUNTRY_CODE'});
845 0         0 return $self->{_country};
846             }
847 12 100 66     52 if(($ENV{'HTTP_CF_IPCOUNTRY'}) && ($ENV{'HTTP_CF_IPCOUNTRY'} ne 'XX')) {
848             # Hosted by Cloudfare
849 2         7 $self->{_country} = lc($ENV{'HTTP_CF_IPCOUNTRY'});
850 2         11 return $self->{_country};
851             }
852              
853 10         26 my $ip = $ENV{'REMOTE_ADDR'};
854              
855 10 50       49 return unless(defined($ip));
856              
857 0         0 require Data::Validate::IP;
858 0         0 Data::Validate::IP->import();
859              
860 0 0       0 unless(is_ipv4($ip)) {
861 0 0       0 if($ip eq '::1') {
862             # special case that is easy to handle
863 0         0 $ip = '127.0.0.1';
864             } else {
865 0         0 $self->_warn({
866             warning => "$ip isn't a valid IPv4 address\n"
867             });
868 0         0 return;
869             }
870             }
871 0 0       0 if(is_private_ip($ip)) {
872 0 0       0 if($self->{_logger}) {
873 0         0 $self->{_logger}->trace("Can't determine country from LAN connection $ip");
874             }
875 0         0 return;
876             }
877 0 0       0 if(is_loopback_ip($ip)) {
878 0 0       0 if($self->{_logger}) {
879 0         0 $self->{_logger}->trace("Can't determine country from loopback connection $ip");
880             }
881 0         0 return;
882             }
883              
884 0 0       0 if($self->{_cache}) {
885 0         0 $self->{_country} = $self->{_cache}->get($ip);
886 0 0       0 if($self->{_logger}) {
887 0 0       0 if(defined($self->{_country})) {
888 0         0 $self->{_logger}->debug("Get $ip from cache = $self->{_country}");
889             } else {
890 0         0 $self->{_logger}->debug("$ip isn't in the cache");
891             }
892             }
893 0 0       0 if(defined($self->{_country})) {
894 0         0 return $self->{_country};
895             }
896             }
897              
898 0 0       0 if($self->{_have_ipcountry} == -1) {
899 0 0       0 if(eval { require IP::Country; }) {
  0         0  
900 0         0 IP::Country->import();
901 0         0 $self->{_have_ipcountry} = 1;
902 0         0 $self->{_ipcountry} = IP::Country::Fast->new();
903             } else {
904 0         0 $self->{_have_ipcountry} = 0;
905             }
906             }
907 0 0       0 if($self->{_logger}) {
908 0         0 $self->{_logger}->debug("have_ipcountry $self->{_have_ipcountry}");
909             }
910              
911 0 0       0 if($self->{_have_ipcountry}) {
912 0         0 $self->{_country} = $self->{_ipcountry}->inet_atocc($ip);
913 0 0       0 if($self->{_country}) {
914 0         0 $self->{_country} = lc($self->{_country});
915             } else {
916 0         0 $self->_warn({
917             warning => "$ip is not known by IP::Country"
918             });
919             }
920             }
921 0 0       0 unless(defined($self->{_country})) {
922 0 0       0 if($self->{_have_geoip} == -1) {
923 0 0 0     0 if(($^O eq 'MSWin32') || (-r '/usr/local/share/GeoIP/GeoIP.dat')) {
924 0 0       0 if(eval { require Geo::IP; }) {
  0         0  
925 0         0 Geo::IP->import();
926 0         0 $self->{_have_geoip} = 1;
927             # GEOIP_STANDARD = 0, can't use that because you'll
928             # get a syntax error
929 0         0 $self->{_geoip} = Geo::IP->new(0);
930             } else {
931 0         0 $self->{_have_geoip} = 0;
932             }
933             } else {
934 0         0 $self->{_have_geoip} = 0;
935             }
936             }
937 0 0       0 if($self->{_have_geoip} == 1) {
938 0         0 $self->{_country} = $self->{_geoip}->country_code_by_addr($ip);
939             }
940 0 0       0 unless(defined($self->{_country})) {
941 0 0       0 if($self->{_have_geoipfree} == -1) {
942 0 0       0 if(eval { require Geo::IPfree; }) {
  0         0  
943 0         0 Geo::IPfree::IP->import();
944 0         0 $self->{_have_geoipfree} = 1;
945 0         0 $self->{_geoipfree} = Geo::IPfree->new();
946             } else {
947 0         0 $self->{_have_geoipfree} = 0;
948             }
949             }
950 0 0       0 if($self->{_have_geoipfree} == 1) {
951 0         0 $self->{_country} = lc(($self->{_geoipfree}->LookUp($ip))[0]);
952             }
953             }
954             }
955 0 0 0     0 if($self->{_country} && ($self->{_country} eq 'eu')) {
956 0         0 delete($self->{_country});
957             }
958 0 0 0     0 if((!$self->{_country}) &&
959 0         0 (eval { require LWP::Simple::WithCache; require JSON::Parse } )) {
  0         0  
960 0 0       0 if($self->{_logger}) {
961 0         0 $self->{_logger}->debug("Look up $ip on geoplugin");
962             }
963              
964 0         0 LWP::Simple::WithCache->import();
965 0         0 JSON::Parse->import();
966              
967 0 0       0 if(my $data = LWP::Simple::WithCache::get("http://www.geoplugin.net/json.gp?ip=$ip")) {
968 0         0 $self->{_country} = JSON::Parse::parse_json($data)->{'geoplugin_countryCode'};
969             }
970             }
971 0 0       0 unless($self->{_country}) {
972 0 0       0 if($self->{_logger}) {
973 0         0 $self->{_logger}->debug("Look up $ip on Whois");
974             }
975 0         0 require Net::Whois::IP;
976 0         0 Net::Whois::IP->import();
977              
978 0         0 my $whois;
979              
980 0         0 eval {
981             # Catch connection timeouts to
982             # whois.ripe.net by turning the carp
983             # into an error
984 0     0   0 local $SIG{__WARN__} = sub { die $_[0] };
  0         0  
985 0         0 $whois = Net::Whois::IP::whoisip_query($ip);
986             };
987 0 0 0     0 unless($@ || !defined($whois) || (ref($whois) ne 'HASH')) {
      0        
988 0 0       0 if(defined($whois->{Country})) {
    0          
989 0         0 $self->{_country} = $whois->{Country};
990             } elsif(defined($whois->{country})) {
991 0         0 $self->{_country} = $whois->{country};
992             }
993 0 0       0 if($self->{_country}) {
994 0 0 0     0 if($self->{_country} eq 'EU') {
    0          
995 0         0 delete($self->{_country});
996             } elsif(($self->{_country} eq 'US') && ($whois->{'StateProv'} eq 'PR')) {
997             # RT#131347: Inspite of what Whois thinks, Puerto Rico isn't in the US
998 0         0 $self->{_country} = 'pr';
999             }
1000             }
1001             }
1002              
1003 0 0       0 if($self->{_country}) {
1004 0 0       0 if($self->{_logger}) {
1005 0         0 $self->{_logger}->debug("Found up $ip on Net::WhoisIP as ", $self->{_country});
1006             }
1007             } else {
1008 0 0       0 if($self->{_logger}) {
1009 0         0 $self->{_logger}->debug("Look up $ip on IANA");
1010             }
1011              
1012 0         0 require Net::Whois::IANA;
1013 0         0 Net::Whois::IANA->import();
1014              
1015 0         0 my $iana = Net::Whois::IANA->new();
1016 0         0 eval {
1017 0         0 $iana->whois_query(-ip => $ip);
1018             };
1019 0 0       0 unless ($@) {
1020 0         0 $self->{_country} = $iana->country();
1021 0 0       0 if($self->{_logger}) {
1022 0         0 $self->{_logger}->debug("IANA reports $ip as ", $self->{_country});
1023             }
1024             }
1025             }
1026              
1027 0 0       0 if($self->{_country}) {
1028             # 190.24.1.122 has carriage return in its WHOIS record
1029 0         0 $self->{_country} =~ s/[\r\n]//g;
1030 0 0       0 if($self->{_country} =~ /^(..)\s*#/) {
1031             # Remove comments in the Whois record
1032 0         0 $self->{_country} = $1;
1033             }
1034             }
1035             # TODO - try freegeoip.net if whois has failed
1036             }
1037              
1038 0 0       0 if($self->{_country}) {
1039 0         0 $self->{_country} = lc($self->{_country});
1040 0 0       0 if($self->{_country} eq 'hk') {
    0          
1041             # Hong Kong is no longer a country, but Whois thinks
1042             # it is - try "whois 218.213.130.87"
1043 0         0 $self->{_country} = 'cn';
1044             } elsif($self->{_country} eq 'eu') {
1045 0         0 require Net::Subnet;
1046              
1047             # RT-86809, Baidu claims it's in EU not CN
1048 0         0 Net::Subnet->import();
1049 0 0       0 if(subnet_matcher('185.10.104.0/22')->($ip)) {
1050 0         0 $self->{_country} = 'cn';
1051             } else {
1052             # There is no country called 'eu'
1053 0         0 $self->_warn({
1054             warning => "$ip has country of eu"
1055             });
1056 0         0 $self->{_country} = 'Unknown';
1057             }
1058             }
1059 0 0       0 if($self->{_cache}) {
1060 0 0       0 if($self->{_logger}) {
1061 0         0 $self->{_logger}->debug("Set $ip to $self->{_country}");
1062             }
1063 0         0 $self->{_cache}->set($ip, $self->{_country}, '1 hour');
1064             }
1065             }
1066              
1067 0         0 return $self->{_country};
1068             }
1069              
1070             =head2 locale
1071              
1072             HTTP doesn't have a way of transmitting a browser's localisation information
1073             which would be useful for default currency, date formatting etc.
1074              
1075             This method attempts to detect the information, but it is a best guess
1076             and is not 100% reliable. But it's better than nothing ;-)
1077              
1078             Returns a L<Locale::Object::Country> object.
1079              
1080             To be clear, if you're in the US and request the language in Spanish,
1081             and the site supports it, language() will return 'Spanish', and locale() will
1082             try to return the Locale::Object::Country for the US.
1083              
1084             =cut
1085              
1086             sub locale {
1087 1     1 1 3 my $self = shift;
1088              
1089 1 50       6 if($self->{_locale}) {
1090 0         0 return $self->{_locale};
1091             }
1092              
1093             # First try from the User Agent. Probably only works with Mozilla and
1094             # Safari. I don't know about Opera. It won't work with IE or Chrome.
1095 1         5 my $agent = $ENV{'HTTP_USER_AGENT'};
1096 1         2 my $country;
1097 1 50 33     16 if(defined($agent) && ($agent =~ /\((.+)\)/)) {
1098 1         8 foreach(split(/;/, $1)) {
1099 5         9 my $candidate = $_;
1100              
1101 5         17 $candidate =~ s/^\s//g;
1102 5         10 $candidate =~ s/\s$//g;
1103 5 50       18 if($candidate =~ /^[a-zA-Z]{2}-([a-zA-Z]{2})$/) {
1104 0         0 local $SIG{__WARN__} = undef;
1105 0         0 my $c = $self->_code2country($1);
1106 0 0       0 if($c) {
1107 0         0 $self->{_locale} = $c;
1108 0         0 return $c;
1109             }
1110             # carp "Warning: unknown country $1 derived from $candidate in HTTP_USER_AGENT ($agent)";
1111             }
1112             }
1113              
1114 1 50       4 if(eval { require HTTP::BrowserDetect; } ) {
  1         1174  
1115 1         21315 HTTP::BrowserDetect->import();
1116 1         11 my $browser = HTTP::BrowserDetect->new($agent);
1117              
1118 1 50 33     334 if($browser && $browser->country()) {
1119 1         149 my $c = $self->_code2country($browser->country());
1120 1 50       6 if($c) {
1121 1         5 $self->{_locale} = $c;
1122 1         26 return $c;
1123             }
1124             }
1125             }
1126             }
1127              
1128             # Try from the IP address
1129 0         0 $country = $self->country();
1130              
1131 0 0       0 if($country) {
1132 0         0 $country =~ s/[\r\n]//g;
1133              
1134 0         0 my $c;
1135 0         0 eval {
1136 0     0   0 local $SIG{__WARN__} = sub { die $_[0] };
  0         0  
1137 0         0 $c = $self->_code2country($country);
1138             };
1139 0 0       0 unless($@) {
1140 0 0       0 if($c) {
1141 0         0 $self->{_locale} = $c;
1142 0         0 return $c;
1143             }
1144             }
1145             }
1146              
1147             # Try mod_geoip
1148 0 0       0 if(defined($ENV{'GEOIP_COUNTRY_CODE'})) {
1149 0         0 $country = $ENV{'GEOIP_COUNTRY_CODE'};
1150 0         0 my $c = $self->_code2country($country);
1151 0 0       0 if($c) {
1152 0         0 $self->{_locale} = $c;
1153 0         0 return $c;
1154             }
1155             }
1156 0         0 return (); # returns undef
1157             }
1158              
1159             =head2 time_zone
1160              
1161             Returns the timezone of the web client.
1162              
1163             If L<Geo::IP> is installed,
1164             CGI::Lingua will make use of that, otherwise it will use ip-api.com
1165              
1166             =cut
1167              
1168             sub time_zone {
1169 0     0 1 0 my $self = shift;
1170              
1171 0 0       0 if($self->{_logger}) {
1172 0         0 $self->{_logger}->trace('Entered time_zone');
1173             }
1174 0 0       0 if($self->{_timezone}) {
1175 0 0       0 if($self->{_logger}) {
1176 0         0 $self->{_logger}->trace('quick return: ', $self->{_timezone});
1177             }
1178 0         0 return $self->{_timezone};
1179             }
1180              
1181 0 0       0 if($self->{_have_geoip} == -1) {
1182 0 0 0     0 if(($^O eq 'MSWin32') || (-r '/usr/local/share/GeoIP/GeoIP.dat')) {
1183 0 0       0 if(eval { require Geo::IP; }) {
  0         0  
1184 0         0 Geo::IP->import();
1185 0         0 $self->{_have_geoip} = 1;
1186             # GEOIP_STANDARD = 0, can't use that because you'll
1187             # get a syntax error
1188 0         0 $self->{_geoip} = Geo::IP->new(0);
1189             } else {
1190 0         0 $self->{_have_geoip} = 0;
1191             }
1192             } else {
1193 0         0 $self->{_have_geoip} = 0;
1194             }
1195             }
1196 0 0       0 if(my $ip = $ENV{'REMOTE_ADDR'}) {
1197 0 0       0 if($self->{_have_geoip} == 1) {
1198 0         0 $self->{_timezone} = $self->{_geoip}->time_zone($ip);
1199             }
1200 0 0       0 if(!$self->{_timezone}) {
1201 0 0       0 if(eval { require LWP::Simple::WithCache; require JSON::Parse } ) {
  0         0  
  0         0  
1202 0 0       0 if($self->{_logger}) {
1203 0         0 $self->{_logger}->debug("Look up $ip on ip-api.com");
1204             }
1205              
1206 0         0 LWP::Simple::WithCache->import();
1207 0         0 JSON::Parse->import();
1208              
1209 0 0       0 if(my $data = LWP::Simple::WithCache::get("http://ip-api.com/json/$ip")) {
1210 0         0 $self->{_timezone} = JSON::Parse::parse_json($data)->{'timezone'};
1211             }
1212             } else {
1213 0         0 Carp::croak('You must have LWP::Simple::WithCache installed to connect to ip-api.com');
1214             }
1215             }
1216             } else {
1217             # Not a remote connection
1218 0 0       0 if(open(my $fin, '<', '/etc/timezone')) {
1219 0         0 my $tz = <$fin>;
1220 0         0 chomp $tz;
1221 0         0 $self->{_timezone} = $tz;
1222             } else {
1223 0         0 $self->{_timezone} = DateTime::TimeZone::Local->TimeZone()->name();
1224             }
1225             }
1226              
1227 0         0 return $self->{_timezone};
1228             }
1229              
1230             # Wrapper to Locale::Language::code2language which makes use of the cache
1231             sub _code2language
1232             {
1233 20     20   66 my ($self, $code) = @_;
1234              
1235 20 50       59 return unless($code);
1236 20 50       74 if($self->{_logger}) {
1237 0 0       0 if(defined($self->{_country})) {
1238 0         0 $self->{_logger}->trace("_code2language $code, country ", $self->{_country});
1239             } else {
1240 0         0 $self->{_logger}->trace("_code2language $code");
1241             }
1242             }
1243 20 50       61 unless($self->{_cache}) {
1244 20         127 return Locale::Language::code2language($code);
1245             }
1246 0         0 my $from_cache = $self->{_cache}->get("code2language/$code");
1247 0 0       0 if($from_cache) {
1248 0 0       0 if($self->{_logger}) {
1249 0         0 $self->{_logger}->trace("_code2language found in cache $from_cache");
1250             }
1251 0         0 return $from_cache;
1252             }
1253 0 0       0 if($self->{_logger}) {
1254 0         0 $self->{_logger}->trace('_code2language not in cache, storing');
1255             }
1256 0         0 return $self->{_cache}->set("code2language/$code", Locale::Language::code2language($code), '1 month');
1257             }
1258              
1259             # Wrapper to Locale::Object::Country allowing for persistance to be added
1260             sub _code2country
1261             {
1262 18     18   103 my ($self, $code) = @_;
1263              
1264 18 50       60 return unless($code);
1265 18 50       64 if($self->{_logger}) {
1266 0 0       0 if($self->{_country}) {
1267 0         0 $self->{_logger}->trace("_code2country $code, country ", $self->{_country});
1268             } else {
1269 0         0 $self->{_logger}->trace("_code2country $code");
1270             }
1271             }
1272             local $SIG{__WARN__} = sub {
1273 1 50   1   984 if($_[0] !~ /No result found in country table/) {
1274 0         0 warn $_[0];
1275             }
1276 18         168 };
1277 18         249 my $rc = Locale::Object::Country->new(code_alpha2 => $code);
1278 18         2979319 local $SIG{__WARN__} = 'DEFAULT';
1279 18         179 return $rc;
1280             }
1281              
1282             # Wrapper to Locale::Object::Country->name which makes use of the cache
1283             sub _code2countryname
1284             {
1285 13     13   44 my ($self, $code) = @_;
1286              
1287 13 50       46 return unless($code);
1288 13 50       61 if($self->{_logger}) {
1289 0         0 $self->{_logger}->trace("_code2countryname $code");
1290             }
1291 13 50       48 unless($self->{_cache}) {
1292 13         49 my $country = $self->_code2country($code);
1293 13 50       62 if(defined($country)) {
1294 13         66 return $country->name;
1295             }
1296 0           return;
1297             }
1298 0           my $from_cache = $self->{_cache}->get("code2countryname/$code");
1299 0 0         if($from_cache) {
1300 0 0         if($self->{_logger}) {
1301 0           $self->{_logger}->trace("_code2countryname found in cache $from_cache");
1302             }
1303 0           return $from_cache;
1304             }
1305 0 0         if($self->{_logger}) {
1306 0           $self->{_logger}->trace('_code2countryname not in cache, storing');
1307             }
1308 0           my $country = $self->_code2country($code);
1309 0 0         if(defined($country)) {
1310 0           return $self->{_cache}->set("code2countryname/$code", $country->name, '1 month');
1311             }
1312             }
1313              
1314             =head1 AUTHOR
1315              
1316             Nigel Horne, C<< <njh at bandsman.co.uk> >>
1317              
1318             =head1 BUGS
1319              
1320             If HTTP_ACCEPT_LANGUAGE is 3 characters, e.g., es-419,
1321             sublanguage() returns undef.
1322              
1323             Please report any bugs or feature requests to C<bug-cgi-lingua at rt.cpan.org>,
1324             or through the web interface at
1325             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Lingua>.
1326             I will be notified, and then you'll
1327             automatically be notified of progress on your bug as I make changes.
1328              
1329             =head1 SEE ALSO
1330              
1331             L<Locale::Country>
1332             L<HTTP::BrowserDetect>
1333              
1334             =head1 SUPPORT
1335              
1336             You can find documentation for this module with the perldoc command.
1337              
1338             perldoc CGI::Lingua
1339              
1340             You can also look for information at:
1341              
1342             =over 4
1343              
1344             =item * MetaCPAN
1345              
1346             L<https://metacpan.org/release/CGI-Lingua>
1347              
1348             =item * RT: CPAN's request tracker
1349              
1350             L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-Lingua>
1351              
1352             =item * CPANTS
1353              
1354             L<http://cpants.cpanauthors.org/dist/CGI-Lingua>
1355              
1356             =item * CPAN Testers' Matrix
1357              
1358             L<http://matrix.cpantesters.org/?dist=CGI-Lingua>
1359              
1360             =item * CPAN Ratings
1361              
1362             L<http://cpanratings.perl.org/d/CGI-Lingua>
1363              
1364             =item * CPAN Testers Dependencies
1365              
1366             L<http://deps.cpantesters.org/?module=CGI::Lingua>
1367              
1368             =back
1369              
1370             =head1 ACKNOWLEDGEMENTS
1371              
1372             =head1 LICENSE AND COPYRIGHT
1373              
1374             Copyright 2010-2021 Nigel Horne.
1375              
1376             This program is released under the following licence: GPL2
1377              
1378             =cut
1379              
1380             1; # End of CGI::Lingua