Skip to content
Snippets Groups Projects
Commit 9331618a authored by Jared Hancock's avatar Jared Hancock
Browse files

Add POT generation for the i18n CLI utility

parent 7042e6c8
No related branches found
No related tags found
No related merge requests found
......@@ -10,7 +10,8 @@ class i18n_Compiler extends Module {
var $arguments = array(
"command" => "Action to be performed.
list - Show list of available translations"
list - Show list of available translations
make-pot - Build the PO file for gettext translations"
);
var $options = array(
......@@ -69,6 +70,9 @@ class i18n_Compiler extends Module {
$this->fail('Language code is required. See `list`');
$this->_build($options['lang']);
break;
case 'make-pot':
$this->_make_pot();
break;
}
}
......@@ -124,6 +128,227 @@ class i18n_Compiler extends Module {
// TODO: Sign files
}
function __read_next_string($tokens) {
$string = array();
while (list(,$T) = each($tokens)) {
switch ($T[0]) {
case T_CONSTANT_ENCAPSED_STRING:
// String leading and trailing ' and " chars
$string['form'] = preg_replace(array("`^{$T[1][0]}`","`{$T[1][0]}$`"),array("",""), $T[1]);
$string['line'] = $T[2];
break;
case T_DOC_COMMENT:
case T_COMMENT:
switch ($T[1][0]) {
case '/':
if ($T[1][1] == '*')
$text = trim($T[1], '/* ');
else
$text = ltrim($T[1], '/ ');
break;
case '#':
$text = ltrim($T[1], '# ');
}
$string['comment'] = $text;
break;
case T_WHITESPACE:
// noop
continue;
case T_STRING_VARNAME:
case T_NUM_STRING:
case T_ENCAPSED_AND_WHITESPACE:
case '.':
$string['constant'] = false;
break;
default:
return array($string, $T);
}
}
}
function __read_args($tokens, $constants=1) {
$args = array('forms'=>array());
$arg = null;
while (list($string,$T) = $this->__read_next_string($tokens)) {
if (count($args['forms']) < $constants && $string) {
if (isset($string['constant']) && !$string['constant']) {
throw new Exception($string['form'] . ': Untranslatable string');
}
$args['forms'][] = $string['form'];
$args['line'] = $string['line'];
if (isset($string['comment']))
$args['comments'][] = $string['comment'];
}
switch ($T[0]) {
case ')':
return $args;
}
}
}
function __get_func_args($tokens, $args) {
while (list(,$T) = each($tokens)) {
switch ($T[0]) {
case T_WHITESPACE:
continue;
case '(':
return $this->__read_args($tokens, $args);
default:
// Not a function call
return false;
}
}
}
function __find_strings($tokens, $funcs, $parens=0) {
$T_funcs = array();
$funcdef = false;
while (list(,$T) = each($tokens)) {
switch ($T[0]) {
case T_STRING:
if ($funcdef)
break;;
if ($T[1] == 'sprintf') {
foreach ($this->__find_strings($tokens, $funcs) as $i=>$f) {
// Only the first on gets the php-format flag
if ($i == 0)
$f['flags'] = array('php-format');
$T_funcs[] = $f;
}
break;
}
if (!isset($funcs[$T[1]]))
continue;
$constants = $funcs[$T[1]];
if ($info = $this->__get_func_args($tokens, $constants))
$T_funcs[] = $info;
break;
case T_COMMENT:
case T_DOC_COMMENT:
if (strpos($T[1], '* trans *') !== false) {
// Find the next textual token
list($S, $T) = $this->__read_next_string($tokens);
$string = array('forms'=>array($S['form']), 'line'=>$S['line']);
if (isset($S['comment']))
$string['comments'][] = $S['comment'];
$T_funcs[] = $string;
}
break;
// Track function definitions of the gettext functions
case T_FUNCTION:
$funcdef = true;
break;
case '{';
$funcdef = false;
case '(':
$parens++;
break;
case ')':
// End of scope?
if (--$parens == 0)
return $T_funcs;
}
}
return $T_funcs;
}
function __write_string($string) {
// Unescape single quote (') and escape unescaped double quotes (")
$string = preg_replace(array("`\\\(['$])`", '`(?<!\\\)"`'), array("$1", '\"'), $string);
// Preserve embedded newlines
$string = str_replace("\n", "\\n\n", $string);
// Word-wrap long lines
$string = rtrim(preg_replace('/(?=[\s\p{Ps}])(.{1,76})(\s|$|(\p{Ps}))/uS',
"$1$2\n", $string), "\n");
$strings = explode("\n", $string);
if (count($strings) > 1)
array_unshift($strings, "");
foreach ($strings as $line) {
print "\"{$line}\"\n";
}
}
function __write_pot_header() {
$lines = array(
'msgid ""',
'msgstr ""',
'"Project-Id-Version: osTicket '.trim(`git describe`).'\n"',
'"POT-Create-Date: '.date('Y-m-d H:i O').'\n"',
'"Report-Msgid-Bugs-To: support@osticket.com\n"',
'"Language: en_US\n"',
'"MIME-Version: 1.0\n"',
'"Content-Type: text/plain; charset=UTF-8\n"',
'"Content-Transfer-Encoding: 8bit\n"',
'"X-Generator: osTicket i18n CLI\n"',
);
print implode("\n", $lines);
print "\n";
}
function __write_pot($strings) {
$this->__write_pot_header();
foreach ($strings as $S) {
print "\n";
if ($c = @$S['comments']) {
foreach ($c as $comment) {
foreach (explode("\n", $comment) as $line) {
$line = trim($line);
print "#. {$line}\n";
}
}
}
foreach ($S['usage'] as $ref) {
print "#: ".$ref."\n";
}
if ($f = @$S['flags']) {
print "#, ".implode(', ', $f)."\n";
}
print "msgid ";
$this->__write_string($S['forms'][0]);
if (count($S['forms']) == 2) {
print "msgid_plural ";
$this->__write_string($S['forms'][1]);
print 'msgstr[0] ""'."\n";
print 'msgstr[1] ""'."\n";
}
else {
print 'msgstr ""'."\n";
}
}
}
function _make_pot() {
error_reporting(E_ALL);
$funcs = array('__'=>1, '_N'=>2);
function get_osticket_root_path() { return ROOT_DIR; }
require_once(ROOT_DIR.'setup/test/tests/class.test.php');
$files = Test::getAllScripts();
$strings = array();
foreach ($files as $f) {
$F = str_replace(ROOT_DIR, '', $f);
$this->stderr->write("$F\n");
$tokens = new ArrayObject(token_get_all(fread(fopen($f, 'r'), filesize($f))));
foreach ($this->__find_strings($tokens, $funcs, 1) as $calls) {
if (!($forms = $calls['forms']))
// Transation of non-constant
continue;
$primary = $forms[0];
if (!isset($strings[$primary])) {
$strings[$primary] = array('forms' => $forms);
}
$E = &$strings[$primary];
if (isset($calls['line']))
$E['usage'][] = "{$F}:{$calls['line']}";
if (isset($calls['flags']))
$E['flags'] = array_unique(array_merge(@$E['flags'] ?: array(), $calls['flags']));
if (isset($calls['comments']))
$E['comments'] = array_merge(@$E['comments'] ?: array(), $calls['comments']);
}
}
$this->__write_pot($strings);
}
}
Module::register('i18n', 'i18n_Compiler');
......
......@@ -7,34 +7,11 @@ $selected_test = (isset($argv[1])) ? $argv[1] : false;
require_once "tests/class.test.php";
if (!function_exists('get_osticket_root_path')) {
function get_osticket_root_path() {
# Hop up to the root folder
$start = dirname(__file__);
for (;;) {
if (file_exists($start . '/main.inc.php')) break;
$start .= '/..';
}
return realpath($start);
}
}
$root = get_osticket_root_path();
define('INCLUDE_DIR', "$root/include/");
define('PEAR_DIR', INCLUDE_DIR."pear/");
ini_set('include_path', './'.PATH_SEPARATOR.INCLUDE_DIR.PATH_SEPARATOR.PEAR_DIR);
if (!function_exists('glob_recursive')) {
# Check PHP syntax across all php files
function glob_recursive($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files,
glob_recursive($dir.'/'.basename($pattern), $flags));
}
return $files;
}
}
$fails = array();
function show_fails() {
......
......@@ -5,7 +5,7 @@ class Test {
var $warnings = array();
var $name = "";
var $third_party_paths = array(
static $third_party_paths = array(
'/include/JSON.php',
'/include/htmLawed.php',
'/include/PasswordHash.php',
......@@ -17,9 +17,6 @@ class Test {
'/include/mpdf/',
);
function Test() {
call_user_func_array(array($this, '__construct'), func_get_args());
}
function __construct() {
assert_options(ASSERT_CALLBACK, array($this, 'fail'));
error_reporting(E_ALL & ~E_WARNING);
......@@ -31,14 +28,13 @@ class Test {
function teardown() {
}
/*static*/
function getAllScripts($excludes=true) {
static function getAllScripts($excludes=true) {
$root = get_osticket_root_path();
$scripts = array();
foreach (glob_recursive("$root/*.php") as $s) {
$found = false;
if ($excludes) {
foreach ($this->third_party_paths as $p) {
foreach (self::$third_party_paths as $p) {
if (strpos($s, $p) !== false) {
$found = true;
break;
......@@ -105,4 +101,28 @@ class Test {
return $line;
}
}
if (!function_exists('glob_recursive')) {
# Check PHP syntax across all php files
function glob_recursive($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files,
glob_recursive($dir.'/'.basename($pattern), $flags));
}
return $files;
}
}
if (!function_exists('get_osticket_root_path')) {
function get_osticket_root_path() {
# Hop up to the root folder
$start = dirname(__file__);
for (;;) {
if (file_exists($start . '/main.inc.php')) break;
$start .= '/..';
}
return realpath($start);
}
}
?>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment