CGI-Lingua
view release on metacpan or search on metacpan
Input: none beyond $self
Returns: Str - e.g. "English (United Kingdom)" or "Unknown"
## country
Returns the two-character country code of the remote end in lowercase.
If [IP::Country](https://metacpan.org/pod/IP%3A%3ACountry), [Geo::IPfree](https://metacpan.org/pod/Geo%3A%3AIPfree) or [Geo::IP](https://metacpan.org/pod/Geo%3A%3AIP) is installed,
CGI::Lingua will make use of that, otherwise, it will do a Whois lookup.
If you do not have any of those installed I recommend you use the
caching capability of CGI::Lingua.
### API SPECIFICATION
Input: none beyond $self
Returns: Str (2 lowercase chars) | undef
'Unknown' is only returned in the Baidu-EU special case via _handle_eu_country.
### EXAMPLE
# With mod_geoip (fastest - no IP lookup at all):
local $ENV{GEOIP_COUNTRY_CODE} = 'DE';
print $l->country(); # "de"
# With REMOTE_ADDR and IP::Country installed:
local $ENV{REMOTE_ADDR} = '8.8.8.8';
print $l->country(); # "us" (depends on geo database)
### MESSAGES
"GEOIP_COUNTRY_CODE contains an invalid country code; ignoring"
"HTTP_CF_IPCOUNTRY contains an invalid country code; ignoring"
"X.X.X.X isn't a valid IP address"
"Can't determine country from LAN connection X"
"Can't determine country from loopback connection X"
"cache contains a numeric country: N"
"IP matches to a numeric country"
### PSEUDOCODE
1. Return cached _country if set
2. Check GEOIP_COUNTRY_CODE env var (mod_geoip); validate /^[A-Z]{2}$/
3. Check HTTP_CF_IPCOUNTRY (Cloudflare); skip 'XX'; validate /^[A-Z]{2}$/
4. Untaint and validate REMOTE_ADDR; return undef if absent or invalid
5. Skip private and loopback IPs (return undef)
6. Check CHI cache; return cached value if present
7. Try IP::Country::Fast (local DB, fastest)
8. Try Geo::IP (local DB)
9. Try Geo::IPfree (local DB, skip $BROKEN_GEOIPFREE)
10. Try geoplugin.net JSON API (LWP::Simple::WithCache or LWP::Simple)
11. Last resort: Net::Whois::IP then Net::Whois::IANA
12. Sanitise: discard numeric, normalise HK->CN, handle EU special case
13. Store in CHI cache; return result
## 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 [Locale::Object::Country](https://metacpan.org/pod/Locale%3A%3AObject%3A%3ACountry) object.
### 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"
}
### API SPECIFICATION
Input: none beyond $self
Returns: Locale::Object::Country | undef
### 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
## time\_zone
Returns the timezone of the web client.
If [Geo::IP](https://metacpan.org/pod/Geo%3A%3AIP) is installed,
CGI::Lingua will make use of that, otherwise it will use [ip-api.com](https://metacpan.org/pod/ip-api.com)
### API SPECIFICATION
Input: none beyond $self
Returns: Str (IANA timezone name) | undef
### EXAMPLE
local $ENV{REMOTE_ADDR} = '8.8.8.8';
my $tz = $l->time_zone();
print $tz // 'unknown'; # e.g. "America/New_York"
### MESSAGES
"Couldn't determine the timezone"
"LWP::Simple::WithCache and LWP::Simple are both absent; cannot contact ip-api.com"
Returns undef rather than croaking; install either LWP variant to enable ip-api lookups.
### PSEUDOCODE
1. Return cached _timezone immediately if already computed
2. If REMOTE_ADDR is set:
a. Untaint and validate the IP
b. Try Geo::IP->time_zone() (local DB)
c. Try LWP::Simple::WithCache + JSON::Parse against ip-api.com
d. Fall back to LWP::Simple + JSON::Parse against ip-api.com
e. Warn and return undef if neither LWP variant is installed
- CPANTS
[http://cpants.cpanauthors.org/dist/CGI-Lingua](http://cpants.cpanauthors.org/dist/CGI-Lingua)
- CPAN Testers' Matrix
[http://matrix.cpantesters.org/?dist=CGI-Lingua](http://matrix.cpantesters.org/?dist=CGI-Lingua)
- CPAN Testers Dependencies
[http://deps.cpantesters.org/?module=CGI::Lingua](http://deps.cpantesters.org/?module=CGI::Lingua)
# FORMAL SPECIFICATION
## new
new : Class à Params â CGI::Lingua
â p : Params ⢠p.supported â â
â¹ result.language â (p.supported ⪠{'Unknown'})
## language
language : CGI::Lingua â Str
result â {name(l) | l â supported} ⪠{'Unknown'}
## sublanguage
sublanguage : CGI::Lingua -> Str | undef
result = country_name(sublanguage_code_alpha2(self))
when sublanguage_code_alpha2(self) is defined,
undef otherwise
## 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
## sublanguage\_code\_alpha2
sublanguage_code_alpha2 : CGI::Lingua -> Str(2) | undef
result = variety_code(matched_supported_entry) | undef
## 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
## 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
## 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
## time\_zone
time_zone : CGI::Lingua -> Str | undef
result is an IANA timezone name (e.g. 'Europe/London') or undef
## is\_rtl
is_rtl : CGI::Lingua â Bool
is_rtl(s) â language_code_alpha2(s) â RTL_LANGS
## text\_direction
text_direction : CGI::Lingua â {'rtl', 'ltr'}
text_direction(s) â is_rtl(s) ? 'rtl' : 'ltr'
## 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.
## 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}
# ACKNOWLEDGEMENTS
# LICENSE AND COPYRIGHT
Copyright 2010-2026 Nigel Horne.
Usage is subject to the GPL2 licence terms.
If you use it,
please let me know.
( run in 4.364 seconds using v1.01-cache-2.11-cpan-0fb53d1c279 )