diff --git a/include/class.i18n.php b/include/class.i18n.php index 11b6f5c805d3f5dd1a2a15a9e2a174bc43e6f56f..2a28487eceb33507a550d86ffbd33de8cea79ac9 100644 --- a/include/class.i18n.php +++ b/include/class.i18n.php @@ -334,16 +334,26 @@ class Internationalization { global $cfg; return __($msgid); } - function _SN($msgid, $plural, $count) { + function _NS($msgid, $plural, $count) { global $cfg; } + // Phrases with separate contexts + function _P($context, $msgid) { + return TextDomain::lookup()->getTranslation() + ->pgettext($context, $msgid); + } + function _NP($context, $singular, $plural, $n) { + return TextDomain::lookup()->getTranslation() + ->npgettext($context, $singular, $plural, $n); + } + // Language-specific translations function _L($msgid, $locale) { return TextDomain::lookup()->getTranslation($locale) ->translate($msgid); } - function _LN($msgid, $plural, $count, $locale) { + function _NL($msgid, $plural, $count, $locale) { return TextDomain::lookup()->getTranslation($locale) ->ngettext($msgid); } diff --git a/include/class.translation.php b/include/class.translation.php index 736a1281082e40a189fef09fbe450054e29eeafc..f4ee44662c75cc0f61279df3a44384fb839a48f1 100644 --- a/include/class.translation.php +++ b/include/class.translation.php @@ -3,11 +3,12 @@ class.gettext.php This implements a `Translation` class that is loosely based on the PHP - gettext pure-php module. + gettext pure-php module. It includes some code from the project and some + code which is based in part at least on the PHP gettext project. This extension to the PHP gettext extension using a specially crafted MO - file which is a PHP serialized hash array. The file can be built using a - utility method in this class. + file which is a PHP hash array. The file can be built using a utility + method in this class. Jared Hancock <jared@osticket.com> Copyright (c) 2006-2014 osTicket diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index 3a0f48c14c552f256d446a6dac146266c21578fc..a2473d27f0adc74d8309f18d54499ff3062468ce 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -116,7 +116,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting <select name="status"> <option value="">— <?php echo __('Any Status');?> —</option> <option value="open" - <?php echo ($status=='open')?'selected="selected"':'';?>><?php echo __('Open');?> (<?php echo $thisclient->getNumOpenTickets(); ?>)</option> + <?php echo ($status=='open')?'selected="selected"':'';?>><?php echo _P('ticket-status', 'Open');?> (<?php echo $thisclient->getNumOpenTickets(); ?>)</option> <?php if($thisclient->getNumClosedTickets()) { ?> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index 8aa896126dcf2a96c79cfc4e470d325af8867409..444362b3521f9861cd376334862377f444f266c8 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -373,7 +373,7 @@ if ($_POST) <tr> <td colspan=2> <textarea class="richtext ifhtml draft draft-delete" - placeholder="Optional internal note (recommended on assignment)" + placeholder="<?php echo __('Optional internal note (recommended on assignment)'); ?>" data-draft-namespace="ticket.staff.note" name="note" cols="21" rows="6" style="width:80%;" ><?php echo $info['note']; ?></textarea> @@ -382,7 +382,7 @@ if ($_POST) </tbody> </table> <p style="text-align:center;"> - <input type="submit" name="submit" value="<?php echo __('Open');?>"> + <input type="submit" name="submit" value="<?php echo _P('action-button', 'Open');?>"> <input type="reset" name="reset" value="<?php echo __('Reset');?>"> <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick="javascript: $('.richtext').each(function() { diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 8a427e1e106b37e4b30853b7681c181f9efe52e1..158504928c4e9b1c35e203a9c9be077e1f0429f9 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -554,7 +554,7 @@ if ($results) { <label for="status"><?php echo __('Status');?>:</label> <select id="status" name="status"> <option value="">— <?php echo __('Any Status');?> —</option> - <option value="open"><?php echo __('Open');?></option> + <option value="open"><?php echo _P('ticket-status', 'Open');?></option> <?php if(!$cfg->showAnsweredTickets()) {?> <option value="answered"><?php echo __('Answered');?></option> diff --git a/scp/tickets.php b/scp/tickets.php index 579664d06bf10249d5c20e8ec903a071a9973b65..e3a5693fa4c9069f6bf5c7cba4b96d0285538764 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -522,8 +522,11 @@ $stats= $thisstaff->getTicketsStats(); //Navigation $nav->setTabActive('tickets'); +$open_name = _P('queue-name', + /* This is the name of the open ticket queue */ + 'Open'); if($cfg->showAnsweredTickets()) { - $nav->addSubMenu(array('desc'=>__('Open').' ('.number_format($stats['open']+$stats['answered']).')', + $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']+$stats['answered']).')', 'title'=>__('Open Tickets'), 'href'=>'tickets.php', 'iconclass'=>'Ticket'), @@ -531,7 +534,7 @@ if($cfg->showAnsweredTickets()) { } else { if($stats) { - $nav->addSubMenu(array('desc'=>__('Open').' ('.number_format($stats['open']).')', + $nav->addSubMenu(array('desc'=>$open_name.' ('.number_format($stats['open']).')', 'title'=>__('Open Tickets'), 'href'=>'tickets.php', 'iconclass'=>'Ticket'), diff --git a/setup/cli/modules/i18n.php b/setup/cli/modules/i18n.php index 78183885e757872a4816da9e20ffebdaf3ece633..dfa00cc711ca7f512a01585a4d738a08b52a925e 100644 --- a/setup/cli/modules/i18n.php +++ b/setup/cli/modules/i18n.php @@ -259,21 +259,28 @@ class i18n_Compiler extends Module { } } } - function __read_args($tokens, $constants=1) { + function __read_args($tokens, $proto=false) { $args = array('forms'=>array()); $arg = null; + $proto = $proto ?: array('forms'=>1); while (list($string,$T) = $this->__read_next_string($tokens)) { - if (count($args['forms']) < $constants && $string) { + // Add context and forms + if (isset($proto['context']) && !isset($args['context'])) { + $args['context'] = $string['form']; + } + elseif (count($args['forms']) < $proto['forms'] && $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['comments'])) - $args['comments'] = array_merge( - @$args['comments'] ?: array(), $string['comments']); } + // Add usage and comment info + if (!isset($args['line']) && isset($string['line'])) + $args['line'] = $string['line']; + if (isset($string['comments'])) + $args['comments'] = array_merge( + @$args['comments'] ?: array(), $string['comments']); switch ($T[0]) { case ')': @@ -398,6 +405,10 @@ class i18n_Compiler extends Module { if ($f = @$S['flags']) { print "#, ".implode(', ', $f)."\n"; } + if (isset($S['context'])) { + print "msgctxt "; + $this->__write_string($S['context']); + } print "msgid "; $this->__write_string($S['forms'][0]); if (count($S['forms']) == 2) { @@ -414,7 +425,14 @@ class i18n_Compiler extends Module { function _make_pot() { error_reporting(E_ALL); - $funcs = array('__'=>1, '_S'=>1, '_N'=>2, '_SN'=>2); + $funcs = array( + '__' => array('forms'=>1), + '_S' => array('forms'=>1), + '_N' => array('forms'=>2), + '_NS' => array('forms'=>2), + '_P' => array('context'=>1, 'forms'=>1), + '_NP' => array('context'=>1, 'forms'=>2), + ); $files = Test::getAllScripts(); $strings = array(); foreach ($files as $f) { @@ -436,6 +454,8 @@ class i18n_Compiler extends Module { $primary = $forms[0]; // Normalize the $primary string $primary = preg_replace(array("`\\\(['$])`", '`(?<!\\\)"`'), array("$1", '\"'), $primary); + if (isset($call['context'])) + $primary = $call['context'] . "\x04" . $primary; if (!isset($strings[$primary])) { $strings[$primary] = array('forms' => $forms); } @@ -447,11 +467,13 @@ class i18n_Compiler extends Module { $E['flags'] = array_unique(array_merge(@$E['flags'] ?: array(), $call['flags'])); if (isset($call['comments'])) $E['comments'] = array_merge(@$E['comments'] ?: array(), $call['comments']); + if (isset($call['context'])) + $E['context'] = $call['context']; } function __getAllJsPhrases() { $strings = array(); - $funcs = array('__'=>1); + $funcs = array('__'=>array('forms'=>1)); foreach (glob_recursive(ROOT_DIR . "*.js") as $s) { $script = file_get_contents($s); $s = str_replace(ROOT_DIR, '', $s); diff --git a/setup/doc/i18n.md b/setup/doc/i18n.md index 11cf18a5971eeb2638f13eb3f17ce44d1a4c1eda..21048966730715a1ac8270920376b7e6023a958d 100644 --- a/setup/doc/i18n.md +++ b/setup/doc/i18n.md @@ -44,13 +44,18 @@ Use a few function calls to get your text localized: * `_N('string', 'strings', n)` localize a string with plural alternatives to the current user locale preference. * `_S('string')` localize the string to the system primary language - * `_SN('string', 'strings', n)` localize a string with plural alternatives + * `_NS('string', 'strings', n)` localize a string with plural alternatives to the system primary language. * `_L('string', locale)` localize a string to a named locale. This is useful in a ticket thread, for instance, which may have a language preference separate from both the user and the system primary language - * `_LN('string', 'strings', n, locale)` localize a string with plural + * `_NL('string', 'strings', n, locale)` localize a string with plural alternatives to a specific locale. + * `_P('context', 'string')` localize a string which may have the same + source text as another string but have a different context or usage. An + example would be `open` used as a noun, adjective, and verb. + * `_NP('context', 'string', 'strings', n)` localize a string with plural + alternatives which may have different contexts in the source language. In some cases, it is not possible to use a function to translate your string. For instance, if it is used a as a class constant or default value @@ -69,6 +74,11 @@ set something like this up: In this case the localized version of the class variable is translated when it is used — not when it is defined. +The `* trans *` text in comment is significant. Both the asterisks and the +term `trans` are used to signify that the phrase should be made +translatable. The comment must also be placed immedately before or after the +string without any other PHP symbols in between (like the semi-colon). + ### Adding context to your strings Your text may be ambiguous or unclear when it is viewed outside the context @@ -81,6 +91,21 @@ in the translation template. For instance print __('Localized' /* These comments are exported */); ``` +All types of PHP comments are supported. They must be placed inside the +parentheses of the localization call. If using single-line comments, use +multiple lines if necessary to define the call so that your single-line +comments can be used. For instance: + +```php + print sprintf(__( + // Tokens will be <a> of <b> <object(s)> + '%1$d of %2$d %3$s' + ), + $a, $b, + _N('object', 'objects', $b) + ); +``` + ### Building POT file for translations Use the command line to compile the POT file to standard out