<?php
/**
* This file implements the {@link POFile} and {@link POTFile} classes, used to handle gettext style
* .PO and .POT files.
*
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
* Parts of this file are copyright (c)2004 by Daniel HAHLER - {@link http://thequod.de/contact}.
*
* @package internal
* @author blueyed: Daniel HAHLER
*/
/**
* A quick and dirty class for PO/POT files
*
* @package internal
*/
class POFile
{
var $msgids = array();
function __construct( $filename=nulll )
{
// We probably don't need the windows backslashes replacing any more but leave it for safety because it doesn't hurt:
$this->filename = str_replace( '\\', '/', $filename );
}
/**
* Add a MSGID for a specific source file
*/
function addmsgid( $msgid, $sourcefile = '', $trans = '' )
{
if( in_array($msgid, array('trans_locale', 'trans_charset', 'trans_available')) )
{ // don't put those into POT file
return;
}
// replace links
$msgid = preg_replace('/<a\s+([^>]*)>/', '<a %s>', $msgid);
// replace newlines, tabs, carriage returns and double quotes:
$msgid = str_replace( array( "\n", "\t", "\r", '"' ),
array( '\n', '\t', '\r', '\"' ), $msgid );
if( !isset($this->msgids[ $msgid ]) )
{
$this->msgids[ $msgid ] = '';
}
if( !empty($sourcefile) )
{
$this->msgids[ $msgid ]['source'][] = $sourcefile;
}
$this->msgids[ $msgid ]['trans'][] = $trans;
}
/**
* Translate msgid
* @param string MSGID
*/
function translate( $msgid )
{
$omsgid = $msgid; // remember
if( preg_match_all('/<a\s+([^>]*)>/', $msgid, $matches) )
{ // we have to replace links
// remember a-tag params
$aparams = $matches[1];
// generate clean msgid like in .po files
$msgid = preg_replace('/<a\s+([^>]*)>/', '<a %s>', $msgid);
}
// replace newlines, tabs, carriage returns and double quotes:
$msgid = str_replace( array( "\n", "\t", "\r", '"' ),
array( '\n', '\t', '\r', '\"' ), $msgid );
if( isset($this->msgids[ $msgid ]) )
{
$trans = $this->msgids[ $msgid ]['trans'];
if( isset($aparams) )
{
$trans = vsprintf($trans, $aparams);
}
return str_replace( array( '\n', '\t', '\r', '\"' ),
array( "\n", "\t", "\r", '"' ), $trans );
}
else
{
#pre_dump( $msgid, 'not translated!' );
return TRANSTAG_OPEN.$omsgid.TRANSTAG_CLOSE;
}
}
/**
* Read a .po file
*
* @param boolean Log source info to {@link $Messages}?
* @return array with msgids => array( 'trans' => msgstr )
*/
function read( $log_source_info = true )
{
$lines = file( $this->filename );
$lines[] = ''; // Adds a blank line at the end in order to ensure complete handling of the file
$all = 0;
$fuzzy = 0;
$untranslated = 0;
$translated = 0;
$status = '-';
$matches = array();
$sources = array();
$loc_vars = array();
$this->msgids = array();
$is_fuzzy = false;
foreach ($lines as $line)
{
// echo 'LINE:', $line, '<br />';
if(trim($line) == '' )
{ // Blank line, go back to base status:
if( $status == 't' )
{ // ** End of a translation **:
if( $msgstr == '' )
{
$untranslated++;
// echo 'untranslated: ', $msgid, '<br />';
}
elseif( $is_fuzzy )
{
$fuzzy++;
// echo 'fuzzy: ', $msgid, "<br />\n";
}
else
{
$translated++;
// Inspect where the string is used
$sources = array_unique( $sources );
// echo '<p>sources: ', implode( ', ', $sources ), '</p>';
foreach( $sources as $source )
{
if( !isset( $loc_vars[$source] ) ) $loc_vars[$source] = 1;
else $loc_vars[$source] ++;
}
// Save the string
$this->msgids[$msgid]['trans'] = $msgstr;
}
}
$status = '-';
$msgid = '';
$msgstr = '';
$sources = array();
$is_fuzzy = false;
}
elseif( ($status=='-') && preg_match( '#^msgid "(.*)"#', $line, $matches))
{ // Encountered an original text
$status = 'o';
$msgid = $matches[1];
// echo 'original: "', $msgid, '"<br />';
$all++;
}
elseif( ($status=='o') && preg_match( '#^msgstr "(.*)"#', $line, $matches))
{ // Encountered a translated text
$status = 't';
$msgstr = $matches[1];
// echo 'translated: "', $msgstr, '"<br />';
}
elseif( preg_match( '#^"(.*)"#', $line, $matches))
{ // Encountered a followup line
if ($status=='o')
$msgid .= $matches[1];
elseif ($status=='t')
$msgstr .= $matches[1];
}
elseif( ($status=='-') && preg_match( '@^#:(.*)@', $line, $matches))
{ // Encountered a source code location comment
// echo $matches[0],'<br />';
$sourcefiles = preg_replace( '@\\\\@', '/', $matches[1] );
// $c = preg_match_all( '@ ../../../([^:]*):@', $sourcefiles, $matches);
$c = preg_match_all( '@ ../../../([^/:]*/?)@', $sourcefiles, $matches);
for( $i = 0; $i < $c; $i++ )
{
$sources[] = $matches[1][$i];
}
// echo '<br />';
}
elseif(strpos($line,'#, fuzzy') === 0)
{
$is_fuzzy = true;
}
}
if( $loc_vars && $log_source_info )
{
global $Messages;
ksort( $loc_vars );
$list_counts = '';
foreach( $loc_vars as $source => $c )
{
$Messages->add_to_group( "<code>${source}</code>: ${c}", 'note', sprintf( TB_('Sources and number of strings (%s):'), '<code>'.basename( $this->filename ).'</code>' ) );
}
}
return $this->msgids;
}
/**
* Write POFile::$msgids into $file_path.
*
* @return true|string True on success, string with error on failure
*/
function write_evo_trans( $file_path, $locale, $params = array() )
{
$fp = fopen( $file_path, 'w+' );
if( ! $fp )
{
return "Could not open $file_path for writing!";
}
$params = array_merge( array(
'title' => 'Global lang file',
'source' => 'messages.po',
'type' => 'global',
), $params );
fwrite( $fp, "<?php\n" );
fwrite( $fp, "/*\n" );
fwrite( $fp, " * ".$params['title']."\n" );
fwrite( $fp, " * This file was generated automatically from ".$params['source']."\n" );
fwrite( $fp, " */\n" );
fwrite( $fp, "if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );" );
fwrite( $fp, "\n\n" );
if( $params['type'] == 'global' )
{
fwrite( $fp, '$trans[\''.$locale."'] = array(\n" );
}
else
{
fwrite( $fp, '$trans_'.$params['type'].'[\''.$locale."'] = array(\n" );
}
// Write meta/format info:
$charset = 'utf-8'; // default
if( isset($this->msgids['']) )
{
if( preg_match( '~\\\nContent-Type: text/plain; charset=(.*?);?\\\n~', $this->msgids['']['trans'], $match ) )
{
$charset = strtolower($match[1]);
}
}
fwrite( $fp, "'__meta__' => array('format_version'=>1, 'charset'=>'$charset'),\n" );
foreach( $this->msgids as $msgid => $msginfo )
{
$msgstr = $msginfo['trans'];
fwrite( $fp, POFile::quote( $msgid ).' => '.POFile::quote( $msgstr ).",\n" );
}
fwrite( $fp, "\n);\n?>" );
fclose( $fp );
return true;
}
/**
* Quote a msgid/msgstr, preferrable with single quotes.
*
* Single quotes are preferred, as PHP just handles them as strings and
* does no extra parsing.
* Double quotes are used, if there's \n, \r or \t in the string.
*
* @param string
* @return string Quoted string (either using double or single quotes (preferred))
*/
function quote($s)
{
if( preg_match('~\\\\[nrt]~', $s) ) // \r, \n or \t in there
{
// NOTE: no need to escape '"', as its escaped in .po files already
return '"'.str_replace( '$', '\$', $s ).'"';
}
else
{
return "'".str_replace( array("'", '\"'), array("\'", '"'), $s )."'";
}
}
}
/**
* A class build upon class POFile to provide specific POT actions (write)
*
* @package internal
*/
class POTFile extends POFile
{
/**
* @return boolean
*/
function write()
{
global $targets, $locales;
log_('Writing POTFile '.$this->filename.'..');
$fh = @fopen( $this->filename, 'w' );
if( ! $fh )
{
log_( sprintf('<p class="error">Could not open %s for writing.</p>', $this->filename) );
return false;
}
fwrite($fh, '# SOME DESCRIPTIVE TITLE.'."\n"
.'# Copyright (C) YEAR Francois PLANQUE'."\n"
.'# This file is distributed under the same license as the PACKAGE package.'."\n"
.'# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.'."\n"
.'#'."\n"
.'#, fuzzy'."\n"
.'msgid ""'."\n"
.'msgstr ""'."\n"
.'"Project-Id-Version: PACKAGE VERSION\n"'."\n"
.'"Report-Msgid-Bugs-To: http://fplanque.net/\n"'."\n"
.'"POT-Creation-Date: 2004-04-26 03:00+0200\n"'."\n"
.'"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"'."\n"
.'"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"'."\n"
.'"Language-Team: LANGUAGE <LL@li.org>\n"'."\n"
.'"MIME-Version: 1.0\n"'."\n"
.'"Content-Type: text/plain; charset=CHARSET\n"'."\n"
.'"Content-Transfer-Encoding: 8bit\n"'."\n"
);
$count = 0;
foreach( $this->msgids as $msgid => $arr )
{
if( isset($arr['source']) )
{ // write sources of string
foreach( $arr['source'] as $source )
{
fwrite( $fh, '#: ../../'.$source."\n" );
}
}
fwrite( $fh, 'msgid "'.$msgid.'"'."\nmsgstr ".'""'."\n\n" );
$count++;
}
fclose( $fh );
log_($count.' msgids written.');
return true;
}
}
?>