diff --git includes/base.inc includes/base.inc
index 98625b6..98798f8 100644
--- includes/base.inc
+++ includes/base.inc
@@ -196,4 +196,76 @@ class views_object {
     }
     return $output;
   }
+
+  /**
+   * Unpacks each handler to store translatable texts.
+   */
+  function unpack_translatables(&$translatable) {
+    dsm(get_class($this));
+    foreach ($this->option_definition() as $option => $definition) {
+      $this->unpack_translatable($translatable, $this->options, $option, $definition, array(), array());
+    }
+  }
+
+  /**
+   * Unpack a single option definition.
+   */
+  function unpack_translatable(&$translatable, $storage, $option, $definition, $parents, $keys = array()) {
+//     dvm($definition);
+
+    // Do not export options for which we have no settings.
+    if (!isset($storage[$option])) {
+      return;
+    }
+
+    // Special handling for some items
+    if (isset($definition['unpack_translatable']) && method_exists($this, $definition['unpack_translatable'])) {
+      return $this->{$definition['unpack_translatable']}($translatable, $storage, $option, $definition, $parents, $keys);
+    }
+
+    if (isset($definition['translatable'])) {
+      if ($definition['translatable'] === FALSE) {
+        return;
+      }
+    }
+
+    // Add the current option to the parents tree.
+    $parents[] = $option;
+
+    // If it has child items, unpack those separately.
+    if (isset($definition['contains'])) {
+      foreach ($definition['contains'] as $sub_option => $sub_definition) {
+        $translation_keys = array_merge($keys, array($sub_option));
+        $this->unpack_translatable($translatable, $storage[$option], $sub_option, $sub_definition, $parents, $translation_keys);
+      }
+    }
+
+    // @todo Figure out this double definition stuff.
+    $options = $storage[$option];
+    if (is_array($options)) {
+      foreach ($options as $key => $value) {
+        $translation_keys = array_merge($keys, array($key));
+        if (is_array($value)) {
+          $this->unpack_translatable($translatable, $storage, $key, $definition, $parents, $translation_keys);
+        }
+        else if (!empty($definition[$key]['translatable']) && !empty($value)) {
+          // Build source data and add to the array
+          $translatable[] = array(
+            'value' => $value,
+            'keys' => $translation_keys,
+            'format' => isset($options[$key . '_format']) ? $options[$key . '_format'] : NULL,
+          );
+        }
+      }
+    }
+    else if (!empty($definition['translatable']) && !empty($options)) {
+      $value = $options;
+      // Build source data and add to the array
+      $translatable[] = array(
+        'value' => $value,
+        'keys' => $translation_keys,
+        'format' => isset($options[$option . '_format']) ? $options[$option . '_format'] : NULL,
+      );
+    }
+  }
 }
diff --git includes/plugins.inc includes/plugins.inc
index b30145b..e8c16ba 100644
--- includes/plugins.inc
+++ includes/plugins.inc
@@ -325,7 +325,30 @@ function views_views_plugins() {
         'parent' => 'full',
       ),
     ),
+    '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))));
+  }
 
   if (module_invoke('ctools', 'api_version', '1.3')) {
     $plugins['style']['jump_menu_summary'] = array(
diff --git includes/view.inc includes/view.inc
index d6cbee8..4cf0341 100644
--- includes/view.inc
+++ includes/view.inc
@@ -1454,6 +1454,15 @@ class view extends views_db_object {
       $output .= $display->handler->export_options($indent, '$handler->options');
     }
 
+    // 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;
   }
 
@@ -1587,6 +1596,95 @@ 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;
+    }
+
+    /**
+    * Figure out whether there should be options.
+    */
+    $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.
+    // @fixme Export does not init every handler.
+    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('value' => $display->display_title, 'keys' => array('display_title'));
+        }
+//         $this->unpack_translatable($translatable, $display_id, $display->display_options);
+        // Unpack handlers
+        $this->display[$display_id]->handler->unpack_translatables($translatable);
+        foreach ($translatable as $data) {
+          $data['keys'] = array_merge(array($this->name, $display_id), $data['keys']);
+          list($string, $keys) = $data;
+          switch ($op) {
+            case 'save':
+              $this->localization_plugin->save($data);
+              break;
+            case 'delete':
+              $this->localization_plugin->delete($data);
+              break;
+            case 'export':
+              $this->localization_plugin->export($data);
+              break;
+          }
+        }
+      }
+    }
+  }
+
 }
 
 /**
diff --git plugins/views_plugin_display.inc plugins/views_plugin_display.inc
index 35a6653..3a00465 100644
--- plugins/views_plugin_display.inc
+++ plugins/views_plugin_display.inc
@@ -424,7 +424,7 @@ class views_plugin_display extends views_plugin {
       // should be copied.
       'exposed_form' => array(
         'contains' => array(
-          'type' => array('default' => 'basic', 'export' => 'export_plugin'),
+          'type' => array('default' => 'basic', 'export' => 'export_plugin', 'unpack_translatable' => 'unpack_plugin'),
           'options' => array('default' => array(), 'export' => FALSE),
          ),
       ),
@@ -2345,6 +2345,19 @@ class views_plugin_display extends views_plugin {
 
     return $output;
   }
+  
+  /**
+   * Special handling for plugin unpacking.
+   */
+  function unpack_plugin(&$translatable, $storage, $option, $definition, $parents) {
+    $plugin_type = end($parents);
+    $plugin = $this->get_plugin($plugin_type);
+    if ($plugin) {
+      // Write which plugin to use.
+      return $plugin->unpack_translatables($translatable);
+    }
+
+  }
 }
 
 
diff --git plugins/views_plugin_localization.inc plugins/views_plugin_localization.inc
new file mode 100644
index 0000000..12e25d6
--- /dev/null
+++ plugins/views_plugin_localization.inc
@@ -0,0 +1,127 @@
+<?php
+// $Id: $
+
+/**
+ * @file
+ * Contains the base class for views localization plugins.
+ */
+
+/**
+ * The base plugin to handle localization of Views strings.
+ *
+ * @ingroup views_localization_plugins
+ */
+class views_plugin_localization extends views_plugin {
+  // Store for exported strings
+  var $export_strings = array();
+
+  /**
+   * Initialize the plugin.
+   *
+   * @param $view
+   *   The view object.
+   */
+  function init(&$view) {
+    $this->view = &$view;
+  }
+
+  /**
+   * Translate a string / text with format
+   * 
+   * The $source parameter is an array with the following elements:
+   * - value, source string
+   * - format, input format in case the text has some format to be applied
+   * - keys. An array of keys to identify the string. Generally constructed from
+   *   view name, display_id, and a property, e.g., 'header'.
+   *
+   * @param $source
+   *   Full data for the string to be translated.
+   *
+   * @return string
+   *   Translated string / text
+   */
+  function translate($source) {
+    // Allow other modules to make changes to the string before and after translation
+    $source['pre_process'] = $this->invoke_translation_process($source, 'pre');
+    $source['translation'] = $this->translate_string($source['value'], $source['keys']);
+    $source['post_process'] = $this->invoke_translation_process($source, 'post');
+    return $source['translation'];
+  }
+
+  /**
+   * Translate a string.
+   *
+   * @param $string
+   *   The string to be translated.
+   * @param $keys
+   *   An array of keys to identify the string. Generally constructed from
+   *   view name, display_id, and a property, e.g., 'header'.
+   */
+  function translate_string($string, $keys = array()) {}
+  
+  /**
+   * Save string source for translation.
+   *
+   * @param $source
+   *   Full data for the string to be translated.
+   */
+  function save($source) {
+    // Allow other modules to make changes to the string before saving
+    $source['pre_process'] = $this->invoke_translation_process($source, 'pre');
+    $this->save_string($source['value'], $source['keys']);
+  }
+
+  /**
+   * Save a string for translation
+   *
+   * @param $string
+   *   The string to be translated.
+   * @param $keys
+   *   An array of keys to identify the string. Generally constructed from
+   *   view name, display_id, and a property, e.g., 'header'.
+   */
+  function save_string($string, $keys = array()) {}
+
+  /**
+   * Delete a string.
+   *
+   * @param $source
+   *   Full data for the string to be translated.
+   */
+  function delete($source) { }
+
+  /**
+   * Collect strings to be exported to code.
+   *
+   * @param $source
+   *   Full data for the string to be translated.
+   */
+  function export($source) { }
+
+  /**
+   * Render any collected exported strings to code.
+   *
+   * @param $indent
+   *   An optional indentation for prettifying nested code.
+   */
+  function export_render($indent = '  ') { }
+
+  /**
+   * 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, $op) {
+    $return = array();
+    $hook = 'translation_' . $op . '_process';
+    foreach (module_implements($hook) as $module) {
+      $function = $module . '_' . $hook;
+      $result = $function($value);
+      if (isset($result)) {
+        $return[$module] = $result;
+      }
+    }
+    return $return;
+  }
+}
diff --git plugins/views_plugin_localization_core.inc plugins/views_plugin_localization_core.inc
new file mode 100644
index 0000000..f7a2983
--- /dev/null
+++ plugins/views_plugin_localization_core.inc
@@ -0,0 +1,104 @@
+<?php
+// $Id: $
+
+/**
+ * @file
+ * Contains the Drupal core localization plugin.
+ */
+
+/**
+ * Localization plugin to pass translatable strings through t().
+ *
+ * @ingroup views_localization_plugins
+ */
+class views_plugin_localization_core extends views_plugin_localization {
+
+  /**
+   * Translate a string.
+   *
+   * @param $string
+   *   The string to be translated.
+   * @param $keys
+   *   An array of keys to identify the string. Generally constructed from
+   *   view name, display_id, and a property, e.g., 'header'.
+   */
+  function translate_string($string, $keys = array()) {
+    return t($string); 
+  }
+
+  /**
+   * Save a string for translation.
+   *
+   * @param $string
+   *   The string to be translated.
+   * @param $keys
+   *   An array of keys to identify the string. Generally constructed from
+   *   view name, display_id, and a property, e.g., 'header'.
+   */
+  function save_string($string, $keys) {
+    global $language;
+
+    // If the current language is 'en', we need to reset the language
+    // in order to trigger an update.
+    // TODO: add test for number of languages.
+    if ($language->language == 'en') {
+      $changed = TRUE;
+      $languages = language_list();
+      $cached_language = $language;
+      unset($languages['en']);
+      $language = current($languages);
+    }
+
+    t($string);
+
+    if (isset($cached_language)) {
+      $language = $cached_language;
+    }
+    return TRUE;
+  }
+
+  /**
+   * Delete a string.
+   *
+   * Deletion is not supported.
+   *
+   * @param $source
+   *   Full data for the string to be translated.
+   */
+  function delete($source) {
+    return FALSE;
+  }
+
+  /**
+   * Collect strings to be exported to code.
+   *
+   * String identifiers are not supported so strings are anonymously in an array.
+   *
+   * @param $source
+   *   Full data for the string to be translated.
+   */
+  function export($source) {
+    if (!empty($source['value'])) {
+      $this->export_strings[] = $source['value'];
+    }
+  }
+
+  /**
+   * Render any collected exported strings to code.
+   *
+   * @param $indent
+   *   An optional indentation for prettifying nested code.
+   */
+  function export_render($indent = '  ') {
+    $output = '';
+    if (!empty($this->export_strings)) {
+      $this->export_strings = array_unique($this->export_strings);
+      $output = $indent . '$translatables[\'' . $this->view->name . '\'] = array(' . "\n";
+      foreach ($this->export_strings as $string) {
+        $output .= $indent . "  t('" . str_replace("'", "\'", $string) . "'),\n";
+      }
+      $output .= $indent . ");\n";
+    }
+    return $output;
+  }
+}
diff --git plugins/views_plugin_localization_none.inc plugins/views_plugin_localization_none.inc
new file mode 100644
index 0000000..42969bb
--- /dev/null
+++ plugins/views_plugin_localization_none.inc
@@ -0,0 +1,36 @@
+<?php
+// $Id: $
+
+/**
+ * @file
+ * Contains the 'none' localization plugin.
+ */
+
+/**
+ * Localization plugin for no localization.
+ *
+ * @ingroup views_localization_plugins
+ */
+class views_plugin_localization_none extends views_plugin_localization {
+
+  /**
+   * Translate a string; simply return the string.
+   */
+  function translate($source) {
+    return $source['value'];
+  }
+
+  /**
+   * Save a string for translation; not supported.
+   */
+  function save($source) {
+    return FALSE;
+  }
+
+  /**
+   * Delete a string; not supported.
+   */
+  function delete($source) {
+    return FALSE;
+  }
+}
diff --git tests/views_translatable.test tests/views_translatable.test
new file mode 100644
index 0000000..7d7d5c0
--- /dev/null
+++ tests/views_translatable.test
@@ -0,0 +1,83 @@
+<?php
+// $Id$
+
+class viewsTranslatableTest extends ViewsSqlTest {
+  function getInfo() {
+    return array(
+      'name' => 'Views Translatable Test',
+      'description' => 'Tests the pluggable transltations',
+      'group' => 'views',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    variable_set('views_localization_plugin', 'none');
+  }
+
+  function testUnpackTranslatable() {
+    $view = $this->view_unpack_translatable();
+    $view->init_localization();
+    $this->export_locale_strings('export');
+    $expected_strings = array('Defaults', 'Apply1', 'Sort By1', 'Asc1', 'Desc1', 'simple1');
+    $result_strings = $view->localization_plugin->get_export_strings();
+    $this->assertEqual($expected_strings, $result_strings, 'Localisation plugin got every translatable string.');
+  }
+
+  function view_unpack_translatable() {
+    $view = new view;
+    $view->name = 'view_unpack_translatable';
+    $view->description = '';
+    $view->tag = '';
+    $view->view_php = '';
+    $view->base_table = 'node';
+    $view->is_cacheable = FALSE;
+    $view->api_version = 2;
+    $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+    /* Display: Defaults */
+    $handler = $view->new_display('default', 'Defaults', 'default');
+    $handler->display->display_options['access']['type'] = 'none';
+    $handler->display->display_options['cache']['type'] = 'none';
+    $handler->display->display_options['exposed_form']['type'] = 'basic';
+    $handler->display->display_options['exposed_form']['options']['submit_button'] = 'Apply1';
+    $handler->display->display_options['exposed_form']['options']['exposed_sorts_label'] = 'Sort By1';
+    $handler->display->display_options['exposed_form']['options']['sort_asc_label'] = 'Asc1';
+    $handler->display->display_options['exposed_form']['options']['sort_desc_label'] = 'Desc1';
+    $handler->display->display_options['pager']['type'] = 'full';
+    $handler->display->display_options['style_plugin'] = 'default';
+    $handler->display->display_options['row_plugin'] = 'fields';
+    /* Field: Node: Nid */
+    $handler->display->display_options['fields']['nid']['id'] = 'nid';
+    $handler->display->display_options['fields']['nid']['table'] = 'node';
+    $handler->display->display_options['fields']['nid']['field'] = 'nid';
+    $handler->display->display_options['fields']['nid']['label'] = 'simple';
+    $handler->display->display_options['fields']['nid']['alter']['alter_text'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['make_link'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['trim'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['word_boundary'] = 1;
+    $handler->display->display_options['fields']['nid']['alter']['ellipsis'] = 1;
+    $handler->display->display_options['fields']['nid']['alter']['strip_tags'] = 0;
+    $handler->display->display_options['fields']['nid']['alter']['html'] = 0;
+    $handler->display->display_options['fields']['nid']['hide_empty'] = 0;
+    $handler->display->display_options['fields']['nid']['empty_zero'] = 0;
+    $handler->display->display_options['fields']['nid']['link_to_node'] = 0;
+
+    return $view;
+  }
+}
+
+/**
+ * A stump localisation plugin which has static variables to cache the input.
+ */
+class views_plugin_localization_test extends views_plugin_localization {
+  function export($source) {
+    if (!empty($source['value'])) {
+      $this->export_strings[] = $source['value'];
+    }
+  }
+  
+  function get_export_strings() {
+    return $this->export_strings;
+  }
+}
