CRM.ts() - Respect domains when selecting/transmitting strings
authorTim Otten <totten@civicrm.org>
Sat, 17 Jan 2015 11:42:54 +0000 (03:42 -0800)
committerTim Otten <totten@civicrm.org>
Sat, 17 Jan 2015 11:50:17 +0000 (03:50 -0800)
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
CRM/Core/Resources.php
js/Common.js
tests/qunit/crm-translate/test.js
tests/qunit/crm-translate/test.php

index 9a22988ae02d5d2cbef0de4695e65d3d04246e42..084e7e769e3179fd3ac91ecb4eff9b6da86279a8 100644 (file)
@@ -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');
     }
index 7707c12652f2006ee6b58caa86cd2440829b89e9..61c7095345d9f193efa036ef0b820b7a8a8cd270 100644 (file)
@@ -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;
index f5d29891749d5a5cdcf167981161ec7d096ffe2c..61dbbbe11e93274a4f933df85fa51d93380d7342 100644 (file)
@@ -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') {
index 6ba3cd9eb2a5143961935d2d7250465dc19a45b3..82f196959c48eaf9f27bab87c6dc3613512e0483 100644 (file)
@@ -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')));
 });
index d176059369ebf862adad9dee17971e19d8b23416..ddf4723b90be5d0ba2f3d90cc48126b9ea4a4802 100644 (file)
@@ -1,6 +1,12 @@
 <?php
 CRM_Core_Resources::singleton()->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(...);