PHP高效提取PO文件内容的方法与实践
在国际化(i18n)和本地化(L10n)项目中,PO文件(Portable Object)是存储翻译字符串及其翻译内容的重要格式,PHP作为广泛应用于Web开发的语言,经常需要处理PO文件以提取其中的翻译内容,本文将详细介绍如何使用PHP高效提取PO文件内容,包括直接解析、使用现有库以及处理复杂场景的技巧。
PO文件基础结构
PO文件是Gettext工具链中用于存储翻译的文本文件,基本结构如下:
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-01 12:00+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Hello"
msgstr "你好"
msgid "Goodbye"
msgstr "再见"
msgid是原始字符串,msgstr是对应的翻译内容。
PHP直接解析PO文件
基本解析方法
对于简单的PO文件,可以使用PHP的正则表达式或字符串处理函数进行解析:
function parsePOFile($filePath) { $content = file_get_contents($filePath); $pattern = '/msgid\s+"(.*?)"\s+msgstr\s+"(.*?)"/s'; preg_match_all($pattern, $content, $matches); $translations = []; foreach ($matches[1] as $index => $msgid) { if (!empty($msgid)) { // 忽略空msgid(通常是文件头信息) $translations[$msgid] = $matches[2][$index]; } } return $translations; } // 使用示例 $translations = parsePOFile('messages.po'); print_r($translations);
处理多行和转义字符
PO文件可能包含多行字符串和转义字符,需要更复杂的解析逻辑:
function parsePOFileAdvanced($filePath) { $content = file_get_contents($filePath); $lines = explode("\n", $content); $translations = []; $currentMsgid = null; $currentMsgstr = null; $isMsgid = false; $isMsgstr = false; foreach ($lines as $line) { $line = trim($line); if (empty($line)) continue; if (strpos($line, 'msgid') === 0) { if ($currentMsgid !== null) { $translations[$currentMsgid] = $currentMsgstr; } $currentMsgid = ''; $currentMsgstr = ''; $isMsgid = true; $isMsgstr = false; $value = substr($line, 6); if ($value !== '""') { $currentMsgid = $this->parsePOString($value); } } elseif (strpos($line, 'msgstr') === 0) { $isMsgid = false; $isMsgstr = true; $value = substr($line, 7); if ($value !== '""') { $currentMsgstr = $this->parsePOString($value); } } elseif (($isMsgid || $isMsgstr) && strpos($line, '"') === 0) { $value = $this->parsePOString($line); if ($isMsgid) { $currentMsgid .= $value; } else { $currentMsgstr .= $value; } } } // 添加最后一组翻译 if ($currentMsgid !== null) { $translations[$currentMsgid] = $currentMsgstr; } return $translations; } function parsePOString($str) { $str = substr($str, 1, -1); // 去掉引号 $str = str_replace('\n', "\n", $str); $str = str_replace('\t', "\t", $str); $str = str_replace('\r', "\r", $str); $str = str_replace('\"', '"', $str); $str = str_replace('\\', '\\', $str); return $str; }
使用专业库解析PO文件
直接解析PO文件容易出错,推荐使用成熟的PHP库:
使用Gettext库
// 安装: composer require gettext/gettext use Gettext\Translations; use Gettext\PoParser; $translations = Translations::fromPoFile('messages.po'); $translationsArray = []; foreach ($translations as $translation) { if (!empty($translation->getOriginal())) { $translationsArray[$translation->getOriginal()] = $translation->getTranslation(); } } print_r($translationsArray);
使用Symfony Translation组件
// 安装: composer require symfony/translation use Symfony\Component\Translation\Loader\PoFileLoader; use Symfony\Component\Translation\Translator; $translator = new Translator('en'); $translator->addLoader('po', new PoFileLoader()); $translator->loadResource('po', 'messages.po', 'en'); // 获取所有翻译 $catalogue = $translator->getCatalogue('en'); $translations = $catalogue->all(); print_r($translations);
处理大型PO文件
对于大型PO文件,可以采用流式处理或分块读取:
function parseLargePOFile($filePath, $callback) { $handle = fopen($filePath, 'r'); $buffer = ''; $inMsgid = false; $inMsgstr = false; $msgid = ''; $msgstr = ''; while (!feof($handle)) { $line = fgets($handle); if (strpos($line, 'msgid') === 0) { if ($msgid !== '') { $callback($msgid, $msgstr); } $msgid = ''; $msgstr = ''; $inMsgid = true; $inMsgstr = false; $value = substr($line, 6); if ($value !== '""') { $msgid = parsePOString($value); } } elseif (strpos($line, 'msgstr') === 0) { $inMsgid = false; $inMsgstr = true; $value = substr($line, 7); if ($value !== '""') { $msgstr = parsePOString($value); } } elseif (($inMsgid || $inMsgstr) && strpos($line, '"') === 0) { $value = parsePOString($line); if ($inMsgid) { $msgid .= $value; } else { $msgstr .= $value; } } } // 处理最后一组 if ($msgid !== '') { $callback($msgid, $msgstr); } fclose($handle); } // 使用示例 parseLargePOFile('large_messages.po', function($msgid, $msgstr) { // 处理每个翻译对 echo "Original: $msgid\nTranslation: $msgstr\n\n"; });
提取特定内容
有时只需要提取PO文件中的特定内容,如仅提取未翻译的条目:
function getUntranslated($filePath) { $translations = parsePOFileAdvanced($filePath); $untranslated = []; foreach ($translations as $msgid => $msgstr) { if (empty($msgstr) || $msgstr === $msgid) { $untranslated[$msgid] = $msgstr; } } return $untranslated; } // 使用示例 $untranslated = getUntranslated('messages.po'); print_r($untranslated);
性能优化建议
- 缓存解析结果:对于不常变化的PO文件,可以将解析结果缓存到文件或内存中
- 延迟加载:只在需要时加载特定语言的翻译
- 使用生成器:对于大型文件,使用生成器逐条处理而非一次性加载所有内容
function getPOTranslationsGenerator($filePath) { $handle = fopen($filePath, 'r'); // ... 解析逻辑 ... while (!feof($handle)) { // 解析每个翻译对 yield $msgid => $msgstr; } fclose($handle); } // 使用示例 foreach (getPOTranslationsGenerator('large_messages.po') as $msgid => $msgstr) { // 处理每个翻译对 }
常见问题与解决方案
- 编码问题:确保PO文件使用UTF-8编码,并在读取时指定编码
$content = file_get_contents($filePath, false,
还没有评论,来说两句吧...