From 3f3bba82744319094ba716a515b757d5af521b96 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Sat, 17 Jan 2015 03:42:54 -0800 Subject: [PATCH] CRM.ts() - Respect domains when selecting/transmitting strings Previousy, all strings from all JS files were translated in the default 'civicrm' domain, and they were transmitted via CRM.strings. Now, strings from extension JS files are translated in their respective domains, and they are transmitted via CRM['strings::'+domain]. If a string is unavailable in the extension's domain, then it is translated using the default 'civicrm' domain. Strings from core JS files are still processed as before. --- CRM/Core/I18n.php | 81 +++++++++++++++++++++++------- CRM/Core/Resources.php | 24 ++++++--- js/Common.js | 8 ++- tests/qunit/crm-translate/test.js | 6 ++- tests/qunit/crm-translate/test.php | 8 ++- 5 files changed, 98 insertions(+), 29 deletions(-) diff --git a/CRM/Core/I18n.php b/CRM/Core/I18n.php index 9a22988ae0..084e7e769e 100644 --- a/CRM/Core/I18n.php +++ b/CRM/Core/I18n.php @@ -217,8 +217,9 @@ class CRM_Core_I18n { * @param string $text * the original string. * @param array $params - * the params of the translation (if any). - * + * The params of the translation (if any). + * - domain: string|array a list of translation domains to search (in order) + * - context: string * @return string * the translated string */ @@ -242,6 +243,7 @@ class CRM_Core_I18n { return $text; } + $plural = $count = NULL; if (isset($params['plural'])) { $plural = $params['plural']; unset($params['plural']); @@ -258,10 +260,66 @@ class CRM_Core_I18n { $context = NULL; } + if (isset($params['domain'])) { + $domain = $params['domain']; + unset($params['domain']); + } + else { + $domain = NULL; + } + + if (!empty($domain)) { + // It might be prettier to cast to an array, but this is high-traffic stuff. + if (is_array($domain)) { + foreach ($domain as $d) { + $candidate = $this->crm_translate_raw($text, $d, $count, $plural, $context); + if ($candidate != $text) { + $text = $candidate; + break; + } + } + } + else { + $text = $this->crm_translate_raw($text, $domain, $count, $plural, $context); + } + } + else { + $text = $this->crm_translate_raw($text, NULL, $count, $plural, $context); + } + + // replace the numbered %1, %2, etc. params if present + if (count($params)) { + $text = $this->strarg($text, $params); + } + + // escape SQL if we were asked for it + if (isset($escape) and ($escape == 'sql')) { + $text = CRM_Core_DAO::escapeString($text); + } + + // escape for JavaScript (if requested) + if (isset($escape) and ($escape == 'js')) { + $text = addcslashes($text, "'"); + } + + return $text; + } + + /** + * Lookup the raw translation of a string (without any extra escaping or interpolation). + * + * @param string $text + * @param string|NULL $domain + * @param int|NULL $count + * @param string $plural + * @param string $context + * @return mixed|string|translated + */ + protected function crm_translate_raw($text, $domain, $count, $plural, $context) { // gettext domain for extensions $domain_changed = FALSE; - if (!empty($params['domain']) && $this->_phpgettext) { - if ($this->setGettextDomain($params['domain'])) { + if (!empty($domain) && $this->_phpgettext) { + if ($this->setGettextDomain($domain)) { $domain_changed = TRUE; } } @@ -324,21 +382,6 @@ class CRM_Core_I18n { } } - // replace the numbered %1, %2, etc. params if present - if (count($params)) { - $text = $this->strarg($text, $params); - } - - // escape SQL if we were asked for it - if (isset($escape) and ($escape == 'sql')) { - $text = CRM_Core_DAO::escapeString($text); - } - - // escape for JavaScript (if requested) - if (isset($escape) and ($escape == 'js')) { - $text = addcslashes($text, "'"); - } - if ($domain_changed) { $this->setGettextDomain('civicrm'); } diff --git a/CRM/Core/Resources.php b/CRM/Core/Resources.php index 7707c12652..61c7095345 100644 --- a/CRM/Core/Resources.php +++ b/CRM/Core/Resources.php @@ -159,15 +159,18 @@ class CRM_Core_Resources { * relative weight within a given region. * @param string $region * location within the file; 'html-header', 'page-header', 'page-footer'. - * @param $translate , whether to parse this file for strings enclosed in ts() + * @param bool|string $translate + * Whether to load translated strings for this file. Use one of: + * - FALSE: Do not load translated strings. + * - TRUE: Load translated strings. Use the $ext's default domain. + * - string: Load translated strings. Use a specific domain. * * @return CRM_Core_Resources */ public function addScriptFile($ext, $file, $weight = self::DEFAULT_WEIGHT, $region = self::DEFAULT_REGION, $translate = TRUE) { if ($translate) { - // For each extension, maintain one cache record which - // includes parsed (translatable) strings for all its files. - $this->addString($this->strings->get($ext, $this->getPath($ext, $file), 'text/javascript')); + $domain = ($translate === TRUE) ? $ext : $translate; + $this->addString($this->strings->get($domain, $this->getPath($ext, $file), 'text/javascript'), $domain); } // Look for non-minified version if we are in debug mode if (CRM_Core_Config::singleton()->debug && strpos($file, '.min.js') !== FALSE) { @@ -354,15 +357,22 @@ class CRM_Core_Resources { * And from javascript access it at CRM.myNamespace.myString * * @param string|array $text + * @param string|NULL $domain * @return CRM_Core_Resources */ - public function addString($text) { + public function addString($text, $domain = 'civicrm') { foreach ((array) $text as $str) { - $translated = ts($str); + $translated = ts($str, array( + 'domain' => ($domain == 'civicrm') ? NULL : array($domain, NULL), + )); + // We only need to push this string to client if the translation // is actually different from the original if ($translated != $str) { - $this->addSetting(array('strings' => array($str => $translated))); + $bucket = $domain == 'civicrm' ? 'strings' : 'strings::' . $domain; + $this->addSetting(array( + $bucket => array($str => $translated), + )); } } return $this; diff --git a/js/Common.js b/js/Common.js index f5d2989174..61dbbbe11e 100644 --- a/js/Common.js +++ b/js/Common.js @@ -13,7 +13,13 @@ CRM._ = _; */ function ts(text, params) { "use strict"; - text = CRM.strings[text] || text; + var d = (params && params.domain) ? ('strings::' + params.domain) : null; + if (d && CRM[d] && CRM[d][text]) { + text = CRM[d][text]; + } + else if (CRM['strings'][text]) { + text = CRM['strings'][text]; + } if (typeof(params) === 'object') { for (var i in params) { if (typeof(params[i]) === 'string' || typeof(params[i]) === 'number') { diff --git a/tests/qunit/crm-translate/test.js b/tests/qunit/crm-translate/test.js index 6ba3cd9eb2..82f196959c 100644 --- a/tests/qunit/crm-translate/test.js +++ b/tests/qunit/crm-translate/test.js @@ -1,11 +1,15 @@ module('Translation'); test('ts()', function() { + equal(ts('Lexicographical enigma'), "Lexicographical enigma", "If a string has no translation, pass it through"); equal(ts('One, two, three'), "Un, deux, trois", "We expect translations to work"); + equal(ts('I know'), "Je sais", "We expect translations to work"); }); test('CRM.ts()', function() { (function (ts) { - equal(ts('One, two, three'), "Un, deux, trois", "We expect translations to work"); + equal(ts('Lexicographical enigma'), "Lexicographical enigma", "If a string has no translation, pass it through"); + equal(ts('One, two, three'), "Un, deux, trois", "Fallback to translations from default domain"); + equal(ts('I know'), "Je connais", "We expect translations to work"); }(CRM.ts('org.example.foo'))); }); diff --git a/tests/qunit/crm-translate/test.php b/tests/qunit/crm-translate/test.php index d176059369..ddf4723b90 100644 --- a/tests/qunit/crm-translate/test.php +++ b/tests/qunit/crm-translate/test.php @@ -1,6 +1,12 @@ addSetting(array( - 'strings' => array('One, two, three' => 'Un, deux, trois'), + 'strings' => array( + 'One, two, three' => 'Un, deux, trois', + 'I know' => 'Je sais', + ), + 'strings::org.example.foo' => array( + 'I know' => 'Je connais', + ), ) ); // CRM_Core_Resources::singleton()->addScriptFile(...); -- 2.25.1