File Coverage

blib/lib/CGI/Lingua.pm
Criterion Covered Total %
statement 207 601 34.4
branch 112 452 24.7
condition 23 115 20.0
subroutine 21 29 72.4
pod 11 11 100.0
total 374 1208 30.9


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