1 2# Time-stamp: "2004-06-20 21:47:55 ADT" 3 4require 5; 5package I18N::LangTags::Detect; 6use strict; 7 8our ( $MATCH_SUPERS, $USING_LANGUAGE_TAGS, 9 $USE_LITERALS, $MATCH_SUPERS_TIGHTLY); 10 11BEGIN { unless(defined &DEBUG) { *DEBUG = sub () {0} } } 12 # define the constant 'DEBUG' at compile-time 13 14our $VERSION = "1.08"; 15our @ISA = (); 16use I18N::LangTags qw(alternate_language_tags locale2language_tag); 17 18sub _uniq { my %seen; return grep(!($seen{$_}++), @_); } 19sub _normalize { 20 my(@languages) = 21 map lc($_), 22 grep $_, 23 map {; $_, alternate_language_tags($_) } @_; 24 return _uniq(@languages) if wantarray; 25 return $languages[0]; 26} 27 28#--------------------------------------------------------------------------- 29# The extent of our functional interface: 30 31sub detect () { return __PACKAGE__->ambient_langprefs; } 32 33#=========================================================================== 34 35sub ambient_langprefs { # always returns things untainted 36 my $base_class = $_[0]; 37 38 return $base_class->http_accept_langs 39 if length( $ENV{'REQUEST_METHOD'} || '' ); # I'm a CGI 40 # it's off in its own routine because it's complicated 41 42 # Not running as a CGI: try to puzzle out from the environment 43 my @languages; 44 45 foreach my $envname (qw( LANGUAGE LC_ALL LC_MESSAGES LANG )) { 46 next unless $ENV{$envname}; 47 DEBUG and print "Noting \$$envname: $ENV{$envname}\n"; 48 push @languages, 49 map locale2language_tag($_), 50 # if it's a lg tag, fine, pass thru (untainted) 51 # if it's a locale ID, try converting to a lg tag (untainted), 52 # otherwise nix it. 53 54 split m/[,:]/, 55 $ENV{$envname} 56 ; 57 last; # first one wins 58 } 59 60 if($ENV{'IGNORE_WIN32_LOCALE'}) { 61 # no-op 62 } elsif(&_try_use('Win32::Locale')) { 63 # If we have that module installed... 64 push @languages, Win32::Locale::get_language() || '' 65 if defined &Win32::Locale::get_language; 66 } 67 return _normalize @languages; 68} 69 70#--------------------------------------------------------------------------- 71 72sub http_accept_langs { 73 # Deal with HTTP "Accept-Language:" stuff. Hassle. 74 # This code is more lenient than RFC 3282, which you must read. 75 # Hm. Should I just move this into I18N::LangTags at some point? 76 no integer; 77 78 my $in = (@_ > 1) ? $_[1] : $ENV{'HTTP_ACCEPT_LANGUAGE'}; 79 # (always ends up untainting) 80 81 return() unless defined $in and length $in; 82 83 $in =~ s/\([^\)]*\)//g; # nix just about any comment 84 85 if( $in =~ m/^\s*([a-zA-Z][-a-zA-Z]+)\s*$/s ) { 86 # Very common case: just one language tag 87 return _normalize $1; 88 } elsif( $in =~ m/^\s*[a-zA-Z][-a-zA-Z]+(?:\s*,\s*[a-zA-Z][-a-zA-Z]+)*\s*$/s ) { 89 # Common case these days: just "foo, bar, baz" 90 return _normalize( $in =~ m/([a-zA-Z][-a-zA-Z]+)/g ); 91 } 92 93 # Else it's complicated... 94 95 $in =~ s/\s+//g; # Yes, we can just do without the WS! 96 my @in = $in =~ m/([^,]+)/g; 97 my %pref; 98 99 my $q; 100 foreach my $tag (@in) { 101 next unless $tag =~ 102 m/^([a-zA-Z][-a-zA-Z]+) 103 (?: 104 ;q= 105 ( 106 \d* # a bit too broad of a RE, but so what. 107 (?: 108 \.\d+ 109 )? 110 ) 111 )? 112 $ 113 /sx 114 ; 115 $q = (defined $2 and length $2) ? $2 : 1; 116 #print "$1 with q=$q\n"; 117 push @{ $pref{$q} }, lc $1; 118 } 119 120 return _normalize( 121 # Read off %pref, in descending key order... 122 map @{$pref{$_}}, 123 sort {$b <=> $a} 124 keys %pref 125 ); 126} 127 128#=========================================================================== 129 130my %tried = (); 131 # memoization of whether we've used this module, or found it unusable. 132 133sub _try_use { # Basically a wrapper around "require Modulename" 134 # "Many men have tried..." "They tried and failed?" "They tried and died." 135 return $tried{$_[0]} if exists $tried{$_[0]}; # memoization 136 137 my $module = $_[0]; # ASSUME sane module name! 138 { no strict 'refs'; 139 no warnings 'once'; 140 return($tried{$module} = 1) 141 if %{$module . "::Lexicon"} or @{$module . "::ISA"}; 142 # weird case: we never use'd it, but there it is! 143 } 144 145 print " About to use $module ...\n" if DEBUG; 146 { 147 local $SIG{'__DIE__'}; 148 local @INC = @INC; 149 pop @INC if $INC[-1] eq '.'; 150 eval "require $module"; # used to be "use $module", but no point in that. 151 } 152 if($@) { 153 print "Error using $module \: $@\n" if DEBUG > 1; 154 return $tried{$module} = 0; 155 } else { 156 print " OK, $module is used\n" if DEBUG; 157 return $tried{$module} = 1; 158 } 159} 160 161#--------------------------------------------------------------------------- 1621; 163__END__ 164 165 166=head1 NAME 167 168I18N::LangTags::Detect - detect the user's language preferences 169 170=head1 SYNOPSIS 171 172 use I18N::LangTags::Detect; 173 my @user_wants = I18N::LangTags::Detect::detect(); 174 175=head1 DESCRIPTION 176 177It is a common problem to want to detect what language(s) the user would 178prefer output in. 179 180=head1 FUNCTIONS 181 182This module defines one public function, 183C<I18N::LangTags::Detect::detect()>. This function is not exported 184(nor is even exportable), and it takes no parameters. 185 186In scalar context, the function returns the most preferred language 187tag (or undef if no preference was seen). 188 189In list context (which is usually what you want), 190the function returns a 191(possibly empty) list of language tags representing (best first) what 192languages the user apparently would accept output in. You will 193probably want to pass the output of this through 194C<I18N::LangTags::implicate_supers_tightly(...)> 195or 196C<I18N::LangTags::implicate_supers(...)>, like so: 197 198 my @languages = 199 I18N::LangTags::implicate_supers_tightly( 200 I18N::LangTags::Detect::detect() 201 ); 202 203 204=head1 ENVIRONMENT 205 206This module looks at several environment variables: 207REQUEST_METHOD, HTTP_ACCEPT_LANGUAGE, 208LANGUAGE, LC_ALL, LC_MESSAGES, and LANG. 209 210It will also use the L<Win32::Locale> module, if it's installed 211and IGNORE_WIN32_LOCALE is not set to a true value in the 212environment. 213 214 215=head1 SEE ALSO 216 217L<I18N::LangTags>, L<Win32::Locale>, L<Locale::Maketext>. 218 219(This module's core code started out as a routine in Locale::Maketext; 220but I moved it here once I realized it was more generally useful.) 221 222 223=head1 COPYRIGHT 224 225Copyright (c) 1998-2004 Sean M. Burke. All rights reserved. 226 227This library is free software; you can redistribute it and/or 228modify it under the same terms as Perl itself. 229 230The programs and documentation in this dist are distributed in 231the hope that they will be useful, but without any warranty; without 232even the implied warranty of merchantability or fitness for a 233particular purpose. 234 235 236=head1 AUTHOR 237 238Sean M. Burke C<sburke@cpan.org> 239 240=cut 241 242# a tip: Put a bit of chopped up pickled ginger in your salad. It's tasty! 243