123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- <?php
- namespace Juju;
- class MsgFmt {
- public static function from(string $popath){
- if(!file_exists($popath)){
- throw new \Exception("PO file {$popath} does not exist.");
- }
- $hash = [];
- $temp = [];
- $state = null;
- $fuzzy = false;
- // iterate over lines
- foreach(explode(
- "\n",
- str_replace(
- ["\r\n", "\r"],
- ["\n", "\n"],
- file_get_contents($popath)
- )
- ) as $line){
- $line = trim($line);
- if ($line !== ''){
- $exp = explode(' ', $line, 2);
- $key = $exp[0];
- if(isset($exp[1])){
- $data = $exp[1];
- }
- switch($key){
- case '#,': // flag...
- $fuzzy = in_array('fuzzy', preg_split('/,\s*/', $data));
- case '#': // translator-comments
- case '#.': // extracted-comments
- case '#:': // reference...
- case '#|': // msgid previous-untranslated-string
- // start a new entry
- if(sizeof($temp) && array_key_exists('msgid', $temp) && array_key_exists('msgstr', $temp)){
- if (!$fuzzy){
- $hash[] = $temp;
- }
- $temp = [];
- $state = null;
- $fuzzy = false;
- }
- break;
- case 'msgctxt': // context
- case 'msgid': // untranslated-string
- case 'msgid_plural': // untranslated-string-plural
- $state = $key;
- $temp[$state] = $data;
- break;
- case 'msgstr': // translated-string
- $state= 'msgstr';
- $temp[$state][] = $data;
- break;
- default:
- if(strpos($key, 'msgstr[') !== false){ // translated-string-case-n
- $state= 'msgstr';
- $temp[$state][] = $data;
- }else{ // continued lines
- switch($state){
- case 'msgctxt':
- case 'msgid':
- case 'msgid_plural':
- $temp[$state] .= "\n" . $line;
- break;
- case 'msgstr':
- $temp[$state][sizeof($temp[$state]) - 1] .= "\n" . $line;
- break;
- default: // parse error
- return false;
- }
- }
- break;
- }
- }
- }
- // add final entry
- if ($state === 'msgstr'){
- $hash[] = $temp;
- }
- // Cleanup data, merge multiline entries, reindex hash for ksort
- $temp= $hash;
- $hash= [];
- foreach($temp as $entry){
- foreach($entry as &$value){
- $value = self::clean($value);
- if($value === false){ // parse error
- return false;
- }
- }
- $hash[$entry['msgid']] = $entry;
- }
- return $hash;
- }
- public static function to(string $mopath, array $hash){
- // sort by msgid
- ksort($hash, SORT_STRING);
- // our mo file data
- $mo = '';
- // header data
- $offsets = [];
- $ids = '';
- $strings = '';
- foreach($hash as $entry){
- $id = $entry['msgid'];
- if(isset($entry['msgid_plural'])){
- $id .= "\x00" . $entry['msgid_plural'];
- }
- // context is merged into id, separated by EOT (\x04)
- if(array_key_exists('msgctxt', $entry)){
- $id = $entry['msgctxt'] . "\x04" . $id;
- }
- // plural msgstrs are NUL-separated
- $str = implode("\x00", $entry['msgstr']);
- // keep track of offsets
- $offsets[]= [
- strlen($ids),
- strlen($id),
- strlen($strings),
- strlen($str)
- ];
- // plural msgids are not stored (?)
- $ids .= $id . "\x00";
- $strings .= $str . "\x00";
- }
- // keys start after the header (7 words) + index tables ($#hash * 4 words)
- $key_start = 7 * 4 + sizeof($hash) * 4 * 4;
- // values start right after the keys
- $value_start = $key_start + strlen($ids);
- // first all key offsets, then all value offsets
- $key_offsets = [];
- $value_offsets = [];
- // calculate
- foreach($offsets as $value){
- list($o1, $l1, $o2, $l2) = $value;
- $key_offsets[] = $l1;
- $key_offsets[] = $o1 + $key_start;
- $value_offsets[] = $l2;
- $value_offsets[] = $o2 + $value_start;
- }
- $offsets= array_merge($key_offsets, $value_offsets);
- // write header
- $mo .= pack('Iiiiiii', 0x950412de, // magic number
- 0, // version
- sizeof($hash), // number of entries in the catalog
- 7 * 4, // key index offset
- 7 * 4 + sizeof($hash) * 8, // value index offset,
- 0, // hashtable size (unused, thus 0)
- $key_start // hashtable offset
- );
- // offsets
- foreach($offsets as $offset){
- $mo .= pack('i', $offset);
- }
- // ids
- $mo .= $ids;
- // strings
- $mo .= $strings;
- file_put_contents($mopath, $mo);
- }
- public static function convert(string $popath, string $mopath = null){
- if(is_null($mopath)){
- $mopath = dirname($popath).'/'.basename($popath, '.po').'.mo';
- }
- self::to($mopath, self::from($popath));
- }
- private static function clean($value){
- if(is_array($value)){
- foreach($value as $k => $v){
- $value[$k] = self::clean($v);
- }
- }else{
- if ($value[0] == '"'){
- $value = substr($value, 1, -1);
- }
- $value = (string)str_replace(
- '$',
- '\\$',
- str_replace("\"\n\"", '', $value)
- );
- }
- return $value;
- }
- }
- ?>
|