From ff62162c340f9ac862b089a9ed0a3472c8187a22 Mon Sep 17 00:00:00 2001
From: Logan Smyth <loganfsmyth@gmail.com>
Date: Sun, 28 Aug 2011 23:53:28 +0100
Subject: [PATCH] Issue 488496: Add context support for Drupal.t() and
 Drupal.formatPlural().

---
 includes/locale.inc |  109 +++++++++++++++++++++++++++++++++------------------
 misc/drupal.js      |   24 ++++++++---
 2 files changed, 88 insertions(+), 45 deletions(-)

diff --git a/includes/locale.inc b/includes/locale.inc
index 780459b..c17fb6e 100644
--- a/includes/locale.inc
+++ b/includes/locale.inc
@@ -43,6 +43,21 @@ define('LOCALE_LANGUAGE_NEGOTIATION_SESSION', 'locale-session');
 define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');
 
 /**
+ * Regular expression pattern used to match simple JS object literal.
+ * This will fail on an object with nested objects.
+ *
+ * Uses LOCALE_JS_STRING to avoid errors with } inside strings.
+ */
+define('LOCALE_JS_OBJECT', '\{\s*(?:' . LOCALE_JS_STRING. '|[^\}])*\s*\}');
+
+/**
+ * Regular expression to match an object containing a key 'context'
+ * with a string value, which is captured. Will fail if there
+ * are nested objects.
+ */
+define('LOCALE_JS_OBJECT_CONTEXT', '\{\s*(?:' . LOCALE_JS_STRING . '|[^\}])*?\s*(?:\'context\'|"context"|context)\s*:\s*(' . LOCALE_JS_STRING . ')\s*(?:' . LOCALE_JS_STRING . '|[^\}])*\s*}');
+
+/**
  * Translation import mode overwriting all existing translations
  * if new translated version available.
  */
@@ -1380,55 +1395,73 @@ function _locale_parse_js_file($filepath) {
 
   // Match all calls to Drupal.t() in an array.
   // Note: \s also matches newlines with the 's' modifier.
-  preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING . ')\s*[,\)]~s', $file, $t_matches);
+  preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING . ')\s*(?:,\s*' . LOCALE_JS_OBJECT . '\s*(?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)?)?[,\)]~s', $file, $t_matches);
 
   // Match all Drupal.formatPlural() calls in another array.
-  preg_match_all('~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' . LOCALE_JS_STRING . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s', $file, $plural_matches);
+  preg_match_all('~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' . LOCALE_JS_STRING . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*(?:,\s*' . LOCALE_JS_OBJECT . '\s*(?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)?)?[,\)]~s', $file, $plural_matches);
 
-  // Loop through all matches and process them.
-  $all_matches = array_merge($plural_matches[1], $t_matches[1]);
-  foreach ($all_matches as $key => $string) {
-    $strings = array($string);
+  $matches = array();
+
+  // Add strings from Drupal.t().
+  foreach ($t_matches[1] as $key => $string) {
+    $matches[] = array(
+      'string'  => $string,
+      'context' => $t_matches[2][$key],
+    );
+  }
+
+  // Add string from Drupal.formatPlural().
+  foreach ($plural_matches[1] as $key => $string) {
+    $matches[] = array(
+      'string'  => $string,
+      'context' => $plural_matches[3][$key],
+    );
 
     // If there is also a plural version of this string, add it to the strings array.
     if (isset($plural_matches[2][$key])) {
-      $strings[] = $plural_matches[2][$key];
+      $matches[] = array(
+        'string'  => $plural_matches[2][$key],
+        'context' => $plural_matches[3][$key],
+      );
     }
+  }
 
-    foreach ($strings as $key => $string) {
-      // Remove the quotes and string concatenations from the string.
-      $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($string, 1, -1)));
-
-      $source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source", array(':source' => $string))->fetchObject();
-      if ($source) {
-        // We already have this source string and now have to add the location
-        // to the location column, if this file is not yet present in there.
-        $locations = preg_split('~\s*;\s*~', $source->location);
-
-        if (!in_array($filepath, $locations)) {
-          $locations[] = $filepath;
-          $locations = implode('; ', $locations);
-
-          // Save the new locations string to the database.
-          db_update('locales_source')
-            ->fields(array(
-              'location' => $locations,
-            ))
-            ->condition('lid', $source->lid)
-            ->execute();
-        }
-      }
-      else {
-        // We don't have the source string yet, thus we insert it into the database.
-        db_insert('locales_source')
+  // Loop through all matches and process them.
+  foreach ($matches as $key => $match) {
+
+    // Remove the quotes and string concatenations from the string and context.
+    $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['string'], 1, -1)));
+    $context = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['context'], 1, -1)));
+
+    $source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $string, ':context' => $context))->fetchObject();
+    if ($source) {
+      // We already have this source string and now have to add the location
+      // to the location column, if this file is not yet present in there.
+      $locations = preg_split('~\s*;\s*~', $source->location);
+
+      if (!in_array($filepath, $locations)) {
+        $locations[] = $filepath;
+        $locations = implode('; ', $locations);
+
+        // Save the new locations string to the database.
+        db_update('locales_source')
           ->fields(array(
-            'location' => $filepath,
-            'source' => $string,
-            'context' => '',
+            'location' => $locations,
           ))
+          ->condition('lid', $source->lid)
           ->execute();
       }
     }
+    else {
+      // We don't have the source string yet, thus we insert it into the database.
+      db_insert('locales_source')
+        ->fields(array(
+          'location' => $filepath,
+          'source' => $string,
+          'context' => $context,
+        ))
+        ->execute();
+    }
   }
 }
 
@@ -1722,11 +1755,11 @@ function _locale_rebuild_js($langcode = NULL) {
 
   // Construct the array for JavaScript translations.
   // Only add strings with a translation to the translations array.
-  $result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%'", array(':language' => $language->language));
+  $result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%'", array(':language' => $language->language));
 
   $translations = array();
   foreach ($result as $data) {
-    $translations[$data->source] = $data->translation;
+    $translations[$data->context][$data->source] = $data->translation;
   }
 
   // Construct the JavaScript file, if there are translations.
diff --git a/misc/drupal.js b/misc/drupal.js
index 7e2cc4d..7ae737c 100644
--- a/misc/drupal.js
+++ b/misc/drupal.js
@@ -177,13 +177,21 @@ Drupal.formatString = function(str, args) {
  *   An object of replacements pairs to make after translation. Incidences
  *   of any key in this array are replaced with the corresponding value.
  *   See Drupal.formatString().
+ *
+ * @param options
+ *   - 'context' (defaults to the empty context): The context the source string
+ *     belongs to.
+ *
  * @return
  *   The translated string.
  */
-Drupal.t = function (str, args) {
+Drupal.t = function (str, args, options) {
+  options = options || {};
+  options.context = options.context || '';
+
   // Fetch the localized version of the string.
-  if (Drupal.locale.strings && Drupal.locale.strings[str]) {
-    str = Drupal.locale.strings[str];
+  if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) {
+    str = Drupal.locale.strings[options.context][str];
   }
 
   if (args) {
@@ -216,25 +224,27 @@ Drupal.t = function (str, args) {
  *   See Drupal.formatString().
  *   Note that you do not need to include @count in this array.
  *   This replacement is done automatically for the plural case.
+ * @param options
+ *   The options to pass to the Drupal.t() function.
  * @return
  *   A translated string.
  */
-Drupal.formatPlural = function (count, singular, plural, args) {
+Drupal.formatPlural = function (count, singular, plural, args, options) {
   var args = args || {};
   args['@count'] = count;
   // Determine the index of the plural form.
   var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1);
 
   if (index == 0) {
-    return Drupal.t(singular, args);
+    return Drupal.t(singular, args, options);
   }
   else if (index == 1) {
-    return Drupal.t(plural, args);
+    return Drupal.t(plural, args, options);
   }
   else {
     args['@count[' + index + ']'] = args['@count'];
     delete args['@count'];
-    return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args);
+    return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options);
   }
 };
 
-- 
1.7.5.3

