370 lines
12 KiB
PHP
370 lines
12 KiB
PHP
<?php
|
|
|
|
/*
|
|
********************************************************************
|
|
* Translations CLI commands *
|
|
********************************************************************
|
|
*/
|
|
|
|
if (php_sapi_name() != "cli")
|
|
return true;
|
|
|
|
/**
|
|
* Convert PO file to JSON file
|
|
*
|
|
* @param string $locale The locale of the input PO file
|
|
* @param string $path The path of the input PO file
|
|
*
|
|
* @return string JSON encoded file content
|
|
*/
|
|
function po2json($locale, $path) {
|
|
$fileHandler = new \Sepia\PoParser\SourceHandler\FileSystem($path);
|
|
$poparser = new \Sepia\PoParser\Parser($fileHandler);
|
|
$catalog = $poparser->parse();
|
|
$headers = $catalog->getHeader();
|
|
|
|
$messages = array();
|
|
foreach ($catalog->getEntries() as $entry) {
|
|
// msg id json format
|
|
$msg = $entry->getMsgStr();
|
|
if ($entry->isPlural())
|
|
$msg = array($msg, $entry->getMsgIdPlural());
|
|
$messages[$entry->getMsgId()] = $msg;
|
|
}
|
|
return json_encode(array(
|
|
'messages' => $messages,
|
|
'locale' => $locale,
|
|
'domain' => TEXT_DOMAIN,
|
|
'plural_expr' => '(n > 1)',
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Command to extract messages from PHP/JS & template files and
|
|
* generate the lang/messages.pot file.
|
|
*
|
|
* @param array $command_args The command arguments
|
|
* @return void
|
|
*/
|
|
function cli_extract_messages($command_args) {
|
|
global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
|
|
|
|
// List PHP files to parse
|
|
$php_files = run_external_command(
|
|
array('find', escapeshellarg($root_dir_path), '-name', "'*.php'"),
|
|
null, // no STDIN data
|
|
false // do not escape command args (already done)
|
|
);
|
|
if (!is_array($php_files) || $php_files[0] != 0) {
|
|
logging('FATAL', _("Fail to list PHP files."));
|
|
}
|
|
|
|
// Extract messages from PHP files using xgettext
|
|
$result = run_external_command(
|
|
array(
|
|
"xgettext",
|
|
"--from-code utf-8",
|
|
"--language=PHP",
|
|
"-o", "$root_lang_dir/php-messages.pot", // Output
|
|
"--omit-header", // No POT header
|
|
"--keyword=___", // Handle custom ___() translation function
|
|
"--files=-" // Read files to parse from STDIN
|
|
),
|
|
$php_files[1] // Pass PHP files list via STDIN
|
|
);
|
|
if (!is_array($result) || $result[0] != 0)
|
|
logging('FATAL', _("Fail to extract messages from PHP files using xgettext."));
|
|
|
|
|
|
// List JS files to parse
|
|
$js_files = run_external_command(
|
|
array('find', escapeshellarg("$root_dir_path/public_html/js"), '-name', "'*.js'"),
|
|
null, // no STDIN data
|
|
false // do not escape command args (already done)
|
|
);
|
|
if (!is_array($js_files) || $js_files[0] != 0) {
|
|
logging('FATAL', _("Fail to list JS files."));
|
|
}
|
|
|
|
// Extract messages from JS files using xgettext
|
|
$result = run_external_command(
|
|
array(
|
|
"xgettext",
|
|
"--from-code utf-8",
|
|
"--language=JavaScript",
|
|
"-o", "$root_lang_dir/js-messages.pot", // Output
|
|
"--omit-header", // No POT header
|
|
"--keyword=___", // Handle custom ___() translation function
|
|
"--files=-" // Read files to parse from STDIN
|
|
),
|
|
$js_files[1] // Pass JS files list via STDIN
|
|
);
|
|
if (!is_array($result) || $result[0] != 0)
|
|
logging('FATAL', _("Fail to extract messages from JS files using xgettext."));
|
|
|
|
// Extract messages from templates files using tsmarty2c.php
|
|
$result = run_external_command(
|
|
array (
|
|
"$root_dir_path/vendor/bin/tsmarty2c.php",
|
|
"-o", "$root_lang_dir/templates-messages.pot",
|
|
$smarty_templates_dir,
|
|
)
|
|
);
|
|
if (!is_array($result) || $result[0] != 0)
|
|
logging(
|
|
'FATAL',
|
|
_("Fail to extract messages from template files using tsmarty2c.php script."));
|
|
|
|
$fd = fopen("$root_lang_dir/headers.pot", 'w');
|
|
$headers = array(
|
|
'msgid ""',
|
|
'msgstr ""',
|
|
'"POT-Creation-Date: '.date('Y-m-d H:iO').'\n"',
|
|
'"PO-Revision-Date: '.date('Y-m-d H:iO').'\n"',
|
|
'"MIME-Version: 1.0\n"',
|
|
'"Content-Type: text/plain; charset=utf-8\n"',
|
|
'"Content-Transfer-Encoding: 8bit\n"',
|
|
);
|
|
fwrite($fd, implode("\n", $headers));
|
|
fclose($fd);
|
|
|
|
// Merge previous results in messages.pot file using msgcat
|
|
$result = run_external_command(array(
|
|
'msgcat',
|
|
"$root_lang_dir/headers.pot",
|
|
"$root_lang_dir/php-messages.pot",
|
|
"$root_lang_dir/js-messages.pot",
|
|
"$root_lang_dir/templates-messages.pot",
|
|
"-t", "utf-8", "--use-first",
|
|
"-o", "$root_lang_dir/messages.pot",
|
|
));
|
|
if (!is_array($result) || $result[0] != 0)
|
|
logging('FATAL', _("Fail to merge messages using msgcat."));
|
|
}
|
|
add_cli_command(
|
|
'extract_messages',
|
|
'cli_extract_messages',
|
|
___("Extract messages that need to be translated"),
|
|
null,
|
|
___("This command could be used to generate/update lang/messages.pot file.")
|
|
);
|
|
|
|
/**
|
|
* Command to update messages from lang/messages.pot file to
|
|
* all PO file in lang/[lang]/LC_MESSAGES.
|
|
*
|
|
* @param array $command_args The command arguments
|
|
* @return void
|
|
*/
|
|
function cli_update_messages($command_args) {
|
|
global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
|
|
|
|
$compendium_args = array();
|
|
foreach ($command_args as $path) {
|
|
if (!file_exists($path))
|
|
logging('FATAL', _("Compendium file %s not found."), $path);
|
|
$compendium_args[] = '-C';
|
|
$compendium_args[] = $path;
|
|
}
|
|
|
|
$pot_file = "$root_lang_dir/messages.pot";
|
|
if (!is_file($pot_file))
|
|
logging('FATAL', _("POT file not found (%s). Please run extract_messages first."), $pot_file);
|
|
|
|
if ($dh = opendir($root_lang_dir)) {
|
|
$error = False;
|
|
while (($file = readdir($dh)) !== false) {
|
|
if (
|
|
!is_dir($root_lang_dir . '/' . $file) ||
|
|
in_array($file, array('.', '..')) ||
|
|
is_link($root_lang_dir . '/' . $file)
|
|
)
|
|
continue;
|
|
|
|
logging('DEBUG', _("Lang directory '%s' found"), $file);
|
|
|
|
// Check LC_MESSAGES directory exists
|
|
$lang = $file;
|
|
$lang_dir = $root_lang_dir . '/' . $file . '/LC_MESSAGES' ;
|
|
if (!is_dir($lang_dir)) {
|
|
logging('DEBUG', _("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
|
|
$lang);
|
|
continue;
|
|
}
|
|
|
|
$po_file = $lang_dir . '/' . TEXT_DOMAIN . '.po';
|
|
$created = false;
|
|
if (!is_file($po_file)) {
|
|
// Init PO file from POT file using msginit
|
|
$result = run_external_command(
|
|
array("msginit", "-i", "$pot_file", "-l", "$lang", "-o", $po_file)
|
|
);
|
|
if (is_array($result) && $result[0] == 0) {
|
|
$created = true;
|
|
} else {
|
|
logging('ERROR', _("Fail to init messages in %s PO file using msginit (%s)."),
|
|
$lang, $po_file);
|
|
$error = True;
|
|
}
|
|
}
|
|
|
|
// Update messages in PO file from POT file using msgmerge
|
|
// Note: msginit does not accept compendium files, so we also run
|
|
// msgmerge on creation with compendium file(s).
|
|
if (is_file($po_file) && (!$created || $compendium_args)) {
|
|
$result = run_external_command(
|
|
array_merge(
|
|
array("msgmerge", "-q", "-U"),
|
|
$compendium_args,
|
|
array($po_file, $pot_file)
|
|
)
|
|
);
|
|
if (!is_array($result) || $result[0] != 0) {
|
|
logging('ERROR', _("Fail to update messages in %s PO file using msgmerge (%s)."),
|
|
$lang, $po_file);
|
|
$error = True;
|
|
}
|
|
}
|
|
elseif (!$created) {
|
|
logging('DEBUG', _("PO file not found in lang '%s' directory, ignore it."), $lang);
|
|
}
|
|
}
|
|
closedir($dh);
|
|
return !$error;
|
|
}
|
|
|
|
logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path);
|
|
}
|
|
add_cli_command(
|
|
'update_messages',
|
|
'cli_update_messages',
|
|
___("Update messages in translation PO lang files"),
|
|
null,
|
|
___("This command could be used to init/update PO files in lang/*/LC_MESSAGES directories.")
|
|
);
|
|
|
|
/**
|
|
* Command to compile messages from existing translation PO lang files
|
|
* to corresponding MO files and as JSON catalog (for translation in JS).
|
|
*
|
|
* @param array $command_args The command arguments
|
|
* @return void
|
|
*/
|
|
function cli_compile_messages($command_args) {
|
|
global $root_dir_path, $root_lang_dir, $smarty_templates_dir;
|
|
|
|
if ($dh = opendir($root_lang_dir)) {
|
|
$error = False;
|
|
while (($file = readdir($dh)) !== false) {
|
|
if (
|
|
!is_dir($root_lang_dir . '/' . $file) ||
|
|
in_array($file, array('.', '..'))
|
|
)
|
|
continue;
|
|
|
|
if (is_link($root_lang_dir . '/' . $file)) {
|
|
$real_lang_dir = readlink($root_lang_dir . '/' . $file);
|
|
if (dirname($real_lang_dir) != '.' || !is_dir($root_lang_dir . '/' . $real_lang_dir))
|
|
continue;
|
|
$lang = $file;
|
|
logging('DEBUG', _("Lang alias symlink found: %s -> %s"), $lang, $real_lang_dir);
|
|
|
|
// Create JSON catalog symlink (if not exists)
|
|
$js_link = "$root_dir_path/public_html/translations/$lang.js";
|
|
$link_target = "$real_lang_dir.js";
|
|
if (!file_exists($js_link)) {
|
|
if (symlink($link_target, $js_link)) {
|
|
logging('INFO', _("JSON catalog symlink for %s -> %s created (%s)"),
|
|
$lang, $real_lang_dir, $js_link);
|
|
}
|
|
else {
|
|
logging('ERROR', _("Fail to create JSON catalog symlink for %s -> %s (%s)"),
|
|
$lang, $real_lang_dir, $js_link);
|
|
$error = True;
|
|
}
|
|
}
|
|
elseif (readlink($js_link) == $link_target) {
|
|
logging('DEBUG', _("JSON catalog symlink for %s -> %s already exist (%s)"),
|
|
$lang, $real_lang_dir, $js_link);
|
|
}
|
|
else {
|
|
logging(
|
|
'WARNING',
|
|
_("JSON catalog file for %s already exist, but it's not a symlink to %s (%s)"),
|
|
$lang, $real_lang_dir, $js_link
|
|
);
|
|
$error = True;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
logging('DEBUG', _("Lang directory '%s' found"), $file);
|
|
|
|
// Check LC_MESSAGES directory exists
|
|
$lang = $file;
|
|
$lang_dir = $root_lang_dir . '/' . $file . '/LC_MESSAGES' ;
|
|
if (!is_dir($lang_dir)) {
|
|
logging('DEBUG', _("LC_MESSAGES directory not found in lang '%s' directory, ignore it."),
|
|
$lang);
|
|
continue;
|
|
}
|
|
|
|
// Test .PO file is present
|
|
$po_file = $lang_dir . '/' . TEXT_DOMAIN . '.po';
|
|
if (!is_file($po_file)) {
|
|
logging('DEBUG', _("PO file not found in lang '%s' directory, ignore it."),
|
|
$lang);
|
|
continue;
|
|
}
|
|
|
|
$mo_file = preg_replace('/\.po$/', '.mo', $po_file);
|
|
|
|
// Compile messages from PO file to MO file using msgfmt
|
|
$result = run_external_command(
|
|
array("msgfmt", "-o", $mo_file, $po_file)
|
|
);
|
|
if (!is_array($result) || $result[0] != 0) {
|
|
logging(
|
|
'ERROR',
|
|
_("Fail to compile messages from %s PO file as MO file using msgfmt (%s)."),
|
|
$lang, $po_file
|
|
);
|
|
$error = True;
|
|
}
|
|
|
|
// Compile messages from PO file to JSON catalog file
|
|
$json_catalog = po2json($lang, $po_file);
|
|
$js_file = "$root_dir_path/public_html/translations/$lang.js";
|
|
if(!$fd = fopen($js_file, 'w')) {
|
|
logging('ERROR', _("Fail to open %s JSON catalog file in write mode (%s)."),
|
|
$lang, $js_file);
|
|
$error = True;
|
|
}
|
|
elseif (fwrite($fd, sprintf("translations_data = %s;", $json_catalog)) === false) {
|
|
logging('ERROR', _("Fail to write %s JSON catalog in file (%s)."),
|
|
$lang, $js_file);
|
|
$error = True;
|
|
}
|
|
else {
|
|
logging('INFO', _("%s JSON catalog writed (%s)."), $lang, $js_file);
|
|
}
|
|
}
|
|
closedir($dh);
|
|
|
|
return !$error;
|
|
}
|
|
logging('FATAL', _("Fail to open root lang directory (%s)."), $root_dir_path);
|
|
}
|
|
add_cli_command(
|
|
'compile_messages',
|
|
'cli_compile_messages',
|
|
___(
|
|
"Compile messages from existing translation PO lang files to ".
|
|
"corresponding MO files and JSON catalogs"
|
|
),
|
|
null,
|
|
___(
|
|
"This command could be used to compile PO files in lang/*/LC_MESSAGES ".
|
|
"directories to MO files and as JSON catalogs in public_html/translations."
|
|
)
|
|
);
|