使用gettext切换翻译语言和原始语言

12

我开始使用德文编写我的PHP应用程序,并使用gettext提取所有字符串并将其翻译为英文。
现在我有一个包含所有用德语编写的msgids和英文msgstrs的.po文件。我想要交换它们,这样我的源代码就包含了英语作为msgids,有两个主要原因:

  1. 更多的翻译者会懂得英语,因此向他们提供带有英语msgids的文件是合适的。我可以在分发文件之前和收到文件后随时切换文件,但是不愿意。
  2. 如果内容文本也是英文,则编写英语对象和函数名称以及注释将对我有所帮助。我想这样做,以便该项目更容易开放给其他开源合作者(他们可能比德语更懂英语)。

我可以手动完成此操作,并且这是一种任务,我预计编写自动化例程需要花费更多时间(因为我非常不擅长shell脚本),而手动完成要快得多。但我也预计会像平时一样讨厌每一分钟的手动计算机劳动(感觉像一种矛盾吧?)。

有人以前做过这个吗?我认为这将是一个普遍存在的问题,但是找不到任何东西。提前感谢您。

示例问题:

<title><?=_('Routinen')?></title>

#: /users/ruben/sites/v/routinen.php:43
msgid "Routinen"
msgstr "Routines"

我想我可以将问题缩小范围。 .po文件中的开关当然不是问题,它很简单,只需

preg_replace('/msgid "(.+)"\nmsgstr "(.+)"/', '/msgid "$2"\nmsgstr "$1"/', $str);
对我来说,问题在于搜索我的项目文件夹中的_('$msgid')并替换_('msgstr'),同时解析.po文件(这可能甚至不是最优雅的方法,毕竟.po文件包含了包含所有msgid出现的文件路径的注释)。
通过修改akirk的答案后,我遇到了更多问题。
  1. 由于我混合使用了_('xxx')_("xxx")的调用方式,所以我必须小心谨慎地进行转义和反转义。
    • msgid和msgstr中的双引号"必须被反转义,但是不能去掉斜杠,因为在PHP中也可能对双引号进行转义
    • 当字符串被替换为PHP时,必须转义单引号,但是它们还必须在.po文件中更改。对我而言,幸运的是,单引号只出现在英文文本中。
  2. msgid和msgstr可以有多行,看起来像这样
    msgid = ""
    "line 1\n"
    "line 2\n"
    msgstr = ""
    "line 1\n"
    "line 2\n"
  3. 复数形式当然被跳过了,但在我的情况下,这不是问题
  4. poedit想要将似乎已经成功转换的字符串标记为废弃,我不知道这在(许多)情况下为什么会发生。

我今晚必须停止处理这个问题。尽管如此,使用解析器而不是正则表达式似乎并不是过度设计。


棘手的是,你不仅需要在gettext文件中切换条目,还需要替换代码中的所有字符串。 - markus
@tharkun: 当然,那正是我需要做的事情,但对我来说并不困难。我认为我可以用PHP字符串来实现,但不能用shell。最简单的方法是解析(或搜索).po文件中的msgids和strs,然后在一个文件夹中搜索并替换该字符串的所有文件。我在帖子中包含了开关,以缩小问题范围。 - Ruben
我认为你在这方面走在了正确的轨道上。关键是要确保你使用的正则表达式不会无意中改变实际源代码。别忘了,正则表达式还需要处理对_(...)的单引号和双引号调用。祝你好运。 - Yzmir Ramirez
@Yzmir Ramirez,我想更改实际的源文件。对我来说,“诀窍”是正确解析po文件以提供我的搜索和替换脚本文件和搜索字符串,而这正是我卡住的地方。 - Ruben
3个回答

5

我在akirk的答案基础上进行了修改,并希望将我的答案保留在这里,以防其他人遇到同样的问题。 这不是递归的,但当然可以轻易地改变。欢迎评论提出改进意见,我会关注并编辑此帖。

$po = file_get_contents("locale/en_GB/LC_MESSAGES/messages.po");

$translations = array(); // german => english
$rawmsgids = array(); // find later
$msgidhits = array(); // record success
$msgstrs = array(); // find later

preg_match_all('/msgid "(.+)"\nmsgstr "(.+)"/', $po, $matches, PREG_SET_ORDER);

foreach ($matches as $match) {
    $german = str_replace('\"','"',$match[1]); // unescape double quotes (could misfire if you escaped double quotes in PHP _("<a href=\"bla\">bla</a>") but in my case that was one case versus many)
    $english = str_replace('\"','"',$match[2]);


    $en_sq_e = str_replace("'","\'",$english); // escape single quotes

    $translations['_(\''. $german . '\''] = '_(\'' . $en_sq_e . '\'';
    $rawmsgids['_(\''. $german . '\''] = $match[1]; // find raw msgid with searchstr as key

    $translations['_("'. $match[1] . '"'] = '_("' . $match[2] . '"';
    $rawmsgids['_("'. $match[1] . '"'] = $match[1];

    $translations['__(\''. $german . '\''] = '__(\'' . $en_sq_e . '\'';
    $rawmsgids['__(\''. $german . '\''] = $match[1];

    $translations['__("'. $match[1] . '"'] = '__("' . $match[2] . '"';
    $rawmsgids['__("'. $match[1] . '"'] = $match[1];

    $msgstrs[$match[1]] = $match[2]; // msgid => msgstr
}


foreach (glob("*.php") as $file) {
    $code = file_get_contents($file);

    $filehits = 0; // how many replacements per file

    foreach($translations AS $msgid => $msgstr) {
        $hits = 0;
        $code = str_replace($msgid,$msgstr,$code,$hits);
        $filehits += $hits;

        if($hits!=0) $msgidhits[$rawmsgids[$msgid]] = 1; // this serves to record if the msgid was found in at least one incarnation
        elseif(!isset($msgidhits[$rawmsgids[$msgid]])) $msgidhits[$rawmsgids[$msgid]] = 0;
    }
    // file_put_contents($file, $code); // be careful to test this first before doing the actual replace (and do use a version control system!) 
    echo "$file : $filehits <br>"; 
    echo $code;
}
/* debug */ 
$found = array_keys($msgidhits, 1, true);
foreach($found AS $mid) echo $mid . " => " . $msgstrs[$mid] . "\n\n";

echo "Not Found: <br>";
$notfound = array_keys($msgidhits, 0, true);
foreach($notfound AS $mid) echo $mid . " => " . $msgstrs[$mid] . "\n\n";

/*
following steps are still needed:
    * convert plurals (ngettext)
    * convert multi-line msgids and msgstrs (format mentioned in question)
    * resolve uniqueness conflict (msgids are unique, msgstrs are not), so you may have duplicate msgids (poedit finds these)
*/

2

请参考http://code.activestate.com/recipes/475109-regular-expression-for-python-string-literals/,了解一个很好的基于Python的正则表达式,可以找到字符串字面量并考虑转义。虽然它是Python的,但对于多行字符串和其他边角情况可能非常有用。

请参考http://docs.translatehouse.org/projects/translate-toolkit/en/latest/commands/poswap.html,了解一个现成的、开箱即用的.po文件基础语言交换工具。

例如,以下命令行将把以德语为基础的西班牙语翻译转换为以英语为基础的西班牙语翻译。在开始转换之前,您只需确保您的新基础语言(英语)已经100%翻译完成:

poswap -i de-en.po -t de-es.po -o en-es.po

最后,要将英文po文件转换为德文po文件,请使用swappo: http://manpages.ubuntu.com/manpages/hardy/man1/swappo.1.html 在交换文件之后,可能需要对结果文件进行一些手动修整。例如,标题可能会出现错误,一些重复的文本可能会出现。

我已经发布了一个Python脚本,用于在PO文件中交换源/目标语言。这可能对此情况有用:http://mola.io/2013/09/17/swapping-languages-in-gettext-po-file/ - smola

1

如果我理解你的意思正确的话,您想要用英文替换所有的德语gettext调用。为了替换目录中的内容,可以尝试类似以下的方法。

$po = file_get_contents("translation.pot");
$translations = array(); // german => english
preg_match_all('/msgid "(.+)"\nmsgstr "(.+)"/', $po, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
    $translations['_("'. $match[1] . '")'] = '_("' . $match[2] . '")';
    $translations['_(\''. $match[1] . '\')'] = '_(\'' . $match[2] . '\')';
}
foreach (glob("*.php") as $file) {
    $code = file_get_contents($file);
    $code = str_replace(array_keys($translations), array_values($translations), $code);
    //file_put_contents($file, $code);
    echo $code; // be careful to test this first before doing the actual replace (and do use a version control system!)
}

当然,虽然我可以将po文件作为字符串提供,但我需要搜索和替换一个__php文件目录__,而不是字符串。最终我也想知道哪些msgid找不到(那些是复数形式和占位符的函数调用:很少,我可以手动完成)。我希望gettext解析器本身可以以某种方式使用,毕竟它已经执行了非常类似的操作(解析php文件并在指定的函数调用中查找msgid)。 - Ruben
我不知道在gettext分发中是否有工具,你必须手动完成(这并不那么繁琐)。我已经修改了我的代码以反映这一点。 - akirk
我已经对你的脚本 http://pastebin.com/J7ipM1fy 做了一些尝试,以更轻松地查看找到的字符串。然而,处理引号和多行字符串并不容易,我会更新我的问题以反映这一点。 - Ruben

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接