CGI-Lingua
view release on metacpan or search on metacpan
lib/CGI/Lingua.pm view on Meta::CPAN
my $in_baidu;
if(eval { require Net::Subnet; Net::Subnet->import(); 1 }) {
$in_baidu = subnet_matcher($BAIDU_SUBNET)->($ip);
} else {
$in_baidu = _in_baidu_subnet($ip);
}
if($in_baidu) {
$self->{_country} = 'cn';
} else {
$self->_info("$ip has country of eu");
$self->{_country} = 'Unknown';
}
}
# ââ _load_geoip âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
# Purpose: Probe for the Geo::IP database file and the Geo::IP module;
# set _have_geoip and initialise _geoip on success.
# Entry: _have_geoip must be GEO_UNKNOWN.
# Exit: _have_geoip set to GEO_PRESENT or GEO_ABSENT.
# Side Effects: Requires Geo::IP; opens GeoIP.dat.
sub _load_geoip
{
my $self = shift;
# Check for the database file before even trying to load the module
# (avoids noisy errors on Windows â CPANTESTERS report 54117bd0)
my $db_present = (
(($^O eq 'MSWin32') && (-r 'c:/GeoIP/GeoIP.dat'))
|| (-r '/usr/local/share/GeoIP/GeoIP.dat')
|| (-r '/usr/share/GeoIP/GeoIP.dat')
);
unless($db_present) {
$self->{_have_geoip} = $GEO_ABSENT;
return;
}
eval { require Geo::IP };
if($@) {
$self->{_have_geoip} = $GEO_ABSENT;
return;
}
# No ->import(): Geo::IP->open() and Geo::IP->new() are class methods; import unneeded.
$self->{_have_geoip} = $GEO_PRESENT;
# GEOIP_STANDARD = 0 (can't use the constant name directly)
if(-r '/usr/share/GeoIP/GeoIP.dat') {
$self->{_geoip} = Geo::IP->open('/usr/share/GeoIP/GeoIP.dat', 0);
} else {
$self->{_geoip} = Geo::IP->new(0);
}
}
=head2 locale
HTTP doesn't have a way of transmitting a browser's localisation information
which would be useful for default currency, date formatting, etc.
This method attempts to detect the information, but it is a best guess
and is not 100% reliable. But it's better than nothing ;-)
Returns a L<Locale::Object::Country> object.
=head3 EXAMPLE
local $ENV{REMOTE_ADDR} = '8.8.8.8';
my $locale = $l->locale();
if (defined $locale) {
print $locale->name(); # e.g. "United States"
print $locale->currency_code(); # e.g. "USD"
}
=head3 API SPECIFICATION
Input: none beyond $self
Returns: Locale::Object::Country | undef
=head3 PSEUDOCODE
1. Return cached _locale immediately if already computed
2. Parse HTTP_USER_AGENT parenthetical for xx-YY language tag
3. Try HTTP::BrowserDetect on the full User-Agent string
4. Fall back to country() IP lookup
5. Fall back to GEOIP_COUNTRY_CODE env var (ISO 3166-1 validated)
6. Return undef if all strategies fail
=cut
sub locale {
my $self = shift;
return $self->{_locale} if $self->{_locale};
# Validate and untaint HTTP_USER_AGENT before passing to any parser.
# The User-Agent header is attacker-controlled; apply the same discipline
# as HTTP_ACCEPT_LANGUAGE. Printable ASCII (0x20-0x7e), bounded length.
my $agent;
if(defined(my $raw_agent = $ENV{'HTTP_USER_AGENT'})) {
if($raw_agent =~ /^([\x20-\x7e]{1,$UA_MAX})$/a) {
$agent = $1; # untainted
} else {
$self->_warn({ warning => 'HTTP_USER_AGENT contains invalid characters or exceeds length limit; ignoring' });
}
}
# First try: parse the language tag from the User-Agent parenthetical
if(defined($agent) && ($agent =~ /\((.+)\)/)) {
foreach(split(/;/, $1)) {
my $candidate = $_;
$candidate =~ s/^\s+|\s+$//g; # trim both ends
if($candidate =~ /^[a-zA-Z]{2}-([a-zA-Z]{2})$/) {
local $SIG{__WARN__} = undef;
if(my $c = $self->_code2country($1)) {
$self->{_locale} = $c;
return $c;
}
}
}
lib/CGI/Lingua.pm view on Meta::CPAN
=item * CPAN Testers' Matrix
L<http://matrix.cpantesters.org/?dist=CGI-Lingua>
=item * CPAN Testers Dependencies
L<http://deps.cpantesters.org/?module=CGI::Lingua>
=back
=encoding utf-8
=head1 FORMAL SPECIFICATION
=head2 new
new : Class à Params â CGI::Lingua
â p : Params ⢠p.supported â â
â¹ result.language â (p.supported ⪠{'Unknown'})
=head2 language
language : CGI::Lingua â Str
result â {name(l) | l â supported} ⪠{'Unknown'}
=head2 sublanguage
sublanguage : CGI::Lingua -> Str | undef
result = country_name(sublanguage_code_alpha2(self))
when sublanguage_code_alpha2(self) is defined,
undef otherwise
=head2 language_code_alpha2
language_code_alpha2 : CGI::Lingua -> Str(2) | undef
result = base_code(matched_supported_entry)
when a supported language was matched, undef otherwise
=head2 sublanguage_code_alpha2
sublanguage_code_alpha2 : CGI::Lingua -> Str(2) | undef
result = variety_code(matched_supported_entry) | undef
=head2 requested_language
requested_language : CGI::Lingua -> Str
result = name(base) + " (" + name(variety) + ")"
when variety is known,
= name(base) when no variety,
= 'Unknown' when no language detected
=head2 country
country : CGI::Lingua -> Str(2,lowercase) | undef
-- 'Unknown' returned only in the EU/Baidu special case
result = lc(code) where code satisfies ISO 3166-1 alpha-2
| undef when IP is private, loopback, or unresolvable
=head2 locale
locale : CGI::Lingua -> Locale::Object::Country | undef
-- Best-guess detection; not guaranteed accurate.
result = first defined value from:
1. UA parenthetical language tag
2. HTTP::BrowserDetect country
3. country() IP lookup
4. GEOIP_COUNTRY_CODE env var
=head2 time_zone
time_zone : CGI::Lingua -> Str | undef
result is an IANA timezone name (e.g. 'Europe/London') or undef
=head2 is_rtl
is_rtl : CGI::Lingua â Bool
is_rtl(s) â language_code_alpha2(s) â RTL_LANGS
=head2 text_direction
text_direction : CGI::Lingua â {'rtl', 'ltr'}
text_direction(s) â is_rtl(s) ? 'rtl' : 'ltr'
=head2 plural_category
plural_category : CGI::Lingua x N -> PluralCategory
plural_category(s, n) = PLURAL_RULES[language_code_alpha2(s)](trunc(n))
-- Falls back to English rule (n=1 -> 'one'; else 'other')
-- when language_code_alpha2(s) is undef or not in the rules table.
=head2 translation_file
translation_file : CGI::Lingua à Path à Ext â Path | undef
translation_file(s, d, e) â
first p â candidates(s) ⢠â file d/p.e
where candidates(s) = [lang(s)-sublang(s), lang(s)] \ {undef}
=head1 ACKNOWLEDGEMENTS
=head1 LICENSE AND COPYRIGHT
Copyright 2010-2026 Nigel Horne.
Usage is subject to the GPL2 licence terms.
If you use it,
please let me know.
=cut
1; # End of CGI::Lingua
( run in 1.119 second using v1.01-cache-2.11-cpan-0fb53d1c279 )