Array ( [msgid] => An apple [msgid_plural] => Two apples [fuzzy] => 1 [comments] => Test [source] => /tmp/code.php [msgstr] => Array ( [0] => Une pomme [1] => Deux pommes ) ) ) Export: $translations = array(); $translations[] = array( "msgid" => "A cherry", "msgid_plural" => "Two cherries", "fuzzy" => false, "comments" => "OK", "source" => "/trunk/index.php", "msgstr" => array("Une cerise", "Deux cerises"), ); poTools::setLang("fr"); $po_file = poTools::export($translations); file_put_contents("/tmp/french.po", $po_file); */ class poTools { private static $block = array(); private static $plural_form = 'nplurals=2; plural=n != 1;'; private static $utf8 = true; // From http://www.gnu.org/software/libc/manual/html_node/Advanced-gettext-functions.html // Romanic family: French, Brazilian Portuguese const ROMANIC_PLURAL_FORM = 'nplurals=2; plural=n>1;'; // Germanic family: Danish, Dutch, English, German, Norwegian, Swedish // Finno-Ugric family: Estonian, Finnish // Latin/Greek family: Greek // Semitic family: Hebrew // Romance family: Italian, Portuguese, Spanish // Artificial: Esperanto const GERMANIC_PLURAL_FORM = 'plurals=2; plural=n != 1;'; // Finno-Ugric family: Hungarian // Asian family: Japanese, Korean // Turkic/Altaic family: Turkish const SINGLE_PLURAL_FORM = 'nplurals=1; plural=0;'; // Slavic family: Croatian, Czech, Russian, Ukrainian const RUSSIAN_PLURAL_FORM = 'nplurals=3; plural=n%100/10==1 ? 2 : n%10==1 ? 0 : (n+9)%10>3 ? 2 : 1;'; /** * Set gettext specific settings according to language (plural form) * * @param string $lang Locale lang code (eg. "fr" or "fr-ch") */ public static function setLang($lang) { if ($lang != 'pt-br') $lang = $lang[0].$lang[1]; switch ($lang) { case 'fr': case 'pt-br': self::$plural_form = self::ROMANIC_PLURAL_FORM; break; case 'hu': case 'ja': case 'ko': case 'tr': self::$plural_form = self::SINGLE_PLURAL_FORM; break; case 'ru': case 'hr': case 'cs': case 'uk': self::$plural_form = self::RUSSIAN_PLURAL_FORM; break; case 'lv': self::$plural_form = 'nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;'; break; case 'lt': self::$plural_form = 'nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2;'; break; case 'pl': self::$plural_form = 'nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;'; break; case 'sl': self::$plural_form = 'nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;'; break; case 'sk': self::$plural_form = 'nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;'; break; default: self::$plural_form = self::GERMANIC_PLURAL_FORM; break; } } public static function getPluralForms() { return self::$plural_form; } /** * Enable or disable UTF-8 in imported pot files */ public static function utf8($value=true) { self::$utf8 = $value; } /** * Detects an UTF-8 string and converts it */ private static function charsetConv($string) { if (preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] # ASCII | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )*$%xs', $string)) return utf8_decode($string); return $string; } /** * Cleans a string for backslashed quotes */ private static function cleanStr($str) { if (is_array($str)) { foreach ($str as $k=>$line) { $str[$k] = self::cleanStr($line); } } else { $str = strtr($str, array( '\\"' => '"', "\\'" => "'", "\n" => "", "\\n" => "\n" )); if (!self::$utf8) $str = self::charsetConv($str); } return $str; } /** * Resets the current block and eventually sends the previous block content */ private static function resetBlock() { $arr = false; if(!empty(self::$block)) { $arr = array( 'msgid' => self::cleanStr(self::$block['msgid']), 'msgstr' => self::cleanStr(self::$block['msgstr']), 'msgid_plural' => self::cleanStr(self::$block['msgid_plural']), 'fuzzy' => self::$block['fuzzy'], 'comments' => self::charsetConv(self::$block['comments']), 'source' => self::$block['source']); } self::$block = array('msgid' => false, 'msgstr' => array(), 'msgid_plural' => false, 'fuzzy' => false, 'comments' => false, 'source' => false); if($arr) return $arr; } /** * Imports a .po file into a PHP array encoded in UTF8 by default * * @param string $file_po File path (absolute or relative) */ public static function import($file_po) { if (!file_exists($file_po) || !is_readable($file_po)) { return false; } $file = file($file_po); $file[] = ''; // Init the self::$block array self::resetBlock(); $mode = false; $trans = array(); foreach($file as $line) { $matches = false; $line = trim($line); // msgid "Bla bla" if(preg_match('/^msgid\s+"(.*)"/i', $line, $matches)) { self::$block['msgid'] = $matches[1]; $mode = 'msgid'; } // msgid_plural "Blas blas" elseif(preg_match('/^msgid_plural\s+"(.*)"/i', $line, $matches)) { self::$block['msgid_plural'] = $matches[1]; $mode = 'msgid_plural'; } // msgstr "Blou blou" elseif(preg_match('/^msgstr\s+"(.*)"/i', $line, $matches)) { self::$block['msgstr'][0] = $matches[1]; $mode = 'msgstr'; $key = 0; } // msgstr "Blous blous" (plural forms) elseif(preg_match('/^msgstr\[([0-9]+)\]\s+"(.*)"/i', $line, $matches)) { self::$block['msgstr'][$matches[1]] = $matches[2]; $mode = 'msgstr'; $key = $matches[1]; } // multi line msgstr/msgids elseif(preg_match('/^"(.*)"/i', $line, $matches) && $mode) { if ($mode == 'msgstr') self::$block[$mode][$key] .= $matches[1]; else self::$block[$mode] .= $matches[1]; } // Fuzzy = traduction approximative elseif($line == '#, fuzzy') { self::$block['fuzzy'] = true; } // Free-text comments elseif(preg_match('/^# (.+)$/i', $line, $matches)) { self::$block['comments'] .= $matches[1]."\n"; } // Source comments elseif(preg_match('/^#: (.+)$/i', $line, $matches)) { self::$block['source'] .= $matches[1]."\n"; } // Close the current block elseif(empty($line) && (!empty(self::$block['msgid']) || !empty(self::$block['msgstr']))) { // If the msgid is empty (Pot headers), we don't record current block if(empty(self::$block['msgid'])) { self::resetBlock(); } else { $trans[] = self::resetBlock(); } } } return $trans; } /** * Clean slashes from a string for export */ private static function cleanSlashes($str) { $str = trim($str); $str = strtr($str, array( '\\' => "\\\\", "\n" => "\\n", "\r" => "", '"' => '\\"', "\\'" => "'", )); return $str; } /** * Formats a string for .po export */ private static function formatStr($str) { $str = self::cleanSlashes($str); return '"'.$str.'"'; } /** * Generates POT file header */ public static function makeHeader($headers=array()) { $default_headers = array( '_company_name' => 'PO Export', '_copyright' => 'Copyright (C) Nobody', '_contact' => 'Nobody , YEAR.', 'Project-Id-Version' => 'Nothing v1', 'Report-Msgid-Bugs-To' => 'no@body.com', 'POT-Creation-Date' => gmdate('Y-m-d H:i').'+0200', 'PO-Revision-Date' => gmdate('Y-m-d H:i').'+0200', //'YEAR-MO-DA HO:MI+ZONE', 'Last-Translator' => 'Nobody ', 'Language-Team' => 'Nobody ', 'MIME-Version' => '1.0', 'Content-Type' => 'text/plain; charset=UTF-8', 'Content-Transfer-Encoding' => '8bit', 'Plural-Forms' => self::$plural_form, //"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION; ); foreach ($default_headers as $key=>$value) { if (empty($headers[$key])) $headers[$key] = $value; } $out = "# ".$headers['_company_name']."\n"; $out.= "# ".$headers['_copyright']."\n"; $out.= "# ".$headers['_contact']."\n"; $out.= "#\n"; $out.= "#, fuzzy\n"; $out.= "msgid \"\"\n"; $out.= "msgstr \"\"\n"; foreach ($headers as $key=>$value) { if ($key{0} != '_') $out.= '"'.$key.': '.$value."\\n\"\n"; } $out.= "\n"; return $out; } public static function makeString($line) { // We can't export an empty msgid if (empty($line['msgid'])) return ''; $out = ''; if (!empty($line['source'])) { $sources = explode("\n", trim($line['source'])); foreach ($sources as $source) $out.= "#: ".trim($source)."\n"; } if (!empty($line['comment'])) { $out.= "# ".preg_replace("/[\n\r\t]/", "", $line['comment'])."\n"; } if (!empty($line['fuzzy'])) { $out.= "#, fuzzy\n"; } $out.= "msgid ".self::formatStr($line['msgid'])."\n"; if (!empty($line['msgid_plural'])) { $out.= "msgid_plural ".self::formatStr($line['msgid_plural'])."\n"; foreach ($line['msgstr'] as $key=>$value) { $out.= "msgstr[".$key."] ".self::formatStr($value)."\n"; } } else { if (is_array($line['msgstr'])) $line['msgstr'] = $line['msgstr'][0]; $out.= "msgstr ".self::formatStr($line['msgstr'])."\n"; } $out.= "\n"; return $out; } /** * Generates a .po file content from a PHP array * * You have to encode your strings to the encoding you wanna use * before sending it to export method as it don't encode strings itself * By default, it expects UTF-8. See "Content-Type" header too. * * @param array $content * @param array $headers (optional) */ public static function export($content, $headers=array()) { $out = self::makeHeader($headers); foreach ($content as $i=>$line) { $out .= self::makeString($line); } return $out; } } ?>