📄 langtags.pm
字号:
You have a data file that expresses greetings in different languages.Its format is "[language tag]=[how to say 'Hello']", like: en-US=Hiho fr=Bonjour i-mingo=Hau'And suppose you write a program that reads that file and then runs asa daemon, answering client requests that specify a language tag andthen expect the string that says how to greet in that language. So aninteraction looks like: greeting-client asks: fr greeting-server answers: BonjourSo far so good. But suppose the way you're implementing this is: my %greetings; die unless open(IN, "<in.dat"); while(<IN>) { chomp; next unless /^([^=]+)=(.+)/s; my($lang, $expr) = ($1, $2); $greetings{$lang} = $expr; } close(IN);at which point %greetings has the contents: "en-US" => "Hiho" "fr" => "Bonjour" "i-mingo" => "Hau'"And suppose then that you answer client requests for language $wantedby just looking up $greetings{$wanted}.If the client asks for "fr", that will look up successfully in%greetings, to the value "Bonjour". And if the client asks for"i-mingo", that will look up successfully in %greetings, to the value"Hau'".But if the client asks for "i-Mingo" or "x-mingo", or "Fr", then thelookup in %greetings fails. That's the Wrong Thing.You could instead do lookups on $wanted with: use I18N::LangTags qw(same_language_tag); my $response = ''; foreach my $l2 (keys %greetings) { if(same_language_tag($wanted, $l2)) { $response = $greetings{$l2}; last; } }But that's rather inefficient. A better way to do it is to start yourprogram with: use I18N::LangTags qw(encode_language_tag); my %greetings; die unless open(IN, "<in.dat"); while(<IN>) { chomp; next unless /^([^=]+)=(.+)/s; my($lang, $expr) = ($1, $2); $greetings{ encode_language_tag($lang) } = $expr; } close(IN);and then just answer client requests for language $wanted by justlooking up $greetings{encode_language_tag($wanted)}And that does the Right Thing.=cutsub encode_language_tag { # Only similarity_language_tag() is allowed to analyse encodings! ## Changes in the language tagging standards may have to be reflected here. my($tag) = $_[0] || return undef; return undef unless &is_language_tag($tag); # For the moment, these legacy variances are few enough that # we can just handle them here with regexps. $tag =~ s/^iw\b/he/i; # Hebrew $tag =~ s/^in\b/id/i; # Indonesian $tag =~ s/^cre\b/cr/i; # Cree $tag =~ s/^jw\b/jv/i; # Javanese $tag =~ s/^[ix]-lux\b/lb/i; # Luxemburger $tag =~ s/^[ix]-navajo\b/nv/i; # Navajo $tag =~ s/^ji\b/yi/i; # Yiddish # SMB 2003 -- Hm. There's a bunch of new XXX->YY variances now, # but maybe they're all so obscure I can ignore them. "Obscure" # meaning either that the language is obscure, and/or that the # XXX form was extant so briefly that it's unlikely it was ever # used. I hope. # # These go FROM the simplex to complex form, to get # similarity-comparison right. And that's okay, since # similarity_language_tag is the only thing that # analyzes our output. $tag =~ s/^[ix]-hakka\b/zh-hakka/i; # Hakka $tag =~ s/^nb\b/no-bok/i; # BACKWARDS for Bokmal $tag =~ s/^nn\b/no-nyn/i; # BACKWARDS for Nynorsk $tag =~ s/^[xiXI]-//s; # Just lop off any leading "x/i-" return "~" . uc($tag);}#--------------------------------------------------------------------------=item * the function alternate_language_tags($lang1)This function, if given a language tag, returns all language tags thatare alternate forms of this language tag. (I.e., tags which refer tothe same language.) This is meant to handle legacy tags caused bythe minor changes in language tag standards over the years; andthe x-/i- alternation is also dealt with.Note that this function does I<not> try to equate new (and never-used,and unusable)ISO639-2 three-letter tags to old (and still in use) ISO639-1two-letter equivalents -- like "ara" -> "ar" -- because"ara" has I<never> been in use as an Internet language tag,and RFC 3066 stipulates that it never should be, since a shortertag ("ar") exists.Examples: alternate_language_tags('no-bok') is ('nb') alternate_language_tags('nb') is ('no-bok') alternate_language_tags('he') is ('iw') alternate_language_tags('iw') is ('he') alternate_language_tags('i-hakka') is ('zh-hakka', 'x-hakka') alternate_language_tags('zh-hakka') is ('i-hakka', 'x-hakka') alternate_language_tags('en') is () alternate_language_tags('x-mingo-tom') is ('i-mingo-tom') alternate_language_tags('x-klikitat') is ('i-klikitat') alternate_language_tags('i-klikitat') is ('x-klikitat')This function returns empty-list if given anything other than a formallyvalid language tag.=cutmy %alt = qw( i x x i I X X I );sub alternate_language_tags { my $tag = $_[0]; return() unless &is_language_tag($tag); my @em; # push 'em real goood! # For the moment, these legacy variances are few enough that # we can just handle them here with regexps. if( $tag =~ m/^[ix]-hakka\b(.*)/i) {push @em, "zh-hakka$1"; } elsif($tag =~ m/^zh-hakka\b(.*)/i) { push @em, "x-hakka$1", "i-hakka$1"; } elsif($tag =~ m/^he\b(.*)/i) { push @em, "iw$1"; } elsif($tag =~ m/^iw\b(.*)/i) { push @em, "he$1"; } elsif($tag =~ m/^in\b(.*)/i) { push @em, "id$1"; } elsif($tag =~ m/^id\b(.*)/i) { push @em, "in$1"; } elsif($tag =~ m/^[ix]-lux\b(.*)/i) { push @em, "lb$1"; } elsif($tag =~ m/^lb\b(.*)/i) { push @em, "i-lux$1", "x-lux$1"; } elsif($tag =~ m/^[ix]-navajo\b(.*)/i) { push @em, "nv$1"; } elsif($tag =~ m/^nv\b(.*)/i) { push @em, "i-navajo$1", "x-navajo$1"; } elsif($tag =~ m/^yi\b(.*)/i) { push @em, "ji$1"; } elsif($tag =~ m/^ji\b(.*)/i) { push @em, "yi$1"; } elsif($tag =~ m/^nb\b(.*)/i) { push @em, "no-bok$1"; } elsif($tag =~ m/^no-bok\b(.*)/i) { push @em, "nb$1"; } elsif($tag =~ m/^nn\b(.*)/i) { push @em, "no-nyn$1"; } elsif($tag =~ m/^no-nyn\b(.*)/i) { push @em, "nn$1"; } push @em, $alt{$1} . $2 if $tag =~ /^([XIxi])(-.+)/; return @em;}###########################################################################{ # Init %Panic... my @panic = ( # MUST all be lowercase! # Only large ("national") languages make it in this list. # If you, as a user, are so bizarre that the /only/ language # you claim to accept is Galician, then no, we won't do you # the favor of providing Catalan as a panic-fallback for # you. Because if I start trying to add "little languages" in # here, I'll just go crazy. # Scandinavian lgs. All based on opinion and hearsay. 'sv' => [qw(nb no da nn)], 'da' => [qw(nb no sv nn)], # I guess [qw(no nn nb)], [qw(no nn nb sv da)], 'is' => [qw(da sv no nb nn)], 'fo' => [qw(da is no nb nn sv)], # I guess # I think this is about the extent of tolerable intelligibility # among large modern Romance languages. 'pt' => [qw(es ca it fr)], # Portuguese, Spanish, Catalan, Italian, French 'ca' => [qw(es pt it fr)], 'es' => [qw(ca it fr pt)], 'it' => [qw(es fr ca pt)], 'fr' => [qw(es it ca pt)], # Also assume that speakers of the main Indian languages prefer # to read/hear Hindi over English [qw( as bn gu kn ks kok ml mni mr ne or pa sa sd te ta ur )] => 'hi', # Assamese, Bengali, Gujarati, [Hindi,] Kannada (Kanarese), Kashmiri, # Konkani, Malayalam, Meithei (Manipuri), Marathi, Nepali, Oriya, # Punjabi, Sanskrit, Sindhi, Telugu, Tamil, and Urdu. 'hi' => [qw(bn pa as or)], # I welcome finer data for the other Indian languages. # E.g., what should Oriya's list be, besides just Hindi? # And the panic languages for English is, of course, nil! # My guesses at Slavic intelligibility: ([qw(ru be uk)]) x 2, # Russian, Belarusian, Ukranian 'sr' => 'hr', 'hr' => 'sr', # Serb + Croat 'cs' => 'sk', 'sk' => 'cs', # Czech + Slovak 'ms' => 'id', 'id' => 'ms', # Malay + Indonesian 'et' => 'fi', 'fi' => 'et', # Estonian + Finnish #?? 'lo' => 'th', 'th' => 'lo', # Lao + Thai ); my($k,$v); while(@panic) { ($k,$v) = splice(@panic,0,2); foreach my $k (ref($k) ? @$k : $k) { foreach my $v (ref($v) ? @$v : $v) { push @{$Panic{$k} ||= []}, $v unless $k eq $v; } } }}=item * the function @langs = panic_languages(@accept_languages)This function takes a list of 0 or more languagetags that constitute a given user's Accept-Language list, andreturns a list of tags for I<other> (non-super)languages that are probably acceptable to the user, to beused I<if all else fails>.For example, if a user accepts only 'ca' (Catalan) and'es' (Spanish), and the documents/interfaces you haveavailable are just in German, Italian, and Chinese, thenthe user will most likely want the Italian one (and notthe Chinese or German one!), instead of gettingnothing. So C<panic_languages('ca', 'es')> returnsa list containing 'it' (Italian).English ('en') is I<always> in the return list, butwhether it's at the very end or not dependson the input languages. This function works by consultingan internal table that stipulates what commonlanguages are "close" to each other.A useful construct you might consider using is: @fallbacks = super_languages(@accept_languages); push @fallbacks, panic_languages( @accept_languages, @fallbacks, );=cutsub panic_languages { # When in panic or in doubt, run in circles, scream, and shout! my(@out, %seen); foreach my $t (@_) { next unless $t; next if $seen{$t}++; # so we don't return it or hit it again # push @out, super_languages($t); # nah, keep that separate push @out, @{ $Panic{lc $t} || next }; } return grep !$seen{$_}++, @out, 'en';}#---------------------------------------------------------------------------#---------------------------------------------------------------------------=item * the function implicate_supers( ...languages... )This takes a list of strings (which are presumed to be language-tags;strings that aren't, are ignored); and after each one, this functioninserts super-ordinate forms that don't already appear in the list.The original list, plus these insertions, is returned.In other words, it takes this: pt-br de-DE en-US fr pt-br-janeiroand returns this: pt-br pt de-DE de en-US en fr pt-br-janeiroThis function is most useful in the idiom implicate_supers( I18N::LangTags::Detect::detect() );(See L<I18N::LangTags::Detect>.)=item * the function implicate_supers_strictly( ...languages... )This works like C<implicate_supers> except that the implicatedforms are added to the end of the return list.In other words, implicate_supers_strictly takes a list of strings(which are presumed to be language-tags; strings that aren't, areignored) and after the whole given list, it inserts the super-ordinate forms of all given tags, minus any tags that already appear in the input list.In other words, it takes this: pt-br de-DE en-US fr pt-br-janeiroand returns this: pt-br de-DE en-US fr pt-br-janeiro pt de enThe reason this function has "_strictly" in its name is that whenyou're processing an Accept-Language list according to the RFCs, ifyou interpret the RFCs quite strictly, then you would useimplicate_supers_strictly, but for normal use (i.e., common-sense use,as far as I'm concerned) you'd use implicate_supers.=cutsub implicate_supers { my @languages = grep is_language_tag($_), @_; my %seen_encoded; foreach my $lang (@languages) { $seen_encoded{ I18N::LangTags::encode_language_tag($lang) } = 1 } my(@output_languages); foreach my $lang (@languages) { push @output_languages, $lang; foreach my $s ( I18N::LangTags::super_languages($lang) ) { # Note that super_languages returns the longest first. last if $seen_encoded{ I18N::LangTags::encode_language_tag($s) }; push @output_languages, $s; } } return uniq( @output_languages );}sub implicate_supers_strictly { my @tags = grep is_language_tag($_), @_; return uniq( @_, map super_languages($_), @_ );}###########################################################################1;__END__=back=head1 ABOUT LOWERCASINGI've considered making all the above functions that output languagetags return all those tags strictly in lowercase. Having all yourlanguage tags in lowercase does make some things easier. But youmight as well just lowercase as you like, or callC<encode_language_tag($lang1)> where appropriate.=head1 ABOUT UNICODE PLAINTEXT LANGUAGE TAGSIn some future version of I18N::LangTags, I plan to include supportfor RFC2482-style language tags -- which are basically just normallanguage tags with their ASCII characters shifted into Plane 14.=head1 SEE ALSO* L<I18N::LangTags::List|I18N::LangTags::List>* RFC 3066, C<ftp://ftp.isi.edu/in-notes/rfc3066.txt>, "Tags for theIdentification of Languages". (Obsoletes RFC 1766)* RFC 2277, C<ftp://ftp.isi.edu/in-notes/rfc2277.txt>, "IETF Policy onCharacter Sets and Languages".* RFC 2231, C<ftp://ftp.isi.edu/in-notes/rfc2231.txt>, "MIME ParameterValue and Encoded Word Extensions: Character Sets, Languages, andContinuations".* RFC 2482, C<ftp://ftp.isi.edu/in-notes/rfc2482.txt>, "Language Tagging in Unicode Plain Text".* Locale::Codes, inC<http://www.perl.com/CPAN/modules/by-module/Locale/>* ISO 639-2, "Codes for the representation of names of languages",including two-letter and three-letter codes,C<http://www.loc.gov/standards/iso639-2/langcodes.html>* The IANA list of registered languages (hopefully up-to-date),C<http://www.iana.org/assignments/language-tags>=head1 COPYRIGHTCopyright (c) 1998+ Sean M. Burke. All rights reserved.This library is free software; you can redistribute it and/ormodify it under the same terms as Perl itself.The programs and documentation in this dist are distributed inthe hope that they will be useful, but without any warranty; withouteven the implied warranty of merchantability or fitness for aparticular purpose.=head1 AUTHORSean M. Burke C<sburke@cpan.org>=cut
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -