<?php
/**
* This file implements functions for handling translation.
*
* This file is part of the evoCore framework - {@link http://evocore.net/}
* See also {@link https://github.com/b2evolution/b2evolution}.
*
* @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package evocore
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* Update a table T_i18n_original_string from the file messages.pot
*
* @param boolean TRUE - if process is OK
*/
function translation_update_table_pot()
{
global $DB, $locales_path;
// Reset all previous strings
$DB->query( 'UPDATE T_i18n_original_string SET iost_inpotfile = 0' );
$status = '-';
$lines = file( $locales_path.'messages.pot' );
$lines[] = ''; // Adds a blank line at the end in order to ensure complete handling of the file
foreach( $lines as $line )
{
if( trim( $line ) == '' )
{ // Blank line, go back to base status:
if( $status == 't' && !empty( $msgid ) )
{ // ** End of an original text ** :
translation_update_table_pot_row( trim( $msgid ) );
}
$msgid = '';
$msgstr = '';
$status = '-';
}
elseif( ( $status == '-' ) && preg_match( '#^msgid "(.*)"#', $line, $matches ) )
{ // Encountered an original text
$status = 'o';
$msgid = $matches[1];
}
elseif( ( $status == 'o' ) && preg_match( '#^msgstr "(.*)"#', $line, $matches ) )
{ // Encountered a translated text
$status = 't';
$msgstr = $matches[1];
}
elseif( preg_match( '#^"(.*)"#', $line, $matches ) )
{ // Encountered a followup line
if( $status == 'o' )
$msgid .= $matches[1];
elseif( $status == 't' )
$msgstr .= $matches[1];
}
}
return true;
}
/**
* Update/Insert a string from .POT file into the table T_i18n_original_string
*
* @param string Original string
*/
function translation_update_table_pot_row( $string )
{
global $DB;
// Get original string ID
$SQL = new SQL( 'Get original string ID' );
$SQL->SELECT( 'iost_ID' );
$SQL->FROM( 'T_i18n_original_string' );
$SQL->WHERE( 'iost_string = '.$DB->quote( $string ) );
$original_string_ID = $DB->get_var( $SQL );
if( $original_string_ID )
{ // Update already existing string
$DB->query( 'UPDATE T_i18n_original_string SET iost_inpotfile = 1 WHERE iost_ID = '.$DB->quote( $original_string_ID ) );
}
else
{ // Insert new string
$DB->query( 'INSERT INTO T_i18n_original_string ( iost_string, iost_inpotfile ) VALUES ( '.$DB->quote( $string ).', 1 )' );
}
}
/**
* Update a table T_i18n_translated_string from the file messages.pot
*
* @param string Locale
* @param boolean TRUE - if process is OK
*/
function translation_update_table_po( $locale )
{
global $DB, $locales_path, $locales;
$po_file_name = $locales_path.$locales[$locale]['messages'].'/LC_MESSAGES/messages.po';
// Reset all previous strings
$DB->query( 'UPDATE T_i18n_translated_string SET itst_inpofile = 0 WHERE itst_locale = '.$DB->quote( $locale ) );
$status = '-';
if( !file_exists( $po_file_name ) )
{ // No locale file, Exit here
global $Messages;
$Messages->add( T_('No .PO file found'), 'error' );
return false;
}
$lines = file( $po_file_name );
$lines[] = ''; // Adds a blank line at the end in order to ensure complete handling of the file
foreach( $lines as $line )
{
if( trim( $line ) == '' )
{ // Blank line, go back to base status:
if( $status == 't' && !empty( $msgstr ) )
{ // ** End of an original text ** :
translation_update_table_po_row( $locale, trim( $msgid ), trim( $msgstr ) );
}
$msgid = '';
$msgstr = '';
$status = '-';
}
elseif( ( $status == '-' ) && preg_match( '#^msgid "(.*)"#', $line, $matches ) )
{ // Encountered an original text
$status = 'o';
$msgid = $matches[1];
}
elseif( ( $status == 'o' ) && preg_match( '#^msgstr "(.*)"#', $line, $matches ) )
{ // Encountered a translated text
$status = 't';
$msgstr = $matches[1];
}
elseif( preg_match( '#^"(.*)"#', $line, $matches ) )
{ // Encountered a followup line
if( $status == 'o' )
$msgid .= $matches[1];
elseif( $status == 't' )
$msgstr .= $matches[1];
}
}
return true;
}
/**
* Update/Insert a string from .PO file into the table T_i18n_translated_string
*
* @param string Locale
* @param string Original string
* @param string Translated string
*/
function translation_update_table_po_row( $locale, $original_string, $translated_string )
{
global $DB;
// Get original string ID
$SQL = new SQL( 'Get original string ID' );
$SQL->SELECT( 'iost_ID' );
$SQL->FROM( 'T_i18n_original_string' );
$SQL->WHERE( 'iost_string = '.$DB->quote( $original_string ) );
$original_string_ID = $DB->get_var( $SQL );
if( !$original_string_ID )
{ // No original string, Exit here
return;
}
// Get translated string
$SQL = new SQL( 'Get translated string' );
$SQL->SELECT( 'itst_ID' );
$SQL->FROM( 'T_i18n_translated_string' );
$SQL->WHERE( 'itst_standard = '.$DB->quote( $translated_string ) );
$SQL->WHERE_and( 'itst_iost_ID = '.$DB->quote( $original_string_ID ) );
$translated_string_ID = $DB->get_var( $SQL );
if( $translated_string_ID )
{ // Update already existing string
$DB->query( 'UPDATE T_i18n_translated_string SET itst_inpofile = 1 WHERE itst_ID = '.$DB->quote( $translated_string_ID ) );
}
else
{ // Insert new string
$DB->query( 'INSERT INTO T_i18n_translated_string ( itst_iost_ID, itst_locale, itst_standard, itst_inpofile ) VALUES ( '.$DB->quote( $original_string_ID ).', '.$DB->quote( $locale ).', '.$DB->quote( $translated_string ).', 1 )' );
}
}
/**
* Generate .PO file
*
* @param string Locale
*/
function translation_generate_po_file( $locale )
{
global $DB, $locales_path, $locales;
$po_folder_name = $locales_path.$locales[$locale]['messages'].'/LC_MESSAGES/';
$po_file_name = $po_folder_name.'messages.po';
if( !file_exists( $po_file_name ) )
{
if( !file_exists( $locales_path.$locales[$locale]['messages'] ) )
{
evo_mkdir( $locales_path.$locales[$locale]['messages'] );
}
if( !file_exists( $locales_path.$locales[$locale]['messages'].'/LC_MESSAGES' ) )
{
evo_mkdir( $locales_path.$locales[$locale]['messages'].'/LC_MESSAGES' );
}
}
$locale_name = explode( ' ', $locales[$locale]['name'] );
$po_content = array();
$po_content[] = '# b2evolution - '.$locale_name[0].' language file';
$po_content[] = '# Copyright (C) '.date( 'Y' ).' Francois PLANQUE';
$po_content[] = '# This file is distributed under the same license as the b2evolution package.';
$po_content[] = '';
// Get the translated strings from DB
$SQL = new SQL();
$SQL->SELECT( 'iost_string, itst_standard' );
$SQL->FROM( 'T_i18n_original_string' );
$SQL->FROM_add( 'RIGHT OUTER JOIN T_i18n_translated_string ON iost_ID = itst_iost_ID' );
$SQL->WHERE( 'itst_locale = '.$DB->quote( $locale ) );
$SQL->ORDER_BY( 'iost_string' );
$translated_strings = $DB->get_results( $SQL->get() );
foreach( $translated_strings as $string )
{
$po_content[] = 'msgid "'.$string->iost_string.'"';
$po_content[] = 'msgstr "'.$string->itst_standard.'"';
$po_content[] = '';
}
// Write to .PO file
$ok = (bool) save_to_file( implode("\r\n", $po_content), $po_file_name, 'w+' );
if( ! $ok )
{ // Inform user about no permission to write PO file
global $Messages;
$Messages->add( sprintf( T_('The file %s cannot be written to disk. Please check the filesystem permissions.'), '<b>'.$po_file_name.'</b>' ), 'error' );
}
return $ok;
}
/**
* Generate .POT file
*/
function translation_generate_pot_file()
{
global $DB, $locales_path;
$pot_file_name = $locales_path.'messages.pot';
$pot_content = array();
$pot_content[] = '# b2evolution - Language file';
$pot_content[] = '# Copyright (C) '.date( 'Y' ).' Francois PLANQUE';
$pot_content[] = '# This file is distributed under the same license as the b2evolution package.';
$pot_content[] = '';
global $basepath;
$translation_strings = array();
translation_scandir( $basepath, $translation_strings );
foreach( $translation_strings as $string => $files )
{ // Format the translation strings to write in .POT file
if( isset( $files['trans'] ) )
{ // Text of TRANS info
if( is_array( $files['trans'] ) )
{ // Multiline TRANS info
foreach( $files['trans'] as $ft => $files_trans )
{
$pot_content[] = '#. '.( $ft == 0 ? 'TRANS: ' : '' ).$files_trans;
}
}
else
{ // Single TRANS info
$pot_content[] = '#. TRANS: '.$files['trans'];
}
unset( $files['trans'] );
}
foreach( $files as $file )
{ // File name and line number where string exists
$pot_content[] = '#: '.$file[1].':'.$file[0];
}
if( strpos( $string, '%' ) !== false )
{ // Char '%' is detected in the string
if( preg_match( '/%(s|\d*d)/', $string ) )
{ // The string contains a mask like %s or %d
$pot_content[] = '#, php-format';
}
else
{ // The string contains a simple char '%'
$pot_content[] = '#, no-php-format';
}
}
$pot_content[] = 'msgid "'.$string.'"';
$pot_content[] = 'msgstr ""';
$pot_content[] = '';
}
// Write to .POT file
$ok = (bool) save_to_file( implode( "\n", $pot_content ), $pot_file_name, 'w+' );
if( ! $ok )
{ // Inform user about no permission to write POT file
global $Messages;
$Messages->add( sprintf( T_('The file %s cannot be written to disk. Please check the filesystem permissions.'), '<b>'.$pot_file_name.'</b>' ), 'error' );
}
return $ok;
}
/**
* Scan dir to find the translation strings
*
* @param string Path
* @param array Translation strings (by reference)
*/
function translation_scandir( $path, & $translation_strings )
{
$files = scandir( $path );
foreach( $files as $file )
{
if( is_file( $path.$file ) && preg_match( '/\.php$/i', $path.$file ) )
{ // PHP file; Find all translation strings in current file
translation_find_T_strings( $path.$file, $translation_strings );
}
elseif( $file != '.' && $file != '..' && is_dir( $path.$file ) )
{ // Directory; Scan each directory recursively to find all PHP files
translation_scandir( $path.$file.'/', $translation_strings );
}
}
}
/**
* Get substring from the selected position with shift
*
* @param string Source string
* @param integer Current index
* @param integer Shift
* @return string
*/
function translation_get_chars( $line_string, $index, $shift )
{
$char = '';
if( $shift > 0 )
{ // Get next chars
if( strlen( $line_string ) > $index + $shift )
{
$char = substr( $line_string, $index + 1, $shift );
}
}
elseif( $shift < 0 )
{ // Get previous chars
if( $index + $shift >= 0 )
{
$char = substr( $line_string, $index + $shift, 0 - $shift );
}
}
else
{ // Current char
$char = $line_string[ $index ];
}
return $char;
}
/**
* Find the translation strings in the file
*
* @param string File path
* @param array Translation strings (by reference)
*/
function translation_find_T_strings( $file, & $translation_strings )
{
global $basepath;
$line_is_multiple = false;
// Split file content with lines in order to know line number of each string
$file_lines = explode( "\n", file_get_contents( $file ) );
$T_line_number = 0;
$T_string_is_opened = false;
$T_string_text = '';
$T_string_quote_sign = "'";
$TRANS_is_opened = false;
$TRANS_text = '';
$prev_line_number = -1;
foreach( $file_lines as $line_number => $line_string )
{
if( ! $T_string_is_opened && strpos( $line_string, 'T_' ) === false && strpos( $line_string, 'TS_' ) === false &&
! $TRANS_is_opened && strpos( $line_string, 'TRANS:' ) === false )
{ // This line doesn't contain any T_ string AND TRANS info, Skip it
continue;
}
for( $l = 0; $l < strlen( $line_string ); $l++ )
{
$char = $line_string[ $l ];
/************ T_ strings ************/
$char_prev3 = translation_get_chars( $line_string, $l, -3 );
$char_prev2 = translation_get_chars( $line_string, $l, -2 );
if( ! $T_string_is_opened &&
( $char_prev2 == 'T_' || $char_prev3 == 'TS_' || $char_prev3 == 'NT_' ) )
{ // T_ string is detected
$char_next1 = translation_get_chars( $line_string, $l, 1 );
$char_next2 = translation_get_chars( $line_string, $l, 2 );
if( $char == '(' &&
( $char_next1 == '"' || $char_next1 == "'" ||
$char_next2 == ' "' || $char_next2 == " '" ) )
{ // OPEN T_ string
$T_string_quote_sign = ( $char_next1 == '"' || $char_next2 == ' "' ) ? '"' : "'";
$l += ( $char_next1 == '"' || $char_next1 == "'" ) ? 1 : 2;
$T_line_number = $line_number + 1;
$T_string_is_opened = true;
$TRANS_is_opened = false;
continue;
}
}
$char_prev1 = translation_get_chars( $line_string, $l, -1 );
if( $T_string_is_opened && $char_prev1 != '\\' && $char == $T_string_quote_sign )
{ // CLOSE T_ string
// Save T_ string to array
$T_string_text = str_replace( array( '"', "\'", "\r\n", "\n", '\n', "\t" ), array( '\"', "'", '\n', '\n', '\n"'."\n".'"', '\t' ), $T_string_text );
if( strpos( $T_string_text, "\n" ) !== false )
{ // Add empty new line before multiline string
$T_string_text = '"'."\n".'"'.$T_string_text;
}
if( !isset( $translation_strings[ $T_string_text ] ) )
{ // Set array for each string in order to store the file paths where this string is found
$translation_strings[ $T_string_text ] = array();
}
if( ! empty( $TRANS_text ) && ! isset( $translation_strings[ $T_string_text ]['trans'] ) )
{ // Text of TRANS info
if( strpos( $TRANS_text, "\n" ) !== false )
{
$TRANS_text = explode( "\n", $TRANS_text );
foreach( $TRANS_text as $t_i => $TRANS_text_value )
{
$TRANS_text[ $t_i ] = trim( $TRANS_text_value, ' ' );
}
}
$translation_strings[ $T_string_text ]['trans'] = $TRANS_text;
}
$translation_strings[ $T_string_text ][] = array(
$T_line_number, // Line number
str_replace( $basepath, '../../../', $file ), // String
);
// Reset vars for next T_ string
$T_line_number = 0;
$T_string_text = '';
$T_string_is_opened = false;
$TRANS_text = '';
}
if( $T_string_is_opened )
{ // Store chars of the current opened T_ string
if( $prev_line_number != $line_number )
{ // String is multiline
if( $T_string_text != '' )
{
$T_string_text .= "\n";
}
$prev_line_number = $line_number;
}
$T_string_text .= $line_string[ $l ];
}
/************ TRANS info ************/
if( $T_string_is_opened )
{ // T_ is opened skip this
continue;
}
// Try to find TRANS
$char_prev5 = translation_get_chars( $line_string, $l, -5 );
if( $char == ':' && $char_prev5 == 'TRANS' && ( strpos( $line_string, '//' ) !== false || strpos( $line_string, '/*' ) !== false ) )
{ // TRANS info is opened
$TRANS_is_opened = true;
$prev_line_number = $line_number;
$l++;
continue;
}
$char_next2 = translation_get_chars( $line_string, $l, 2 );
if( $TRANS_is_opened &&
( $char_next2 == '*/' ||
( $prev_line_number != $line_number &&
( strpos( $line_string, 'T_' ) !== false || strpos( $line_string, 'TS_' ) !== false ) ) ) )
{ // CLOSE TRANS info
$TRANS_is_opened = false;
}
if( $TRANS_is_opened )
{ // Store chars of the current opened TRANS info
if( $prev_line_number != $line_number )
{
$TRANS_text .= "\n";
$prev_line_number = $line_number;
}
$TRANS_text .= $line_string[ $l ];
}
}
$prev_line_number = $line_number;
}
}
?>