Index: views_ui.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/views_ui.module,v
retrieving revision 1.109
diff -u -p -r1.109 views_ui.module
--- views_ui.module	30 Jan 2009 00:56:01 -0000	1.109
+++ views_ui.module	5 Jul 2009 16:55:52 -0000
@@ -233,6 +233,10 @@ function views_ui_cache_load($name) {
       // Check to see if someone else is already editing this view.
       global $user;
       $view->locked = db_fetch_object(db_query("SELECT s.uid, v.updated FROM {views_object_cache} v INNER JOIN {sessions}  s ON v.sid = s.sid WHERE s.sid != '%s' and v.name = '%s' and v.obj = 'view' ORDER BY v.updated ASC", session_id(), $view->name));
+      // Set a flag to indicate that this view is being edited.
+      // This flag will be used e.g. to determine whether strings
+      // should be localized.
+      $view->editing = TRUE;
     }
   }
 
Index: help/views.help.ini
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/help/views.help.ini,v
retrieving revision 1.17
diff -u -p -r1.17 views.help.ini
--- help/views.help.ini	22 Apr 2009 07:03:35 -0000	1.17
+++ help/views.help.ini	5 Jul 2009 16:55:52 -0000
@@ -159,6 +159,9 @@ parent = analyze-theme
 [overrides]
 title = What are overrides?
 
+[localization]
+title = Localizing views data like header and footer text
+
 [embed]
 title = Embedding a view into other parts of your site
 
Index: includes/admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/admin.inc,v
retrieving revision 1.154.2.6
diff -u -p -r1.154.2.6 admin.inc
--- includes/admin.inc	10 Jun 2009 22:02:26 -0000	1.154.2.6
+++ includes/admin.inc	5 Jul 2009 16:55:53 -0000
@@ -2741,6 +2741,14 @@ function views_ui_admin_tools() {
     '#default_value' => variable_get('views_no_javascript', FALSE),
   );
 
+  $form['views_localization_plugin'] =  array(
+    '#type' => 'radios',
+    '#title' => t('Translation method'),
+    '#options' => views_fetch_plugin_names('localization', NULL, array(), TRUE),
+    '#default_value' => variable_get('views_localization_plugin', 'core'),
+    '#description' => t('Select a translation method to use for Views data like header, footer, and empty text.'),
+  );
+ 
   $regions = system_region_list(variable_get('theme_default', 'garland'));
   $regions['watchdog'] = t('Watchdog');
 
Index: includes/base.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/base.inc,v
retrieving revision 1.2.2.1
diff -u -p -r1.2.2.1 base.inc
--- includes/base.inc	25 Jun 2009 22:15:36 -0000	1.2.2.1
+++ includes/base.inc	5 Jul 2009 16:55:53 -0000
@@ -19,7 +19,8 @@ class views_object {
    * @code
    * 'option_name' => array(
    *  - 'default' => default value,
-   *  - 'translatable' => TRUE/FALSE (wrap in t() on export if true),
+   *  - 'translatable' => TRUE/FALSE (use a localization plugin on display and
+   *      wrap in t() on export if true),
    *  - 'contains' => array of items this contains, with its own defaults, etc.
    *      If contains is set, the default will be ignored and assumed to
    *      be array()
@@ -28,8 +29,6 @@ class views_object {
    *  @endcode
    * Each option may have any of the following functions:
    *  - export_option_OPTIONNAME -- Special export handling if necessary.
-   *  - translate_option_OPTIONNAME -- Special handling for translating data
-   *    within the option, if necessary.
    */
   function option_definition() { return array(); }
 
@@ -76,7 +75,7 @@ class views_object {
    * Unpack options over our existing defaults, drilling down into arrays
    * so that defaults don't get totally blown away.
    */
-  function unpack_options(&$storage, $options, $definition = NULL) {
+  function unpack_options(&$storage, $options, $definition = NULL, $localization_keys = array()) {
     if (!is_array($options)) {
       return;
     }
@@ -85,17 +84,35 @@ class views_object {
       $definition = $this->option_definition();
     }
 
+    // Ensure we have a localization plugin.
+    $this->view->init_localization();
     foreach ($options as $key => $value) {
+
       if (is_array($value)) {
         if (!isset($storage[$key]) || !is_array($storage[$key])) {
           $storage[$key] = array();
         }
 
         $this->unpack_options($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array());
+        $this->unpack_options($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array(), array_merge($localization_keys, array($key)));
       }
-      else if (!empty($definition[$key]['translatable']) && !empty($value)) {
-        $storage[$key] = t($value);
-      }
+      // Don't localize strings during editing. When editing, we need to work with
+      // the original data, not the translated version.
+      else if (!$this->view->editing && (!empty($definition[$key]['translatable']) || !empty($definition['contains'][$key]['translatable'])) && !empty($value)) {
+        // Allow other modules to make changes to the string before it's
+        // sent for translation.
+        // Look for a propertyname_format property.
+        $translation_data = $this->view->invoke_translation_process($value, isset($options[$key . '_format']) ? $options[$key . '_format'] : NULL, 'pre');
+        if ($this->view->is_translatable()) {
+          // The $keys array is built from the view name, any localization keys
+          // sent in, and the name of the property being processed.
+          $storage[$key] = $this->view->localization_plugin->translate($value, array_merge(array($this->view->name), $localization_keys, array($key)));
+        }
+        // Otherwise, this is a code-based string, so we can use t().
+        else {
+          $storage[$key] = t($value);
+        }
+        $this->view->invoke_translation_process($value, $translation_data, 'post');
       else {
         $storage[$key] = $value;
       }
Index: includes/plugins.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/plugins.inc,v
retrieving revision 1.152.2.3
diff -u -p -r1.152.2.3 plugins.inc
--- includes/plugins.inc	2 Jun 2009 20:22:24 -0000	1.152.2.3
+++ includes/plugins.inc	5 Jul 2009 16:55:53 -0000
@@ -11,7 +11,7 @@
  */
 function views_views_plugins() {
   $path = drupal_get_path('module', 'views') . '/js';
-  return array(
+  $plugins = array(
     'module' => 'views', // This just tells our themes are elsewhere.
     'display' => array(
       'parent' => array(
@@ -267,7 +267,31 @@ function views_views_plugins() {
         'help topic' => 'cache-time',
       ),
     ),
+    'localization' => array(
+      'parent' => array(
+        'no ui' => TRUE,
+        'handler' => 'views_plugin_localization',
+        'parent' => '',
+      ),
+      'none' => array(
+        'title' => t('None'),
+        'help' => t('Do not pass admin strings for translation.'),
+        'handler' => 'views_plugin_localization_none',
+        'help topic' => 'localization-none',
+      ),
+      'core' => array(
+        'title' => t('Core'),
+        'help' => t("Use Drupal core t() function. Not recommended, as it doesn't support updates to existing strings."),
+        'handler' => 'views_plugin_localization_core',
+        'help topic' => 'localization-core',
+      ),
+    ),
   );
+  // Add a help message pointing to the i18views module if it is not present.
+  if (!module_exists('i18nviews')) {
+    $plugins['localization']['core']['help'] .= ' ' . t('If you need to translate Views labels into other languages, consider installing the <a href="!path">Internationalization</a> package\'s Views translation module.', array('!path' => url('http://drupal.org/project/i18n', array('absolute' => TRUE))));
+  }
+  return $plugins;
 }
 
 /**
Index: includes/view.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/view.inc,v
retrieving revision 1.151.2.12
diff -u -p -r1.151.2.12 view.inc
--- includes/view.inc	1 Jul 2009 15:36:26 -0000	1.151.2.12
+++ includes/view.inc	5 Jul 2009 16:55:53 -0000
@@ -23,6 +23,7 @@ class view extends views_db_object {
   // State variables
   var $built = FALSE;
   var $executed = FALSE;
+  var $editing = FALSE;
 
   var $args = array();
   var $build_info = array();
@@ -62,6 +63,9 @@ class view extends views_db_object {
     }
 
     $this->query = new stdClass();
+
+    // Initialize localization.
+    $this->init_localization();
   }
 
   /**
@@ -1319,6 +1323,9 @@ class view extends views_db_object {
       $this->_save_rows($key);
     }
 
+    // Save data for translation.
+    $this->save_locale_strings();
+
     cache_clear_all('views_urls', 'cache_views');
     cache_clear_all(); // clear the page cache as well.
   }
@@ -1344,6 +1351,8 @@ class view extends views_db_object {
       return;
     }
 
+    $this->delete_locale_strings();
+
     db_query("DELETE FROM {views_view} WHERE vid = %d", $this->vid);
     // Delete from all of our subtables as well.
     foreach ($this->db_objects() as $key) {
@@ -1399,6 +1408,15 @@ class view extends views_db_object {
       }
     }
 
+    // Give the localization system a chance to export translatables to code.
+    if ($this->init_localization()) {
+      $this->export_locale_strings('export');
+      $translatables = $this->localization_plugin->export_render($indent);
+      if (!empty($translatables)) {
+        $output .= $translatables;
+      }
+    }
+
     return $output;
   }
 
@@ -1521,6 +1539,137 @@ class view extends views_db_object {
 
     return $errors ? $errors : TRUE;
   }
+
+  /**
+   * Find and initialize the localizer plugin.
+   */
+  function init_localization() {
+    if (isset($this->localization_plugin) && is_object($this->localization_plugin)) {
+      return TRUE;
+    }
+
+    $this->localization_plugin = views_get_plugin('localization', variable_get('views_localization_plugin', 'core'));
+
+    if (empty($this->localization_plugin)) {
+      return FALSE;
+    }
+
+    $this->localization_plugin->init($this);
+
+    return TRUE;
+  }
+
+  /**
+   * Determine whether a view supports admin string translation.
+   */
+  function is_translatable() {
+    // If the view is normal or overridden, use admin string translation.
+    // A newly created view won't have a type. Accept this.
+    return (!isset($this->type) || in_array($this->type, array(t('Normal'), t('Overridden')))) ? TRUE : FALSE;
+  }
+
+  /**
+   * Send strings for localization.
+   */
+  function save_locale_strings() {
+    $this->process_locale_strings('save');
+  }
+
+  /**
+   * Delete localized strings.
+   */
+  function delete_locale_strings() {
+    $this->process_locale_strings('delete');
+  }
+
+  /**
+   * Export localized strings.
+   */
+  function export_locale_strings() {
+    $this->process_locale_strings('export');
+  }
+
+  /**
+   * Process strings for localization, deletion or export to code.
+   */
+  function process_locale_strings($op) {
+    // Ensure this view supports translation, we have a display, and we
+    // have a localization plugin.
+    if (($this->is_translatable() || $op == 'export') && $this->init_display() && $this->init_localization()) {
+      foreach ($this->display as $display_id => $display) {
+        $translatable = array();
+        // Special handling for display title.
+        if (isset($display->display_title)) {
+          $translatable[] = array($display->display_title, array('display_title'));
+        }
+        $this->unpack_translatable($translatable, $display_id, $display->display_options);
+        foreach ($translatable as $data) {
+          list($string, $keys) = $data;
+          switch ($op) {
+            case 'save':
+              $this->localization_plugin->save($string, array_merge(array($this->name, $display_id), $keys));
+              break;
+            case 'delete':
+              $this->localization_plugin->delete($string, array_merge(array($this->name, $display_id), $keys));
+              break;
+            case 'export':
+              $this->localization_plugin->export($string, array_merge(array($this->name, $display_id), $keys));
+              break;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Unpack translatable properties and their values.
+   */
+  function unpack_translatable(&$translatable, $display_id, $options, $definition = NULL, $keys = array()) {
+
+    if (!is_array($options)) {
+      return;
+    }
+
+    // Ensure we have displays with handlers.
+    $this->init_display();
+
+    if (!isset($definition)) {
+      $definition = $this->display[$display_id]->handler->option_definition();
+    }
+
+    foreach ($options as $key => $value) {
+      if (is_array($value)) {
+        $this->unpack_translatable($translatable, $display_id, $value, isset($definition[$key]) ? $definition[$key] : array(), array_merge($keys, array($key)));
+      }
+      else if (!empty($definition[$key]['translatable']) && !empty($value)) {
+        // Allow other modules to make changes to the string before it's
+        // sent for translation.
+        // Look for a propertyname_format property.
+        $this->invoke_translation_process($value, isset($options[$key . '_format']) ? $options[$key . '_format'] : NULL, 'pre');
+        $translatable[] = array($value, array_merge($keys, array($key)));
+      }
+    }
+  }
+
+  /**
+   * Invoke hook_translation_pre_process() or hook_translation_post_process().
+   *
+   * Like node_invoke_nodeapi(), this function is needed to enable both passing
+   * by reference and fetching return values.
+   */
+  function invoke_translation_process(&$value, $arg, $op) {
+    $return = array();
+    $hook = 'translation_' . $op . '_process';
+    foreach (module_implements($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $result = $function($value, $arg);
+      if (isset($result)) {
+        $return[$module] = $result;
+      }
+    }
+    return $return;
+  }
+
 }
 
 /**
Index: plugins/views_plugin_display.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/plugins/views_plugin_display.inc,v
retrieving revision 1.20.2.6
diff -u -p -r1.20.2.6 views_plugin_display.inc
--- plugins/views_plugin_display.inc	26 Jun 2009 00:23:37 -0000	1.20.2.6
+++ plugins/views_plugin_display.inc	5 Jul 2009 16:55:53 -0000
@@ -40,7 +40,9 @@ class views_plugin_display extends views
       unset($options['defaults']);
     }
 
-    $this->unpack_options($this->options, $options);
+    // Last argument is an array of keys to be used in identifying
+    // strings for translation.
+    $this->unpack_options($this->options, $options, NULL, array($display->id));
   }
 
   function destroy() {
