Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.642
diff -u -p -r1.642 common.inc
--- includes/common.inc	25 May 2007 12:46:43 -0000	1.642
+++ includes/common.inc	25 May 2007 15:06:02 -0000
@@ -747,6 +747,28 @@ function t($string, $args = 0, $langcode
 }
 
 /**
+ * Dynamic object translation.
+ *
+ * @param $domain
+ *   Text domain to search string in. For the 'built-in' domain,
+ *   use the t() function. For other domains, use dt(). Domains
+ *   should be defined using hook_locale().
+ * @param $object_type
+ *   The name of the object type as specified in hook_locale().
+ * @param $object
+ *   Object to translate. Localizable properties defined in hook_locale()
+ *   are replaced with localized versions if available.
+ * @param $dt_language
+ *   Optional language code to look up the string in. Defaults to the page language. 
+ */
+function dt($domain, $object_type, $object, $dt_language = NULL) {
+  if (function_exists('locale_dynamic')) {
+    return locale_dynamic($domain, $object_type, $object, $dt_language);
+  }
+  return $object;
+}
+
+/**
  * @defgroup validation Input validation
  * @{
  * Functions to validate user input.
Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.129
diff -u -p -r1.129 locale.inc
--- includes/locale.inc	22 May 2007 07:42:36 -0000	1.129
+++ includes/locale.inc	25 May 2007 15:06:06 -0000
@@ -733,51 +733,62 @@ function locale_translate_export_po_form
  * User interface for string editing.
  */
 function locale_translate_edit_form($lid) {
-  $languages = language_list();
-  unset($languages['en']);
-
-  $result = db_query('SELECT DISTINCT s.source, t.translation, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.lid = %d', $lid);
-  $form = array();
-  $form['translations'] = array('#tree' => TRUE);
-  while ($translation = db_fetch_object($result)) {
-    $orig = $translation->source;
-
-    // Approximate the number of rows in a textfield with a maximum of 10.
-    $rows = min(ceil(str_word_count($orig) / 12), 10);
-
-    $form['translations'][$translation->language] = array(
-      '#type' => 'textarea',
-      '#title' => $languages[$translation->language]->name,
-      '#default_value' => $translation->translation,
-      '#rows' => $rows,
-    );
-    unset($languages[$translation->language]);
-  }
 
-  // Handle erroneous lid.
-  if (!isset($orig)) {
+  // Fetch source string, if possible.
+  $source = db_fetch_object(db_query('SELECT source, textgroup, location FROM {locales_source} WHERE lid = %d', $lid));
+  if (!$source) {
     drupal_set_message(t('String not found.'), 'error');
     drupal_goto('admin/build/translate/search');
   }
 
-  // Add original text. Assign negative weight so that it floats to the top.
-  $form['item'] = array('#type' => 'item',
-    '#title' => t('Original text'),
-    '#value' => check_plain(wordwrap($orig, 0)),
-    '#weight' => -1,
+  // Add original text to the top and some values for form altering.
+  $form = array(
+    'original' => array(
+      '#type'  => 'item',
+      '#title' => t('Original text'),
+      '#value' => check_plain(wordwrap($source->source, 0)),
+    ),
+    'lid' => array(
+      '#type'  => 'value',
+      '#value' => $lid
+    ),
+    'textgroup' => array(
+      '#type'  => 'value',
+      '#value' => $source->textgroup,
+    ),
+    'location' => array(
+      '#type'  => 'value',
+      '#value' => $source->location
+    ),
   );
 
+
+  // Include default form controls with empty values for all languages.
+  // This ensures that the languages are always in the same order in forms.
+  $languages = language_list();
+  $default = language_default();
+  // We don't need the default language value, that value is in $source.
+  $omit = $source->textgroup == 'default' ? 'en' : $default->language;
+  unset($languages[($omit)]);
+  $form['translations'] = array('#tree' => TRUE);
+  // Approximate the number of rows to use in the default textarea.
+  $rows = min(ceil(str_word_count($source->source) / 12), 10);
   foreach ($languages as $langcode => $language) {
     $form['translations'][$langcode] = array(
       '#type' => 'textarea',
       '#title' => t($language->name),
       '#rows' => $rows,
+      '#default_value' => '',
     );
   }
 
-  $form['lid'] = array('#type' => 'value', '#value' => $lid);
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
+  // Fetch translations and fill in default values in the form.
+  $result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = %d AND language != '%s'", $lid, $omit);
+  while ($translation = db_fetch_object($result)) {
+    $form['translations'][$translation->language]['#default_value'] = $translation->translation;
+  }
 
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
   return $form;
 }
 
@@ -1776,7 +1787,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
Index: modules/aggregator/aggregator.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/aggregator/aggregator.module,v
retrieving revision 1.339
diff -u -p -r1.339 aggregator.module
--- modules/aggregator/aggregator.module	14 May 2007 13:43:33 -0000	1.339
+++ modules/aggregator/aggregator.module	25 May 2007 15:06:06 -0000
@@ -200,6 +200,21 @@ function aggregator_menu() {
   return $items;
 }
 
+/**
+ * Implementation of hook_locale().
+ */
+function aggregator_locale($op = 'groups') {
+  switch($op) {
+    case 'groups':
+      return array('aggregator' => t('Aggregator'));
+    case 'objects':
+      return array('aggregator' => array(
+        'category' => array('cid', 'title', 'description'),
+        'feed'     => array('fid', 'title'),
+      ));
+  }
+}
+
 function aggregator_init() {
   drupal_add_css(drupal_get_path('module', 'aggregator') .'/aggregator.css');
 }
@@ -267,11 +282,11 @@ function aggregator_block($op = 'list', 
   if (user_access('access news feeds')) {
     if ($op == 'list') {
       $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
-      while ($category = db_fetch_object($result)) {
+      while ($category = dt('aggregator', 'category', db_fetch_object($result))) {
         $block['category-'. $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
       }
       $result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid');
-      while ($feed = db_fetch_object($result)) {
+      while ($feed = dt('aggregator', 'feed', db_fetch_object($result))) {
         $block['feed-'. $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
       }
     }
@@ -299,7 +314,7 @@ function aggregator_block($op = 'list', 
       list($type, $id) = explode('-', $delta);
       switch ($type) {
         case 'feed':
-          if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) {
+          if ($feed = dt('aggregator', 'feed', db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id)))) {
             $block['subject'] = check_plain($feed->title);
             $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block);
             $read_more = '<div class="more-link">'. l(t('more'), 'aggregator/sources/'. $feed->fid, array('title' => t("View this feed's recent news."))) .'</div>';
@@ -307,7 +322,7 @@ function aggregator_block($op = 'list', 
           break;
 
         case 'category':
-          if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) {
+          if ($category = dt('aggregator', 'category', db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id)))) {
             $block['subject'] = check_plain($category->title);
             $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = %d ORDER BY i.timestamp DESC, i.iid DESC', $category->cid, 0, $category->block);
             $read_more = '<div class="more-link">'. l(t('more'), 'aggregator/categories/'. $category->cid, array('title' => t("View this category's recent news."))) .'</div>';
@@ -421,14 +436,17 @@ function aggregator_form_category_submit
 function aggregator_save_category($edit) {
   if (!empty($edit['cid']) && !empty($edit['title'])) {
     db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']);
+    module_invoke('locale', 'dynamic_update', 'aggregator', 'category', (object) $edit);    
   }
   else if (!empty($edit['cid'])) {
     db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']);
+    module_invoke('locale', 'dynamic_update', 'aggregator', 'category', (object) $edit, 'delete');    
   }
   else if (!empty($edit['title'])) {
     // A single unique id for bundles and feeds, to use in blocks
-    $next_id = db_next_id('{aggregator_category}_cid');
-    db_query("INSERT INTO {aggregator_category} (cid, title, description, block) VALUES (%d, '%s', '%s', 5)", $next_id, $edit['title'], $edit['description']);
+    $edit['cid'] = db_next_id('{aggregator_category}_cid');
+    db_query("INSERT INTO {aggregator_category} (cid, title, description, block) VALUES (%d, '%s', '%s', 5)", $edit['cid'], $edit['title'], $edit['description']);
+    module_invoke('locale', 'dynamic_update', 'aggregator', 'category', (object) $edit);   
   }
 }
 
@@ -467,7 +485,7 @@ function aggregator_form_feed($edit = ar
   $options = array();
   $values = array();
   $categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = %d ORDER BY title', $edit['fid']);
-  while ($category = db_fetch_object($categories)) {
+  while ($category = dt('aggregator', 'category', db_fetch_object($categories))) {
     $options[$category->cid] = check_plain($category->title);
     if ($category->fid) $values[] = $category->cid;
   }
@@ -565,6 +583,7 @@ function aggregator_save_feed($edit) {
   }
   if ($edit['fid'] && $edit['title']) {
     db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']);
+    module_invoke('locale', 'dynamic_update', 'aggregator', 'feed', (object) $edit);
   }
   else if ($edit['fid']) {
     $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
@@ -576,11 +595,13 @@ function aggregator_save_feed($edit) {
     }
     db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']);
     db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
+    module_invoke('locale', 'dynamic_update', 'aggregator', 'feed', (object) $edit, 'delete');
   }
   else if ($edit['title']) {
     // A single unique id for bundles and feeds, to use in blocks.
     $edit['fid'] = db_next_id('{aggregator_feed}_fid');
     db_query("INSERT INTO {aggregator_feed} (fid, title, url, refresh, block) VALUES (%d, '%s', '%s', %d, 5)", $edit['fid'], $edit['title'], $edit['url'], $edit['refresh']);
+    module_invoke('locale', 'dynamic_update', 'aggregator', 'feed', (object) $edit);
   }
   if ($edit['title']) {
     // The feed is being saved, save the categories as well.
@@ -1010,7 +1031,7 @@ function aggregator_view() {
 
   $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
   $rows = array();
-  while ($feed = db_fetch_object($result)) {
+  while ($feed = dt('aggregator', 'feed', db_fetch_object($result))) {
     $rows[] = array(l($feed->title, "aggregator/sources/$feed->fid"), format_plural($feed->items, '1 item', '@count items'), ($feed->checked ? t('@time ago', array('@time' => format_interval(time() - $feed->checked))) : t('never')), ($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - time()))) : t('never')), l(t('edit'), "admin/content/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/content/aggregator/remove/$feed->fid"), l(t('update items'), "admin/content/aggregator/update/$feed->fid"));
   }
   $output .= theme('table', $header, $rows);
@@ -1021,7 +1042,7 @@ function aggregator_view() {
 
   $header = array(t('Title'), t('Items'), t('Operations'));
   $rows = array();
-  while ($category = db_fetch_object($result)) {
+  while ($category = dt('aggregator', 'category', db_fetch_object($result))) {
     $rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/content/aggregator/edit/category/$category->cid"));
   }
   $output .= theme('table', $header, $rows);
@@ -1065,7 +1086,7 @@ function aggregator_page_last() {
  * Menu callback; displays all the items captured from a particular feed.
  */
 function aggregator_page_source() {
-  $feed = db_fetch_object(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', arg(2)));
+  $feed = dt('aggregator', 'feed', db_fetch_object(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', arg(2))));
   drupal_set_title(check_plain($feed->title));
   $info = theme('aggregator_feed', $feed);
 
@@ -1076,7 +1097,7 @@ function aggregator_page_source() {
  * Menu callback; displays all the items aggregated in a particular category.
  */
 function aggregator_page_category() {
-  $category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
+  $category = dt('aggregator', 'category', db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2))));
 
   drupal_add_feed(url('aggregator/rss/'. arg(2)), variable_get('site_name', 'Drupal') .' '. t('aggregator - @title', array('@title' => $category->title)));
 
@@ -1099,7 +1120,7 @@ function aggregator_page_list($sql, $hea
     if ($categorize) {
       $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = %d', $item->iid);
       $selected = array();
-      while ($category = db_fetch_object($categories_result)) {
+      while ($category = dt('aggregator', 'category', db_fetch_object($categories_result))) {
         if (!$done) {
           $categories[$category->cid] = check_plain($category->title);
         }
@@ -1188,7 +1209,7 @@ function aggregator_page_list_submit($fo
 function aggregator_page_sources() {
   $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
   $output = "<div id=\"aggregator\">\n";
-  while ($feed = db_fetch_object($result)) {
+  while ($feed = dt('aggregator', 'feed', db_fetch_object($result))) {
     $output .= '<h2>'. check_plain($feed->title) ."</h2>\n";
 
     // Most recent items:
@@ -1220,7 +1241,7 @@ function aggregator_page_rss() {
   // arg(2) is the passed cid, only select for that category
   $result = NULL;
   if (arg(2)) {
-    $category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
+    $category = dt('aggregator', 'category', db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2))));
     $url = '/categories/'. $category->cid;
     $title = ' '. t('in category') .' '. $category->title;
     $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = %d ORDER BY timestamp DESC, iid DESC';
@@ -1276,7 +1297,7 @@ function aggregator_page_opml($cid = NUL
   $output .= "</head>\n";
   $output .= "<body>\n";
 
-  while ($feed = db_fetch_object($result)) {
+  while ($feed = dt('aggregator', 'feed', db_fetch_object($result))) {
     $output .= '<outline text="'. check_plain($feed->title) .'" xmlUrl="'. check_url($feed->url) ."\" />\n";
   }
 
@@ -1294,7 +1315,7 @@ function aggregator_page_categories() {
   $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description');
   $output = "<div id=\"aggregator\">\n";
 
-  while ($category = db_fetch_object($result)) {
+  while ($category = dt('aggregator', 'category', db_fetch_object($result))) {
     $output .= '<h2>'. check_plain($category->title) ."</h2>\n";
     if (variable_get('aggregator_summary_items', 3)) {
       $list = array();
@@ -1413,7 +1434,7 @@ function theme_aggregator_page_item($ite
 
   $result = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = %d ORDER BY c.title', $item->iid);
   $categories = array();
-  while ($category = db_fetch_object($result)) {
+  while ($category = dt('aggregator', 'category', db_fetch_object($result))) {
     $categories[] = l($category->title, 'aggregator/categories/'. $category->cid);
   }
   if ($categories) {
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.174
diff -u -p -r1.174 locale.module
--- modules/locale/locale.module	22 May 2007 07:42:37 -0000	1.174
+++ modules/locale/locale.module	25 May 2007 15:06:06 -0000
@@ -389,6 +389,154 @@ function locale_get_plural($count) {
   return $plurals[$count];
 }
 
+/**
+ * Dynamic object translation.
+ *
+ * @param $group
+ *   Text group to search strings in. For the 'default' group,
+ *   use the t() function. For other groups, use dt(). Groups
+ *   should be defined using hook_locale().
+ * @param $object_type
+ *   The name of the object type as specified in hook_locale().
+ * @param $object
+ *   Object to translate. Localizable properties defined in hook_locale()
+ *   are replaced with localized versions if available.
+ * @param $dt_language
+ *   Optional language code to look up strings in.
+ *   Defaults to the language used to generate the page.
+ *
+ * @todo
+ *   - We could add a cache property to $object[$group][$object_type]['cache'] = TRUE | FALSE
+ *   - An empty string may be a valid translation too!! Let's allow some flexibility.
+ */
+function locale_dynamic($group, $object_type, $object, $dt_language = NULL) {
+  global $language;
+  static $objects = NULL;
+  static $cache = array();
+
+  // Cache object metadata provided by modules for translation.
+  if (!isset($objects)) {
+    $objects = module_invoke_all('locale', 'objects');
+  }
+  
+  // Fall back on default language if not instructed otherwise.
+  $default = language_default();  
+  if (!isset($dt_language)) {
+    $dt_language = $language->language;
+  }
+  
+  // Skip if default language or found no descriptor for the object.
+  if (($dt_language == $default->language) || !isset($objects[$group][$object_type])) {
+    return $object;
+  }
+  
+  // Allow groups to be translated with custom callbacks by contributed
+  // modules, enabling better caching tailored for specific needs.
+  if (isset($objects[$group]['#callback']) && function_exists($objects[$group]['#callback'])) {
+    return call_user_func($objects[$group]['#callback'], $group, $object_type, $object, $dt_language);
+  }
+  
+  // Build a list of locations searched for.
+  $properties = $objects[$group][$object_type];
+  $idname = array_shift($properties);
+  $locations = array();  
+  foreach ($properties as $property) {
+    // Only try to look up properties present in the passed object.
+    if (isset($object->$property)) {
+      $location = $object_type .':'. $object->$idname .':'. $property;
+      // Try to find in cached properties first.
+      if (isset($cache[$dt_language][$group][$location])) {
+        // Nonexistent translations are cached as NULL.
+        $object->property = is_null($cache[$dt_language][$group][$location]) ? $object->property : $cache[$dt_language][$group][$location];
+      } else {
+        $locations[] = $location;
+        // Expecting that we have no translation for this, or will
+        // replace this NULL later with a value.
+        $cache[$dt_language][$group][$location] = NULL;
+      }
+    }
+  }  
+  
+  // We assume source strings will exist in the database, because
+  // locale_dynamic_update() should have been called earlier for
+  // the object. We search the database if not all properties were
+  // in our local cache.
+  if (count($locations)) {
+    $result = db_query("SELECT s.location, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND s.location IN ('". join("','", $locations) ."') AND s.textgroup = '%s'", $dt_language, $group);
+  
+    // Translate every object property for which we have translation.
+    while ($row = db_fetch_object($result)) {
+      list (,,$property) = explode(':', $row->location);
+      if ($object->$property == $row->source) {
+        $object->$property = $row->translation;
+      }
+      $cache[$dt_language][$group][$row->location] = $row->translation;
+    }
+  }
+
+  return $object;
+}
+
+/**
+ * Update dynamic strings from objects.
+ * Create new entries and remove old ones.
+ * 
+ * @param $group
+ *   Text group to save the strings in. This function should not be used for 
+ *   the 'default' group! Groups should be defined using hook_locale().
+ * @param $object_type
+ *   The name of the object type as specified in hook_locale().
+ * @param $object
+ *   Object to save. Localizable properties defined in hook_locale() are saved.
+ * @param $op
+ *   'update' (default) to create/update existing source strings.
+ *   'delete' to remove existing entries.
+ */
+function locale_dynamic_update($group, $object_type, $object, $op = 'update') {
+  static $objects = NULL;
+  
+  // Cache object metadata provided by modules for translation.
+  if (!isset($objects)) {
+    $objects = module_invoke_all('locale', 'objects');
+  }
+
+  // Return with error if no information found about this object type.
+  if (!isset($objects[$group][$object_type])) {
+    return FALSE;
+  }
+
+  // Build a list of locations we need to work with.
+  $properties = $objects[$group][$object_type];
+  $idname = array_shift($properties);
+  $locations = array();  
+  foreach ($properties as $property) {
+    $locations[$property] = $object_type .':'. $object->$idname .':'. $property;
+  }  
+
+  switch ($op) {
+    case 'update':
+      foreach ($locations as $property => $location) {
+        if ($source = db_fetch_object(db_query("SELECT * FROM {locales_source} WHERE location = '%s' AND textgroup = '%s'", $location, $group))) {
+          // Update entry only if source string changed.
+          if ($source->source != $object->$property) {
+            db_query("UPDATE {locales_source} SET source = '%s' WHERE location = '%s' AND textgroup = '%s'", $object->$property, $location, $group);
+          }
+        } else { 
+          // Create new entry.
+          db_query("INSERT INTO {locales_source} (source, location, textgroup) VALUES('%s', '%s', '%s')", $object->$property, $location, $group);
+        }
+      }
+      break;
+    case 'delete':
+      foreach ($locations as $location) {
+        if ($lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE location = '%s' AND textgroup = '%s'", $location, $group))) {
+          db_query("DELETE FROM {locales_source} WHERE lid = %d", $lid);
+          db_query("DELETE FROM {locales_target} WHERE lid = %d", $lid);
+        }
+      }
+      break;
+  }
+}
 
 /**
  * Returns a language name
