通过通过 SSH 执行的 PHP 脚本运行时,Gettext 回退到非根语言的英语

Gettext falls back to English for non-root language when running via PHP script executed over SSH

提问人:DevelJoe 提问时间:9/8/2023 最后编辑:DevelJoe 更新时间:9/8/2023 访问量:55

问:

我正在准备一个服务器端基于 PHP 的应用程序,并使用本机 gettext 函数实现翻译。

为了加载应该用于检索 gettext 翻译的区域设置,我遵循文档并使用以下命令:

// Set language to German
putenv('LC_ALL=de_DE');
setlocale(LC_ALL, 'de_DE');

到目前为止,对于我的 REST API 来说,这始终以可靠的方式工作,它以链接/描述的方式使用 gettext 翻译。但是,我有一个情况,它没有像我预期的那样工作。

我有以下PHP脚本:

$locales = [
    'de_CH',
    'en_GB',
    'fr_FR',
    'es_ES',
    'pt_PT',
    'it_IT'
];

$textdomain_loaded = false;

foreach ($locales as $locale_value) {

    var_dump(putenv("LC_ALL=$locale_value.UTF-8"));
    var_dump(
        setlocale(
            LC_ALL,
            "$locale_value.UTF-8"
        )
    );

    if ($textdomain_loaded === false) {

        bindtextdomain(
            domain   : 'firstdomain',
            directory: ROOT . '/translations'
        );

        bindtextdomain(
            domain   : 'seconddomain',
            directory: ROOT . '/translations'
        );

        // Set default textdomain to be first, as most translations are retrieved from there
        textdomain('firstdomain');
        
        $textdomain_loaded = true;

    }


    var_dump(_(message: 'Beispiel'));

}

所有 .mo 文件都已正确翻译和生成。 如果我通过脚本()执行脚本,则只需通过以下方式调用脚本.sh.zsh

php -r 'require "path/to/php/script.php"; MyClass::for_loop();'

或者,如果我向执行脚本的控制器触发 HTTP 请求,则会得到以下输出:

bool(true)
string(11) "de_CH.UTF-8"
string(8) "Beispiel"
bool(true)
string(11) "en_GB.UTF-8"
string(7) "Example"
bool(true)
string(11) "fr_FR.UTF-8"
string(7) "Exemple"
bool(true)
string(11) "es_ES.UTF-8"
string(7) "Ejemplo"
bool(true)
string(11) "pt_PT.UTF-8"
string(7) "Esemplo"
bool(true)
string(11) "it_IT.UTF-8"
string(7) "Esempio“

但是,如果我通过终端中的 ssh 连接在服务器上执行完全相同的脚本(当然具有相应的改编路径,但完全相同的 php 脚本),我得到的输出是:.sh

bool(true)
string(11) "de_CH.UTF-8"
string(8) "Beispiel"
bool(true)
string(11) "en_GB.UTF-8"
string(7) "Example"
bool(true)
string(11) "fr_FR.UTF-8"
string(7) "Example"
bool(true)
string(11) "es_ES.UTF-8"
string(7) "Example"
bool(true)
string(11) "pt_PT.UTF-8"
string(7) "Example"
bool(true)
string(11) "it_IT.UTF-8"
string(7) "Example“

因此,语言环境(环境和 PHP 语言环境)的切换似乎有效,但检索到的 gettext 翻译总是回退到英语翻译。

我已经在我的服务器上运行了;我设置/调用的所有语言环境都定义/安装在我的服务器上。locale -a

运行时,我可以在输出中看到以下部分:gettext --help

Standard search directory: /usr/local/share/locale

如上所述,我的文件存储在根目录下,例如:.motranslations

/translations/en_GB.UTF-8/LC_MESSAGES/firstdomain.mo /translations/en_GB.UTF-8/LC_MESSAGES/seconddomain.mo /translations/fr_FR.UTF-8/LC_MESSAGES/firstdomain.mo /translations/fr_FR.UTF-8/LC_MESSAGES/seconddomain.mo

依此类推,对于上面指定的所有(非德语,因为 MSGID 是德语/德语是根语言)区域设置。

所以我的问题是,为什么会发生这个问题?

我发现它实际上是英语被用作非德语gettext检索的默认翻译语言。例如,如果用 的内容覆盖文件,则上面的输出显示,如果脚本在服务器上按 ssh 运行:/translations/en_GB.UTF-8/LC_MESSAGES/firstdomain.mo/translations/pt_PT.UTF-8/LC_MESSAGES/firstdomain.mo

bool(true)
string(11) "de_CH.UTF-8"
string(8) "Beispiel"
bool(true)
string(11) "en_GB.UTF-8"
string(7) "Esemplo"
bool(true)
string(11) "fr_FR.UTF-8"
string(7) "Esemplo"
bool(true)
string(11) "es_ES.UTF-8"
string(7) "Esemplo"
bool(true)
string(11) "pt_PT.UTF-8"
string(7) "Esemplo"
bool(true)
string(11) "it_IT.UTF-8"
string(7) "Esemplo“

因此,脚本似乎总是回退到通过文件提供的翻译,但只有在脚本是按 ssh 终端会话运行时才回退的。为什么?/translations/en_GB.UTF-8/LC_MESSAGES/firstdomain.mo

笔记:

在设置语言环境之前执行,或者如此处所述,没有更改任何内容。putenv("LANGUAGE=");putenv("LC_ALL=");

在设置语言环境之前执行,无论是使用语言环境,包括或排除后缀,还是仅使用前两个字符串字符,就像这里所做的那样,也没有解决问题。putenv("LANGUAGE=");.UTF-8

按照这里提到的去做也没有奏效。putenv('LANGUAGE=nl_NL');

我注意到 setlocale 的 PHP 手册中关于“多线程服务器 API”的注释,并看到了这个现有的问题,但我不确定这是否与这种情况相关。

我现在发现,如果我更换部件,我实际上可以在本地重现我从服务器获得的输出:

var_dump(putenv("LC_ALL=$locale_value.UTF-8"));
    var_dump(
        setlocale(
            LC_ALL,
            "$locale_value.UTF-8"
        )
    );

跟:

var_dump(putenv("LANG=$locale_value.UTF-8"));

对我来说,这表明出于某种原因,

putenv("LC_ALL=$locale_value.UTF-8");
setlocale(LC_ALL,"$locale_value.UTF-8");

在本地运行脚本时切换 gettext 语言环境就足够了,但在 Apache 服务器上切换语言环境是不够的/忽略的。

php 多线程 apache 本地化 gettext

评论


答:

0赞 DevelJoe 9/8/2023 #1

多亏了这个神圣帖子的公认答案,我才能找到解决方案。更改上面的代码示例:

foreach ($locales as $locale_value) {

    var_dump(putenv("LC_ALL=$locale_value.UTF-8"));
    var_dump(
        setlocale(
            LC_ALL,
            "$locale_value.UTF-8"
        )
    );

    if ($textdomain_loaded === false) {

        bindtextdomain(
            domain   : 'firstdomain',
            directory: ROOT . '/translations'
        );

        bindtextdomain(
            domain   : 'seconddomain',
            directory: ROOT . '/translations'
        );

        // Set default textdomain to be first, as most translations are retrieved from there
        textdomain('firstdomain');
        
        $textdomain_loaded = true;

    }


    var_dump(_(message: 'Beispiel'));

}

对此:

foreach ($locales as $locale_value) {

    var_dump(putenv("LC_ALL=$locale_value.UTF-8"));
    var_dump(
        setlocale(
            LC_ALL,
            "$locale_value.UTF-8"
        )
    );

    bindtextdomain(
        domain   : 'firstdomain',
        directory: ROOT . '/translations'
    );

    bindtextdomain(
        domain   : 'seconddomain',
        directory: ROOT . '/translations'
    );

    // Set default textdomain to be first, as most translations are retrieved from there
    textdomain('firstdomain');


    var_dump(_(message: 'Beispiel'));

}

也就是说,为每次迭代重新启动 textdomain 启动和绑定。对于通常的PHP进程,这似乎不是必需的,但是如果您通过ssh命令在以PHP-FPM(FCGI)身份运行PHP的Apache服务器上运行PHP脚本,则它是必需的。为什么它能成功?绝对没有线索;仍然会对原因非常感兴趣。

评论

1赞 IMSoP 9/8/2023
如果您从 SSH 命令运行,则不涉及 Apache、FPM 等 - 这些仅在您发送某种 HTTP (web) 请求时才相关。如果您只是在命令行或 shell 脚本中运行,它只是作为单个独立程序运行,而不是连接到任何内容。因此,这可能会缩小问题范围 - 当涉及 FPM 时,它显然有效,但在不参与时则然。php
0赞 DevelJoe 9/8/2023
好的,是的,有道理。这可能意味着每次我更新LC_ALL textdomain 源目录不是在 PHP“进程”中启动的目录,而是 gettext CLI 的默认目录。然而,对我来说仍然是一个谜,为什么这对英语有用。