diff --git a/core/includes/gettext.inc b/core/includes/gettext.inc
index 18c27ea..7b5f22f 100644
--- a/core/includes/gettext.inc
+++ b/core/includes/gettext.inc
@@ -23,11 +23,15 @@
  *   Drupal file object corresponding to the PO file to import.
  * @param $langcode
  *   Language code.
- * @param $mode
- *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
- *   LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ *   An associative array indicating what data should be overwritten, if any.
+ *   - not_customized: not customized strings should be overwritten.
+ *   - customized: customized strings should be overwritten.
+ * @param $customized
+ *   Whether the strings being imported should be saved as customized.
+ *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
  */
-function _locale_import_po($file, $langcode, $mode) {
+function _locale_import_po($file, $langcode, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
   // Try to allocate enough time to parse and import the data.
   drupal_set_time_limit(240);
 
@@ -38,7 +42,7 @@ function _locale_import_po($file, $langcode, $mode) {
   }
 
   // Get strings from file (returns on failure after a partial import, or on success)
-  $status = _locale_import_read_po('db-store', $file, $mode, $langcode);
+  $status = _locale_import_read_po('db-store', $file, $overwrite_options, $langcode, $customized);
   if ($status === FALSE) {
     // Error messages are set in _locale_import_read_po().
     return FALSE;
@@ -80,13 +84,17 @@ function _locale_import_po($file, $langcode, $mode) {
  *   Storage operation type: db-store or mem-store.
  * @param $file
  *   Drupal file object corresponding to the PO file to import.
- * @param $mode
- *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
- *   LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ *   An associative array indicating what data should be overwritten, if any.
+ *   - not_customized: not customized strings should be overwritten.
+ *   - customized: customized strings should be overwritten.
  * @param $lang
  *   Language code.
+ * @param $customized
+ *   Whether the strings being imported should be saved as customized.
+ *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
  */
-function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
+function _locale_import_read_po($op, $file, $overwrite_options = NULL, $lang = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
 
   // The file will get closed by PHP on returning from this function.
   $fd = fopen($file->uri, 'rb');
@@ -138,7 +146,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
       }
       elseif (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
         // We are currently in string token, close it out.
-        _locale_import_one_string($op, $current, $mode, $lang, $file);
+        _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
 
         // Start a new entry for the comment.
         $current         = array();
@@ -182,7 +190,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
 
       if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
         // We are currently in a message string, close it out.
-        _locale_import_one_string($op, $current, $mode, $lang, $file);
+        _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
 
         // Start a new context for the id.
         $current = array();
@@ -212,7 +220,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
 
       if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
         // We are currently in a message, start a new one.
-        _locale_import_one_string($op, $current, $mode, $lang, $file);
+        _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
         $current = array();
       }
       elseif (!empty($current['msgctxt'])) {
@@ -326,7 +334,7 @@ function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) {
 
   // End of PO file, closed out the last entry.
   if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
-    _locale_import_one_string($op, $current, $mode, $lang, $file);
+    _locale_import_one_string($op, $current, $overwrite_options, $lang, $file, $customized);
   }
   elseif ($context != 'COMMENT') {
     _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
@@ -360,16 +368,20 @@ function _locale_import_message($message, $file, $lineno = NULL) {
  *   Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'.
  * @param $value
  *   Details of the string stored.
- * @param $mode
- *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
- *   LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ *   An associative array indicating what data should be overwritten, if any.
+ *   - not_customized: not customized strings should be overwritten.
+ *   - customized: customized strings should be overwritten.
  * @param $lang
  *   Language to store the string in.
  * @param $file
  *   Object representation of file being imported, only required when op is
  *   'db-store'.
+ * @param $customized
+ *   Whether the strings being imported should be saved as customized.
+ *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
  */
-function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL) {
+function _locale_import_one_string($op, $value = NULL, $overwrite_options = NULL, $lang = NULL, $file = NULL, $customized = LOCALE_NOT_CUSTOMIZED) {
   $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0));
   $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE);
   $strings = &drupal_static(__FUNCTION__ . ':strings', array());
@@ -395,7 +407,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
         // If 'msgid' is empty, it means we got values for the header of the
         // file as per the structure of the Gettext format.
         $locale_plurals = variable_get('locale_translation_plurals', array());
-        if (($mode != LOCALE_IMPORT_KEEP) || empty($locale_plurals[$lang]['plurals'])) {
+        if ((array_sum($overwrite_options) > 0) || empty($locale_plurals[$lang]['plurals'])) {
           // Since we only need to parse the header if we ought to update the
           // plural formula, only run this if we don't need to keep existing
           // data untouched or if we don't have an existing plural formula.
@@ -432,7 +444,8 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
           $value['msgid'],
           $value['msgstr'],
           $comments,
-          $mode
+          $overwrite_options,
+          $customized
         );
       }
   } // end of db-store operation
@@ -454,15 +467,34 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
  *   Translation to language specified in $langcode.
  * @param $location
  *   Location value to save with source string.
- * @param $mode
- *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
+ * @param $overwrite_options
+ *   An associative array indicating what data should be overwritten, if any.
+ *   - not_customized: not customized strings should be overwritten.
+ *   - customized: customized strings should be overwritten.
+ * @param $customized
+ *   (optional) Whether the strings being imported should be saved as customized.
+ *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
  *
  * @return
  *   The string ID of the existing string modified or the new string added.
  */
-function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $mode) {
+function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $overwrite_options, $customized = LOCALE_NOT_CUSTOMIZED) {
+
+  // Initialize overwrite options if not set.
+  $overwrite_options += array(
+    'not_customized' => FALSE,
+    'customized' => FALSE,
+  );
+
   $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $source, ':context' => $context))->fetchField();
 
+  // See whether a translation exists already and is customized;
+  // we'll consider it customized if at least one of the plural forms is.
+  $existing_customized = NULL;
+  if ($lid) {
+    $existing_customized = db_query("SELECT MAX(customized) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+  }
+
   if (!empty($translation)) {
     // Skip this string unless it passes a check for dangerous code.
     if (!locale_string_is_safe($translation)) {
@@ -479,25 +511,25 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
         ->condition('lid', $lid)
         ->execute();
 
-      $exists = db_query("SELECT COUNT(lid) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
-
-      if (!$exists) {
+      if (!isset($existing_customized)) {
         // No translation in this language.
         db_insert('locales_target')
           ->fields(array(
             'lid' => $lid,
             'language' => $langcode,
             'translation' => $translation,
+            'customized' => $customized,
           ))
           ->execute();
 
         $report['additions']++;
       }
-      elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+      elseif ($overwrite_options[$existing_customized ? 'customized' : 'not_customized']) {
         // Translation exists, only overwrite if instructed.
         db_update('locales_target')
           ->fields(array(
             'translation' => $translation,
+            'customized' => $customized,
           ))
           ->condition('language', $langcode)
           ->condition('lid', $lid)
@@ -521,13 +553,14 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
            'lid' => $lid,
            'language' => $langcode,
            'translation' => $translation,
+           'customized' => $customized,
         ))
         ->execute();
 
       $report['additions']++;
     }
   }
-  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+  elseif (isset($existing_customized) && $overwrite_options[$existing_customized ? 'customized' : 'not_customized']) {
     // Empty translation, remove existing if instructed.
     db_delete('locales_target')
       ->condition('language', $langcode)
@@ -828,17 +861,67 @@ function _locale_import_parse_quoted($string) {
  * @param $language
  *   Language object to generate the output for, or NULL if generating
  *   translation template.
+ * @param $options
+ *   (optional) An associative array specifying what to include in the output:
+ *   - customized: include customized strings (if TRUE)
+ *   - uncustomized: include non-customized string (if TRUE)
+ *   - untranslated: include untranslated source strings (if TRUE)
+ *   Ignored if $language is NULL.
  *
  * @return
  *   An array of translated strings that can be used to generate an export.
  */
-function _locale_export_get_strings($language = NULL) {
-  if (isset($language)) {
-    $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language", array(':language' => $language->langcode));
+function _locale_export_get_strings($language = NULL, $options = array()) {
+
+  // Assume FALSE for all options if not provided by the API.
+  $options += array(
+    'customized' => FALSE,
+    'not_customized' => FALSE,
+    'not_translated' => FALSE,
+  );
+  if (array_sum($options) == 0) {
+    // If user asked to not include anything in the translation files,
+    // that would not make sense, so just fall back on providing a template.
+    $language = NULL;
+  }
+
+  // Build and execute query to collect source strings and translations.
+  $query = db_select('locales_source', 's');
+  if (!empty($language)) {
+    if ($options['not_translated']) {
+      // Left join to keep untranslated strings in.
+      $query->leftJoin('locales_target', 't', 's.lid = t.lid AND t.language = :language', array(':language' => $language->langcode));
+    }
+    else {
+      // Inner join to filter for only translations.
+      $query->innerJoin('locales_target', 't', 's.lid = t.lid AND t.language = :language', array(':language' => $language->langcode));
+    }
+    if ($options['customized']) {
+      if (!$options['not_customized']) {
+        // Filter for customized strings only.
+        $query->condition('t.customized', LOCALE_CUSTOMIZED);
+      }
+      // Else no filtering needed in this case.
+    }
+    else {
+      if ($options['not_customized']) {
+        // Filter for non-customized strings only.
+        $query->condition('t.customized', LOCALE_NOT_CUSTOMIZED);
+      }
+      else {
+        // Filter for strings without translation.
+        $query->isNull('t.translation');
+      }
+    }
+    $query->fields('t', array('translation'));
   }
   else {
-    $result = db_query("SELECT s.lid, s.source, s.context, s.location FROM {locales_source} s");
+    $query->leftJoin('locales_target', 't', 's.lid = t.lid');
   }
+  $query->fields('s', array('lid', 'source', 'context', 'location'));
+  $result = $query->execute();
+
+  // Structure results in an array with metainformation on the strings.
   $strings = array();
   foreach ($result as $child) {
     $strings[$child->lid] = array(
diff --git a/core/includes/locale.inc b/core/includes/locale.inc
index 1f9567b..45e4a95 100644
--- a/core/includes/locale.inc
+++ b/core/includes/locale.inc
@@ -73,26 +73,28 @@ define('LOCALE_JS_OBJECT_CONTEXT', '
 ');
 
 /**
- * Translation import mode overwriting all existing translations
- * if new translated version available.
+ * Flag for locally not customized interface translation.
+ *
+ * Such translations are imported from .po files downloaded from
+ * localize.drupal.org for example.
  */
-const LOCALE_IMPORT_OVERWRITE = 0;
+const LOCALE_NOT_CUSTOMIZED = 0;
 
 /**
- * Translation import mode keeping existing translations and only
- * inserting new strings.
+ * Flag for locally customized interface translation.
+ *
+ * Such translations are edited from their imported originals on the user
+ * interface or are imported as customized.
  */
-const LOCALE_IMPORT_KEEP = 1;
+const LOCALE_CUSTOMIZED = 1;
 
 /**
- * URL language negotiation: use the path prefix as URL language
- * indicator.
+ * URL language negotiation: use the path prefix as URL language indicator.
  */
 const LANGUAGE_NEGOTIATION_URL_PREFIX = 0;
 
 /**
- * URL language negotiation: use the domain as URL language
- * indicator.
+ * URL language negotiation: use the domain as URL language indicator.
  */
 const LANGUAGE_NEGOTIATION_URL_DOMAIN = 1;
 
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index a4982e1..aa757a3 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -34,62 +34,80 @@ function locale_translate_import_form($form, &$form_state) {
   else {
     $default = key($existing_languages);
     $language_options = array(
-      t('Already added languages') => $existing_languages,
+      t('Existing languages') => $existing_languages,
       t('Languages not yet added') => language_admin_predefined_list()
     );
   }
 
-  $form['import'] = array('#type' => 'fieldset',
-    '#title' => t('Import translation'),
-  );
-  $form['import']['file'] = array('#type' => 'file',
-    '#title' => t('Language file'),
+  $form['file'] = array(
+    '#type' => 'file',
+    '#title' => t('Translation file'),
     '#size' => 50,
     '#description' => t('A Gettext Portable Object (<em>.po</em>) file.'),
   );
-  $form['import']['langcode'] = array('#type' => 'select',
-    '#title' => t('Import into'),
+  $form['langcode'] = array(
+    '#type' => 'select',
+    '#title' => t('Language'),
     '#options' => $language_options,
     '#default_value' => $default,
-    '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, it will be added.'),
   );
-  $form['import']['mode'] = array('#type' => 'radios',
-    '#title' => t('Mode'),
-    '#default_value' => LOCALE_IMPORT_KEEP,
-    '#options' => array(
-      LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace existing ones, new ones are added. The plural format is updated.'),
-      LOCALE_IMPORT_KEEP => t('Existing strings and the plural format are kept, only new strings are added.')
+  $form['customized'] = array(
+    '#title' => t('Import contains custom translations'),
+    '#type' => 'checkbox',
+  );
+  $form['overwrite_options'] = array(
+    '#type' => 'container',
+    '#tree' => TRUE,
+  );
+  $form['overwrite_options']['not_customized'] = array(
+    '#title' => t('Overwrite existing non-customized translations'),
+    '#type' => 'checkbox',
+    '#default_value' => TRUE,
+    '#states' => array(
+      'checked' => array(
+         ':input[name=customized]' => array('checked' => TRUE),
+      ),
     ),
   );
-  $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
+  $form['overwrite_options']['customized'] = array(
+    '#title' => t('Overwrite existing customized translations'),
+    '#type' => 'checkbox',
+  );
 
+  $form['actions'] = array(
+    '#type' => 'actions'
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Import')
+  );
   return $form;
 }
 
 /**
- * Process the locale import form submission.
+ * Processes the locale import form submission.
  */
 function locale_translate_import_form_submit($form, &$form_state) {
   $validators = array('file_validate_extensions' => array('po'));
-  // Ensure we have the file uploaded
+
+  // Ensure we have the file uploaded.
   if ($file = file_save_upload('file', $validators)) {
 
-    // Add language, if not yet supported
-    drupal_static_reset('language_list');
-    $languages = language_list();
-    $langcode = $form_state['values']['langcode'];
-    if (!isset($languages[$langcode])) {
+    // Add language, if not yet supported.
+    $language = language_load($form_state['values']['langcode']);
+    if (empty($language)) {
       include_once DRUPAL_ROOT . '/core/includes/standard.inc';
       $predefined = standard_language_list();
       $language = (object) array(
-        'langcode' => $langcode,
+        'langcode' => $form_state['values']['langcode'],
       );
-      language_save($language);
-      drupal_set_message(t('The language %language has been created.', array('%language' => t($predefined[$langcode][0]))));
+      $language = language_save($language);
+      drupal_set_message(t('The language %language has been created.', array('%language' => t($language->name))));
     }
+    $customized = $form_state['values']['customized'] ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED;
 
     // Now import strings into the language
-    if ($return = _locale_import_po($file, $langcode, $form_state['values']['mode']) == FALSE) {
+    if ($return = _locale_import_po($file, $language->langcode, $form_state['values']['overwrite_options'], $customized) == FALSE) {
       $variables = array('%filename' => $file->filename);
       drupal_set_message(t('The translation import of %filename failed.', $variables), 'error');
       watchdog('locale', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR);
@@ -106,11 +124,9 @@ function locale_translate_import_form_submit($form, &$form_state) {
 }
 
 /**
- * User interface for the translation export screen.
+ * Builds form to export Gettext translation files.
  */
-function locale_translate_export_screen() {
-  // Get all enabled languages, except English, if we should not translate that.
-  drupal_static_reset('language_list');
+function locale_translate_export_form($form, &$form_state) {
   $languages = language_list(TRUE);
   $language_options = array();
   foreach ($languages as $langcode => $language) {
@@ -119,64 +135,55 @@ function locale_translate_export_screen() {
     }
   }
 
-  $output = '';
-  // Offer translation export if any language is set up.
-  if (!empty($language_options)) {
-    $elements = drupal_get_form('locale_translate_export_po_form', $language_options);
-    $output = drupal_render($elements);
-  }
-  $elements = drupal_get_form('locale_translate_export_pot_form');
-  $output .= drupal_render($elements);
-  return $output;
-}
-
-/**
- * Form to export PO files for the languages provided.
- *
- * @param $names
- *   An associate array with localized language names
- */
-function locale_translate_export_po_form($form, &$form_state, $names) {
-  $form['export_title'] = array('#type' => 'item',
-    '#title' => t('Export translation'),
+  $form['langcode'] = array(
+    '#type' => 'select',
+    '#title' => t('Language'),
+    '#options' => $language_options,
+    '#empty_option' => t('Source text only'),
+    '#empty_value' => LANGUAGE_SYSTEM,
+  );
+  $form['content_options'] = array(
+    '#type' => 'container',
+    '#states' => array(
+      'invisible' => array(
+        ':input[name=langcode]' => array('value' => LANGUAGE_SYSTEM),
+      )
+    ),
+    '#tree' => TRUE,
   );
-  $form['langcode'] = array('#type' => 'select',
-    '#title' => t('Language name'),
-    '#options' => $names,
-    '#description' => t('Select the language to export in Gettext Portable Object (<em>.po</em>) format.'),
+  $form['content_options']['customized'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Include customized translations'),
+    '#default_value' => TRUE,
+  );
+  $form['content_options']['not_customized'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Include non-customized translations'),
+    '#default_value' => TRUE,
+  );
+  $form['content_options']['not_translated'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Include untranslated text'),
+    '#default_value' => TRUE,
   );
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
-  return $form;
-}
 
-/**
- * Translation template export form.
- */
-function locale_translate_export_pot_form() {
-  // Complete template export of the strings
-  $form['export_title'] = array('#type' => 'item',
-    '#title' => t('Export template'),
-    '#description' => t('Generate a Gettext Portable Object Template (<em>.pot</em>) file with all strings from the Drupal locale database.'),
+  $form['actions'] = array(
+    '#type' => 'actions'
+  );
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Export')
   );
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
-  // Reuse PO export submission callback.
-  $form['#submit'][] = 'locale_translate_export_po_form_submit';
   return $form;
 }
 
 /**
- * Process a translation (or template) export form submission.
+ * Processes a translation (or template) export form submission.
  */
-function locale_translate_export_po_form_submit($form, &$form_state) {
+function locale_translate_export_form_submit($form, &$form_state) {
   // If template is required, language code is not given.
-  $language = NULL;
-  if (isset($form_state['values']['langcode'])) {
-    $languages = language_list();
-    $language = $languages[$form_state['values']['langcode']];
-  }
-  _locale_export_po($language, _locale_export_po_generate($language, _locale_export_get_strings($language)));
+  $language = ($form_state['values']['langcode'] != LANGUAGE_SYSTEM ? language_load($form_state['values']['langcode']) : NULL);
+  _locale_export_po($language, _locale_export_po_generate($language, _locale_export_get_strings($language, $form_state['values']['content_options'])));
 }
 
 /**
@@ -267,7 +274,7 @@ function locale_translate_batch_import($filepath, &$context) {
   // we can extract the language code to use for the import from the end.
   if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
     $file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath);
-    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
+    _locale_import_read_po('db-store', $file, array(), $langcode[2]);
     $context['results'][] = $filepath;
   }
 }
diff --git a/core/modules/locale/locale.css b/core/modules/locale/locale.css
index 66de82b..96f08b9 100644
--- a/core/modules/locale/locale.css
+++ b/core/modules/locale/locale.css
@@ -6,7 +6,7 @@
 
 #locale-translation-filter-form .form-item-language,
 #locale-translation-filter-form .form-item-translation,
-#locale-translation-filter-form .form-item-group {
+#locale-translation-filter-form .form-item-customized {
   float: left; /* LTR */
   padding-right: .8em; /* LTR */
   margin: 0.1em;
diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install
index acf3a80..8ee3332 100644
--- a/core/modules/locale/locale.install
+++ b/core/modules/locale/locale.install
@@ -176,6 +176,12 @@ function locale_schema() {
         'default' => '',
         'description' => 'Language code. References {language}.langcode.',
       ),
+      'customized' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0, // LOCALE_NOT_CUSTOMIZED
+        'description' => 'Boolean indicating whether the translation is modified.',
+      ),
     ),
     'primary key' => array('language', 'lid'),
     'foreign keys' => array(
@@ -485,6 +491,27 @@ function locale_update_8005() {
 }
 
 /**
+ * Add column to track customized string status to locales_target.
+ */
+function locale_update_8006() {
+  $spec = array(
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0, // LOCALE_NOT_CUSTOMIZED
+  );
+  db_add_field('locales_target', 'customized', $spec);
+
+  // If l10n_update module was installed retain its status data, but drop the
+  // old field from the table.
+  if (db_field_exists('locales_target', 'l10n_status')) {
+    db_update('locales_target')
+      ->expression('customized', 'l10n_status')
+      ->execute();
+    db_drop_field('locales_target', 'l10n_status');
+  }
+}
+
+/**
  * @} End of "addtogroup updates-7.x-to-8.x"
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 6d3ded0..4b00e6c 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -124,7 +124,8 @@ function locale_menu() {
   );
   $items['admin/config/regional/translate/export'] = array(
     'title' => 'Export',
-    'page callback' => 'locale_translate_export_screen',  // possibly multiple forms concatenated
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('locale_translate_export_form'),
     'access arguments' => array('translate interface'),
     'weight' => 30,
     'type' => MENU_LOCAL_TASK,
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 0d59f42..595d4e5 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -29,6 +29,7 @@ function _locale_translate_seek() {
     $query = array(
       'translation' => 'all',
       'language' => 'all',
+      'customized' => 'all',
       'string' => '',
     );
   }
@@ -36,13 +37,16 @@ function _locale_translate_seek() {
   $sql_query = db_select('locales_source', 's');
   $sql_query->leftJoin('locales_target', 't', 't.lid = s.lid');
   $sql_query->fields('s', array('source', 'location', 'context', 'lid'));
-  $sql_query->fields('t', array('translation', 'language'));
+  $sql_query->fields('t', array('translation', 'language', 'customized'));
 
   // Compute LIKE section.
   switch ($query['translation']) {
     case 'translated':
       $sql_query->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE');
       $sql_query->orderBy('t.translation', 'DESC');
+      if ($query['customized'] != 'all') {
+        $sql_query->condition('t.customized', $query['customized'], '=');
+      }
       break;
     case 'untranslated':
       $sql_query->condition(db_and()
@@ -134,7 +138,7 @@ function _locale_translate_seek_query() {
   $query = &drupal_static(__FUNCTION__);
   if (!isset($query)) {
     $query = array();
-    $fields = array('string', 'language', 'translation');
+    $fields = array('string', 'language', 'translation', 'customized');
     foreach ($fields as $field) {
       if (isset($_SESSION['locale_translation_filter'][$field])) {
         $query[$field] = $_SESSION['locale_translation_filter'][$field];
@@ -172,7 +176,25 @@ function locale_translation_filters() {
 
   $filters['translation'] = array(
     'title' => t('Search in'),
-    'options' => array('all' => t('Both translated and untranslated strings'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')),
+    'options' => array(
+      'all' => t('Both translated and untranslated strings'),
+      'translated' => t('Only translated strings'),
+      'untranslated' => t('Untranslated strings')
+    ),
+  );
+
+  $filters['customized'] = array(
+    'title' => t('Customized'),
+    'options' => array(
+      'all' => t('All'),
+      LOCALE_NOT_CUSTOMIZED => t('Non-customized only'),
+      LOCALE_CUSTOMIZED => t('Customized only'),
+    ),
+    'states' => array(
+      'visible' => array(
+        ':input[name=translation]' => array('value' => 'translated'),
+      )
+    ),
   );
 
   return $filters;
@@ -210,6 +232,9 @@ function locale_translation_filter_form() {
         '#size' => 0,
         '#options' => $filter['options'],
       );
+      if (isset($filter['states'])) {
+        $form['filters']['status'][$key]['#states'] = $filter['states'];
+      }
     }
     if (!empty($_SESSION['locale_translation_filter'][$key])) {
       $form['filters']['status'][$key]['#default_value'] = $_SESSION['locale_translation_filter'][$key];
@@ -435,21 +460,23 @@ function locale_translate_edit_form_submit($form, &$form_state) {
     }
     if ($has_translation) {
       // Only update or insert if we have a value to use.
-      if (!empty($translation)) {
+      if (!empty($translation) && ($translation != $value)) {
         db_update('locales_target')
           ->fields(array(
             'translation' => $value,
+            'customized' => LOCALE_CUSTOMIZED,
           ))
           ->condition('lid', $lid)
           ->condition('language', $langcode)
           ->execute();
       }
-      else {
+      if (empty($translation)) {
         db_insert('locales_target')
           ->fields(array(
             'lid' => $lid,
             'translation' => $value,
             'language' => $langcode,
+            'customized' => LOCALE_CUSTOMIZED,
           ))
           ->execute();
       }
diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test
index f2395ad..05523da 100644
--- a/core/modules/locale/locale.test
+++ b/core/modules/locale/locale.test
@@ -621,9 +621,11 @@ class LocalePluralFormatTest extends DrupalWebTestCase {
     // will not overwrite the proper plural formula imported above.
     $this->importPoFile($this->getPoFileWithMissingPlural(), array(
       'langcode' => 'fr',
+      'overwrite_options[not_customized]' => 1,
     ));
     $this->importPoFile($this->getPoFileWithBrokenPlural(), array(
       'langcode' => 'hr',
+      'overwrite_options[not_customized]' => 1,
     ));
 
     // Reset static caches from locale_get_plural() to ensure we get fresh data.
@@ -809,7 +811,6 @@ class LocalePluralFormatTest extends DrupalWebTestCase {
     $name = tempnam('temporary://', "po_") . '.po';
     file_put_contents($name, $contents);
     $options['files[file]'] = $name;
-    $options['mode'] = LOCALE_IMPORT_OVERWRITE;
     $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
     drupal_unlink($name);
   }
@@ -972,7 +973,6 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
     // strings are kept.
     $this->importPoFile($this->getOverwritePoFile(), array(
       'langcode' => 'fr',
-      'mode' => 1, // Existing strings are kept, only new strings are added.
     ));
 
     // The import should have created 1 string.
@@ -994,7 +994,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
     // strings are overwritten.
     $this->importPoFile($this->getOverwritePoFile(), array(
       'langcode' => 'fr',
-      'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
+      'overwrite_options[not_customized]' => 1,
     ));
 
     // The import should have updated 2 strings.
@@ -1074,7 +1074,6 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase {
     // Try importing a .po file.
     $this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
       'langcode' => $langcode,
-      'mode' => 0,
     ));
     $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), t('The translation file was successfully imported.'));
     // This is the language indicator on the translation search screen for
@@ -1316,9 +1315,6 @@ class LocaleExportFunctionalTest extends DrupalWebTestCase {
    */
   function testExportTranslationTemplateFile() {
     // Get the translation template file.
-    // There are two 'Export' buttons on this page, but it somehow works.  It'd
-    // be better if we could use the submit button id like documented but that
-    // doesn't work.
     $this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
     // Ensure we have a translation file.
     $this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.'));
