File Coverage

blib/lib/CGI/Lingua.pm
Criterion Covered Total %
statement 207 595 34.7
branch 109 444 24.5
condition 23 112 20.5
subroutine 21 29 72.4
pod 11 11 100.0
total 371 1191 31.1


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