我目前正在国际化一个大型Perl/Mason web应用程序(Perl 5.8.0, Mason 1.48, mod_perl &Apache)。在选择本地化模块时,我决定使用Locale::TextDomain而不是Locale::Maketext,主要是因为后者的复数形式支持不像我希望的那样好。
我在Locale::TextDomain遇到的问题是,它根据进程的语言环境来解析翻译使用哪个目录。当我意识到这一点时,我开始担心,如果我希望用户能够使用不同的语言环境,这将如何影响我的应用程序——为了适应一个用户的设置而改变语言环境,是否有可能影响另一个用户的会话?例如,是否存在这样一种情况:由于德语用户的会话更改了进程的语言环境,英语用户收到了德语页面?我不是很了解Apache的线程/进程模型是如何工作的,尽管似乎如果多个用户可以由同一个线程服务,这可能会发生。
这个电子邮件线程将表明这是可能的;这里的OP描述了我正在考虑的情况。
如果这是真的,是否有一种方法可以防止这种情况,而仍然使用Locale::TextDomain?我想我总是可以破解模块加载目录在一个独立的地区(可能使用DBD::PO),但希望我只是错过了一些东西,将解决我的问题…
使用
web_set_locale
可以完全避免setlocale
的问题。
(邮件列表上的消息比添加该功能早了大约4年。)
编辑:你是正确的,全局行为在Apache子节点中持续存在,导致错误的行为。
app.psgi
use 5.010;
use strictures;
use Foo::Bar qw(run);
my $app = sub {
my ($env) = @_;
run($env);
};
Foo/Bar.pm
package Foo::Bar;
use 5.010;
use strictures;
use Encode qw(encode);
use File::Basename qw(basename);
use Locale::TextDomain __PACKAGE__, '/tmp/Foo-Bar/share/locale';
use Locale::Util qw(web_set_locale);
use Plack::Request qw();
use Sub::Exporter -setup => { exports => [ 'run' ] };
our $DEFAULT_LANGUAGE = 'en'; # untranslated source strings
sub run {
my ($env) = @_;
my $req = Plack::Request->new($env);
web_set_locale($env->{HTTP_ACCEPT_LANGUAGE}, undef, undef, [
map { basename $_ } grep { -d } glob '/tmp/Foo-Bar/share/locale/*'
]); # XXX here
return $req
->new_response(
200,
['Content-Type' => 'text/plain; charset=UTF-8'],
[encode('UTF-8', __ 'Hello, world!')],
)->finalize;
}
应用程序作为PerlResponseHandler运行。当用户请求无法设置的语言时,调用静默失败,上次成功使用的语言仍然启用。
解决这个问题的技巧是始终设置为具有回退机制的语言。在标记XXX的地方,添加代码or web_set_locale($DEFAULT_LANGUAGE)
,这样尽管使用全局设置,行为不能持续,因为我们保证每个请求设置/更改一次。
编辑2:进一步的测试表明,它不是线程安全的,对不起。只使用
prefork
MPM,它将请求作为进程隔离;但是worker
和event
受到影响,因为它们是基于线程的。