Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.135
diff -u -p -r1.135 locale.inc
--- includes/locale.inc	9 Jun 2007 09:44:28 -0000	1.135
+++ includes/locale.inc	9 Jun 2007 13:26:56 -0000
@@ -9,6 +9,18 @@
 define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');
 
 /**
+ * Translation import mode overwriting all existing translations
+ * if new translated version available.
+ */
+define('LOCALE_IMPORT_OVERWRITE', 0);
+
+/**
+ * Translation import mode keeping existing translations and only
+ * inserting new strings.
+ */
+define('LOCALE_IMPORT_KEEP', 1);
+
+/**
  * @defgroup locale-language-overview Language overview functionality
  * @{
  */
@@ -601,7 +613,7 @@ function locale_translate_import_form() 
   $form['import']['mode'] = array('#type' => 'radios',
     '#title' => t('Mode'),
     '#default_value' => 'overwrite',
-    '#options' => array('overwrite' => t('Strings in the uploaded file replace existing ones, new ones are added'), 'keep' => t('Existing strings are kept, only new strings are added')),
+    '#options' => array(LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace existing ones, new ones are added'), LOCALE_IMPORT_KEEP => t('Existing strings are kept, only new strings are added')),
   );
   $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
   $form['#attributes']['enctype'] = 'multipart/form-data';
@@ -796,12 +808,19 @@ function locale_translate_edit_form(&$fo
 function locale_translate_edit_form_submit($form, &$form_state) {
   $lid = $form_state['values']['lid'];
   foreach ($form_state['values']['translations'] as $key => $value) {
-    $trans = db_fetch_object(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
-    if (isset($trans->translation)) {
-      db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND language = '%s'", $value, $lid, $key);
+    $translation = db_result(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
+    if (!empty($value)) {
+      // Only update or insert if we have a value to use.
+      if (!empty($translation)) {
+        db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND language = '%s'", $value, $lid, $key);
+      }
+      else {
+        db_query("INSERT INTO {locales_target} (lid, translation, language) VALUES (%d, '%s', '%s')", $lid, $value, $key);
+      }
     }
-    else {
-      db_query("INSERT INTO {locales_target} (lid, translation, language) VALUES (%d, '%s', '%s')", $lid, $value, $key);
+    elseif (!empty($translation)) {
+      // Empty translation entered: remove existing entry from database.
+      db_query("DELETE FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key);
     }
 
     // Refresh the JS file for this language.
@@ -886,12 +905,6 @@ function locale_add_language($langcode, 
 
   db_query("INSERT INTO {languages} (language, name, native, direction, domain, prefix, enabled) VALUES ('%s', '%s', '%s', %d, '%s', '%s', %d)", $langcode, $name, $native, $direction, $domain, $prefix, $enabled);
 
-  // Add empty translations for strings (to optimize locale())
-  $result = db_query("SELECT lid FROM {locales_source}");
-  while ($string = db_fetch_object($result)) {
-    db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d,'%s', '')", $string->lid, $langcode);
-  }
-
   // Only set it as default if enabled.
   if ($enabled && $default) {
     variable_set('language_default', (object) array('language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => $prefix, 'weight' => 0));
@@ -919,7 +932,7 @@ function locale_add_language($langcode, 
  * @param $langcode
  *   Language code
  * @param $mode
- *   Should existing translations be replaced ('overwrite' or 'keep')
+ *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
  * @param $group
  *   Text group to import PO file into (eg. 'default' for interface translations)
  */
@@ -969,7 +982,7 @@ function _locale_import_po($file, $langc
  * @param $file
  *   Drupal file object corresponding to the PO file to import
  * @param $mode
- *   Should existing translations be replaced ('overwrite' or 'keep')
+ *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
  * @param $lang
  *   Language code
  * @param $group
@@ -1134,7 +1147,7 @@ function _locale_import_message($message
  * @param $value
  *   Details of the string stored
  * @param $mode
- *   Should existing translations be replaced ('overwrite' or 'keep')
+ *   Should existing translations be replaced LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE
  * @param $lang
  *   Language to store the string in
  * @param $file
@@ -1143,8 +1156,7 @@ function _locale_import_message($message
  *   Text group to import PO file into (eg. 'default' for interface translations)
  */
 function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL, $group = 'default') {
-  static $additions = 0;
-  static $updates = 0;
+  static $report = array();
   static $headerdone = FALSE;
   static $strings = array();
 
@@ -1160,16 +1172,16 @@ function _locale_import_one_string($op, 
 
     // Called at end of import to inform the user
     case 'db-report':
-      return array($headerdone, $additions, $updates);
+      return array($headerdone, $report[0], $report[1], $report[2]);
 
-    // Store the string we got in the database
+    // Store the string we got in the database.
     case 'db-store':
-      // We got header information
+      // We got header information.
       if ($value['msgid'] == '') {
-        $hdr = _locale_import_parse_header($value['msgstr']);
+        $header = _locale_import_parse_header($value['msgstr']);
 
-        // Get the plural formula
-        if (isset($hdr["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($hdr["Plural-Forms"], $file->filename)) {
+        // Get the plural formula and update in database.
+        if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->filename)) {
           list($nplurals, $plural) = $p;
           db_query("UPDATE {languages} SET plurals = %d, formula = '%s' WHERE language = '%s'", $nplurals, $plural, $lang);
         }
@@ -1179,12 +1191,12 @@ function _locale_import_one_string($op, 
         $headerdone = TRUE;
       }
 
-      // Some real string to import
       else {
+        // Some real string to import.
         $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']);
-
-        // Handle a translation for some plural string
+        
         if (strpos($value['msgid'], "\0")) {
+          // This string has plural versions.
           $english = explode("\0", $value['msgid'], 2);
           $entries = array_keys($value['msgstr']);
           for ($i = 3; $i <= count($entries); $i++) {
@@ -1196,82 +1208,79 @@ function _locale_import_one_string($op, 
             if ($key == 0) {
               $plid = 0;
             }
-            $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $english[$key], $group));
-            if (!empty($loc->lid)) { // a string exists
-              $lid = $loc->lid;
-              // update location field
-              db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $comments, $lid);
-              $trans2 = db_fetch_object(db_query("SELECT lid, translation, plid, plural FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $lang));
-              if (!isset($trans2->lid)) { // no translation in current language
-                db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, $trans, $plid, $key);
-                $additions++;
-              } // translation exists
-              else if ($mode == 'overwrite' || $trans2->translation == '') {
-                db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE language = '%s' AND lid = %d", $trans, $plid, $key, $lang, $lid);
-                if ($trans2->translation == '') {
-                  $additions++;
-                }
-                else {
-                  $updates++;
-                }
-              }
-            }
-            else { // no string
-              db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', '%s')", $comments, $english[$key], $group);
-              $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $english[$key], $group));
-              $lid = $loc->lid;
-              db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, $trans, $plid, $key);
-              if ($trans != '') {
-                $additions++;
-              }
-            }
+            _locale_import_one_string_db($report, $lang, $english[$key], $trans, $group, $comments, $mode, $plid, $key);
             $plid = $lid;
           }
         }
 
-        // A simple translation
         else {
+          // A simple string to import.
           $english = $value['msgid'];
           $translation = $value['msgstr'];
-          $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $english, $group));
-          if (!empty($loc->lid)) { // a string exists
-            $lid = $loc->lid;
-            // update location field
-            db_query("UPDATE {locales_source} SET location = '%s' WHERE source = '%s'", $comments, $english);
-            $trans = db_fetch_object(db_query("SELECT lid, translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $lang));
-            if (!isset($trans->lid)) { // no translation in current language
-              db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '%s')", $lid, $lang, $translation);
-              $additions++;
-            } // translation exists
-            else if ($mode == 'overwrite') { //overwrite in any case
-              db_query("UPDATE {locales_target} SET translation = '%s' WHERE language = '%s' AND lid = %d", $translation, $lang, $lid);
-              if ($trans->translation == '') {
-                $additions++;
-              }
-              else {
-                $updates++;
-              }
-            } // overwrite if empty string
-            else if ($trans->translation == '') {
-              db_query("UPDATE {locales_target} SET translation = '%s' WHERE language = '%s' AND lid = %d", $translation, $lang, $lid);
-              $additions++;
-            }
-          }
-          else { // no string
-            db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', '%s')", $comments, $english, $group);
-            $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $english, $group));
-            $lid = $loc->lid;
-            db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '%s')", $lid, $lang, $translation);
-            if ($translation != '') {
-              $additions++;
-            }
-          }
+          _locale_import_one_string_db($report, $lang, $english, $translation, $group, $comments, $mode);
         }
       }
   } // end of db-store operation
 }
 
 /**
+ * Import one string into the database.
+ *
+ * @param $report
+ *   Report array summarizing the number of changes done in the form:
+ *   array(inserts, updates, deletes).
+ * @param $langcode
+ *   Language code to import string into.
+ * @param $source
+ *   Source string.
+ * @param $translation
+ *   Translation to language specified in $langcode.
+ * @param $textgroup
+ *   Name of textgroup to store translation in.
+ * @param $location
+ *   Location value to save with source string.
+ * @param $mode
+ *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
+ * @param $plid
+ *   Optional plural ID to use.
+ * @param $plural
+ *   Optional plural value to use.
+ */
+function _locale_import_one_string_db(&$report, $langcode, $source, $translation, $textgroup, $location, $mode, $plid = NULL, $plural = NULL) {
+  if (!empty($translation)) {
+    // If we have some translation to work with.
+    $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup));
+    if ($lid) {
+      // We have this source string saved already.
+      db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $location, $lid);
+      $exists = (bool) db_result(db_query("SELECT lid FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $langcode));
+      if (!$exists) {
+        // No translation in this language.
+        db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode, $translation, $plid, $plural);
+        $report[0]++;
+      }
+      else if ($mode == LOCALE_IMPORT_OVERWRITE) {
+        // Translation exists, only overvwrite if instructed.
+        db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE language = '%s' AND lid = %d", $translation, $plid, $plural, $langcode, $lid);
+        $report[1]++;
+      }
+    }
+    else {
+      // No such source string in the database yet.
+      db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', '%s')", $comments, $source, $textgroup);
+      $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $source, $textgroup));
+      db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode, $translation, $plid, $plural);
+      $report[0]++;
+    }
+  }
+  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+    // Empty translation, remove existing if instructed.
+    db_query("DELETE FROM {locales_target} WHERE language = '%s' AND lid = %d AND plid = %d AND plural = %d", $translation, $langcode, $lid, $plid, $plural);
+    $report[2]++;
+  }
+}
+
+/**
  * Parses a Gettext Portable Object file header
  *
  * @param $header
@@ -1280,18 +1289,18 @@ function _locale_import_one_string($op, 
  *   An associative array of key-value pairs
  */
 function _locale_import_parse_header($header) {
-  $hdr = array();
+  $header = array();
 
   $lines = explode("\n", $header);
   foreach ($lines as $line) {
     $line = trim($line);
     if ($line) {
       list($tag, $contents) = explode(":", $line, 2);
-      $hdr[trim($tag)] = trim($contents);
+      $header[trim($tag)] = trim($contents);
     }
   }
 
-  return $hdr;
+  return $header;
 }
 
 /**
@@ -1661,8 +1670,6 @@ function _locale_parse_js_file($filepath
       else {
         // We don't have the source string yet, thus we insert it into the database.
         db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', 'default')", $filepath, $string);
-        $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = 'default'", $string));
-        db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '')", $lid, $language->language);
       }
     }
   }
@@ -1688,10 +1695,10 @@ function _locale_export_po($language = N
   // Get language specific strings, or all strings
   if (isset($language)) {
     $meta = db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $language));
-    $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.textgroup = '%s' ORDER BY t.plid, t.plural", $language, $group);
+    $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.textgroup = '%s' ORDER BY t.plid, t.plural", $language, $group);
   }
   else {
-    $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = '%s' ORDER BY t.plid, t.plural", $group);
+    $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = '%s' ORDER BY t.plid, t.plural", $group);
   }
 
   // Build array out of the database results
@@ -1889,7 +1896,7 @@ function _locale_translate_seek() {
 
   // We have at least one criterion to match
   if ($query = _locale_translate_seek_query()) {
-    $join = "SELECT s.source, s.location, s.lid, s.textgroup, t.translation, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid ";
+    $join = "SELECT s.source, s.location, s.lid, s.textgroup, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid ";
 
     $arguments = array();
     // Compute LIKE section
@@ -2002,7 +2009,7 @@ function _locale_rebuild_js($langcode = 
 
   // Construct the array for JavaScript translations.
   // We sort on plural so that we have all plural forms before singular forms.
-  $result = db_query("SELECT s.lid, s.source, t.plid, t.plural, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.location LIKE '%%.js%%' AND s.textgroup = 'default' ORDER BY t.plural DESC", $language->language);
+  $result = db_query("SELECT s.lid, s.source, t.plid, t.plural, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.location LIKE '%%.js%%' AND s.textgroup = 'default' ORDER BY t.plural DESC", $language->language);
 
   $translations = $plurals = array();
   while ($data = db_fetch_object($result)) {
Index: modules/locale/locale.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v
retrieving revision 1.12
diff -u -p -r1.12 locale.install
--- modules/locale/locale.install	8 Jun 2007 12:51:59 -0000	1.12
+++ modules/locale/locale.install	9 Jun 2007 13:26:56 -0000
@@ -101,6 +101,15 @@ function locale_update_6003() {
 }
 
 /**
+ * Remove empty translations, we don't need these anymore.
+ */
+function locale_update_6004() {
+  $ret = array();
+  $ret[] = update_sql("DELETE FROM {locales_target} WHERE translation = ''");
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-5.x-to-6.x"
  */
 
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.177
diff -u -p -r1.177 locale.module
--- modules/locale/locale.module	30 May 2007 08:08:58 -0000	1.177
+++ modules/locale/locale.module	9 Jun 2007 13:26:56 -0000
@@ -309,34 +309,19 @@ function locale($string, $langcode = NUL
 
   // We do not have this translation cached, so get it from the DB.
   else {
-    $result = db_query("SELECT s.lid, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.source = '%s' AND t.language = '%s' AND s.textgroup = 'default'", $string, $langcode);
-    // Translation found
-    if ($trans = db_fetch_object($result)) {
-      if (!empty($trans->translation)) {
-        $locale_t[$langcode][$string] = $trans->translation;
-        $string = $trans->translation;
+    $translation = db_fetch_object(db_query("SELECT s.lid, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.source = '%s' AND t.language = '%s' AND s.textgroup = 'default'", $string, $langcode));
+    if ($translation) {
+      // We have the source string at least.
+      if ($translation->lid) {
+        // Cache translation string or TRUE if no translation exists.
+        $translation = (empty($translation->translation) ? TRUE : $translation->translation);
+        $locale_t[$langcode][$string] = $translation;
       }
     }
-
-    // Either we have no such source string, or no translation
     else {
-      $result = db_query("SELECT lid, source FROM {locales_source} WHERE source = '%s' AND textgroup = 'default'", $string);
-      // We have no such translation
-      if ($obj = db_fetch_object($result)) {
-        if ($langcode) {
-          db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '')", $obj->lid, $langcode);
-        }
-      }
-      // We have no such source string
-      else {
-        db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', 'default')", request_uri(), $string);
-        if ($langcode) {
-          $lid = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = 'default'", $string));
-          db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '')", $lid->lid, $langcode);
-        }
-      }
-      // Clear locale cache in DB
-      cache_clear_all('locale:'. $langcode, 'cache');
+      // We don't have the source string, cache this as untranslated.
+      db_query("INSERT INTO {locales_source} (location, source, textgroup) VALUES ('%s', '%s', 'default')", request_uri(), $string);
+      $locale_t[$langcode][$string] = TRUE;
     }
   }
 
@@ -353,7 +338,7 @@ function locale_refresh_cache() {
   $languages = $languages['1'];
 
   foreach ($languages as $language) {
-    $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.textgroup = 'default' AND LENGTH(s.source) < 75", $language->language);
+    $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.textgroup = 'default' AND LENGTH(s.source) < 75", $language->language);
     $t = array();
     while ($data = db_fetch_object($result)) {
       $t[$data->source] = (empty($data->translation) ? TRUE : $data->translation);
@@ -484,7 +469,7 @@ function _locale_batch_import($filepath,
   // we can extract the language code to use for the import from the end.
   if (preg_match('!(/|\.)([^\.]+)\.po$!', $filepath, $langcode)) {
     $file = (object) array('filename' => basename($filepath), 'filepath' => $filepath);
-    _locale_import_read_po('db-store', $file, 'keep', $langcode[2]);
+    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
     $context['results'][] = $filepath;
   }
 }
