diff --git a/translate_set.export.inc b/translate_set.export.inc
new file mode 100644
index 0000000..a655bb3
--- /dev/null
+++ b/translate_set.export.inc
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * @file
+ * Translate set import/export functionality.
+ *
+ * This file contains a lot of code which resembles code in locale.admin.inc
+ * and includes/locale.inc. The big difference is that we can export all
+ * languages in one download and mix all textgroups within one .po file. We use
+ * the lid to rematch when importing afterwards.
+ */
+
+define('TRANSLATE_SET_EXPORT_DIR', 'public://translate-set/');
+
+/**
+ * Page callback: import & export form.
+ */
+function translate_set_import_export_form($form, $form_state) {
+
+  $form['export_container'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Export'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  // Check user language settings.
+  $restricted_languages = db_select('locales_translate_set_user_languages', 'ltsul')
+    ->fields('ltsul', array('language'))
+    ->condition('uid', $GLOBALS['user']->uid)
+    ->execute()
+    ->fetchCol();
+
+  $languages = language_list();
+  $language_options = array();
+  foreach ($languages as $key => $language) {
+
+    // Ignore built-in English.
+    if ($key == 'en') {
+      continue;
+    }
+
+    // Check restricted languages.
+    if (!empty($restricted_languages)) {
+      if (!in_array($language->language, $restricted_languages)) {
+        continue;
+      }
+    }
+
+    // Add to options.
+    $language_options[$key] = $language->name . ' (' . $key . ')';
+  }
+
+  // If there are no options for the language, stop here.
+  if (empty($language_options)) {
+    $form['info'] = array(
+      '#type' => 'markup',
+      '#markup' => '<p>' . t('No languages found to translate to. Either add new languages or contact an administrator.') . '</p>',
+    );
+    return $form;
+  }
+
+  $form['export_container']['language_export'] = array(
+    '#title' => t('Language'),
+    '#type' => 'select',
+    '#multiple' => TRUE,
+    '#options' => $language_options,
+    '#size' => count($language_options),
+    '#description' => t('Select languages to export'),
+    '#default_value' => array_keys($language_options),
+  );
+
+  $form['export_container']['export'] = array(
+    '#type' => 'submit',
+    '#value' => t('Export'),
+    '#validate' => array('translate_set_export_form_validate'),
+    '#submit' => array('translate_set_export_form_submit'),
+  );
+
+  $form['import_container'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Import'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  $form['import_container']['language_import'] = array(
+    '#title' => t('Language'),
+    '#type' => 'select',
+    '#options' => array_merge(array('' => t('Select')), $language_options),
+    '#description' => t('Choose the language you want to add strings into.'),
+  );
+
+  $form['import_container']['file'] = array(
+    '#type' => 'file',
+    '#description' => t('A Gettext Portable Object (<em>.po</em>) file that was exported with the Translate set module.'),
+  );
+
+  $form['import_container']['import'] = array(
+    '#type' => 'submit',
+    '#value' => t('Import'),
+    '#validate' => array('translate_set_import_form_validate'),
+    '#submit' => array('translate_set_import_form_submit'),
+  );
+
+  return $form;
+}
+
+/**
+ * Validate the export form.
+ */
+function translate_set_export_form_validate($form, $form_state) {
+  // Validate selection of languages.
+  if (empty($form_state['values']['language_export'])) {
+    form_set_error('language_export', t('Language is required'));
+  }
+
+  // Validate export directory.
+  $directory = TRANSLATE_SET_EXPORT_DIR;
+  if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY)) {
+    form_set_error('', t('The export directory (translate-set) could not be created or is not writable, please check your file system settings.'));
+  }
+}
+
+/**
+ * Submit callback: export translate set to .po files.
+ */
+function translate_set_export_form_submit($form, $form_state) {
+  $language_list = language_list();
+
+  // Clean up the directory first.
+  $files = scandir(TRANSLATE_SET_EXPORT_DIR);
+  foreach ($files as $file) {
+    if (is_file(TRANSLATE_SET_EXPORT_DIR . $file)) {
+      file_unmanaged_delete(TRANSLATE_SET_EXPORT_DIR . $file);
+    }
+  }
+
+  // Create new .po files.
+  foreach ($form_state['values']['language_export'] as $language) {
+    // Get strings for this language from the translate set.
+    $output = _locale_export_po_generate($language_list[$language], _translate_set_export_get_strings($language_list[$language]));
+
+    // Write file.
+    $filename = 'ps_' . REQUEST_TIME . '_' .  $language  . '.po';
+    file_unmanaged_save_data($output, TRANSLATE_SET_EXPORT_DIR . $filename);
+  }
+
+  // Zip the directory and stream it to the client.
+  $files = scandir(TRANSLATE_SET_EXPORT_DIR);
+
+  $archive = new ZipArchive();
+  $archive->open(drupal_realpath(TRANSLATE_SET_EXPORT_DIR . 'po-files.zip'), ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
+  foreach ($files as $file) {
+    if (strpos($file, '.po') === FALSE) {
+      continue;
+    }
+    $archive->addFile(drupal_realpath(TRANSLATE_SET_EXPORT_DIR . $file), 'po-files/' . $file);
+  }
+  $archive->close();
+
+  // Stream zip file.
+  if (file_exists(TRANSLATE_SET_EXPORT_DIR . 'po-files.zip')) {
+    header( "Content-Type: application/x-zip" );
+    header( "Content-Disposition: attachment; filename=\"po-files.zip\"" );
+    readfile(TRANSLATE_SET_EXPORT_DIR . 'po-files.zip');
+    drupal_exit();
+  }
+  else {
+    drupal_set_message(t('Something went wrong exporting the files.'), 'error');
+  }
+}
+
+/**
+ * Get strings in a language set for a language.
+ *
+ * @param $language
+ *   A language key.
+ *
+ * @return array $strings
+ *   An array of strings.
+ */
+function _translate_set_export_get_strings($language = NULL) {
+  $strings = array();
+
+  $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_translate_set} lts INNER JOIN {locales_source} s on lts.lid = s.lid LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language ORDER BY t.plid, t.plural", array(':language' => $language->language));
+  foreach ($result as $child) {
+    $string = array(
+      'comment'     => $child->location,
+      'source'      => $child->source,
+      // Append our own string to the context which we will use to detect the
+      // lid during the import.
+      'context'     => $child->context .' - translate-set---' . $child->lid,
+      'translation' => isset($child->translation) ? $child->translation : '',
+    );
+    if ($child->plid) {
+      // Has a parent lid. Since we process in the order of plids,
+      // we already have the parent in the array, so we can add the
+      // lid to the next plural version to it. This builds a linked
+      // list of plurals.
+      $string['child'] = TRUE;
+      $strings[$child->plid]['plural'] = $child->lid;
+    }
+    $strings[$child->lid] = $string;
+  }
+
+  return $strings;
+}
+
+/**
+ * Validate the import form.
+ */
+function translate_set_import_form_validate($form, $form_state) {
+  // Validate selection of languages.
+  if (empty($form_state['values']['language_import'])) {
+    form_set_error('language_import', t('Language is required'));
+  }
+
+  // Validate file upload.
+  if (empty($form_state['values']['file'])) {
+    form_set_error('file', t('File is required'));
+  }
+}
+
+/**
+ * Submit callback: import .po file.
+ */
+function translate_set_import_form_submit($form, &$form_state) {
+  $validators = array('file_validate_extensions' => array('po'));
+
+  // Ensure we have the file uploaded.
+  if ($file = file_save_upload('file', $validators)) {
+    $langcode = $form_state['values']['language_import'];
+    if ($return = _translate_set_locale_import_po($file, $langcode, $form_state['values']['mode'], $form_state['values']['group']) == FALSE) {
+      $variables = array('%filename' => $file->filename);
+      drupal_set_message(t('The translation import of %filename failed.', $variables), 'error');
+      watchdog('translate_set', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR);
+    }
+  }
+  else {
+    drupal_set_message(t('File to import not found.'), 'error');
+    $form_state['redirect'] = 'admin/config/regional/translate/translate-set/import-export';
+    return;
+  }
+
+  $form_state['redirect'] = 'admin/config/regional/translate/translate-set/import-export';
+}
+
+/**
+ * Parses Gettext Portable Object file information and inserts into database
+ *
+ * @param $file
+ *   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.
+ *
+ * @return bool
+ *   Whether the import succeeded or not.
+ */
+function _translate_set_locale_import_po($file, $langcode, $mode) {
+  // Try to allocate enough time to parse and import the data.
+  drupal_set_time_limit(240);
+
+  // Get strings from file (returns on failure after a partial import, or on success)
+  $status = _locale_import_read_po('db-store', $file, $mode, $langcode, $group);
+  if ($status === FALSE) {
+    // Error messages are set in _locale_import_read_po().
+    return FALSE;
+  }
+
+  // Get status information on import process.
+  list($header_done, $additions, $updates, $deletes, $skips) = _locale_import_one_string('db-report');
+
+  if (!$header_done) {
+    drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error');
+  }
+
+  // Clear cache and force refresh of JavaScript translations.
+  _locale_invalidate_js($langcode);
+  cache_clear_all('locale:', 'cache', TRUE);
+
+  // Rebuild the menu, strings may have changed.
+  menu_rebuild();
+
+  drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
+  watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+  if ($skips) {
+    $skip_message = format_plural($skips, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.');
+    drupal_set_message($skip_message);
+    watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
+  }
+  return TRUE;
+}
diff --git a/translate_set.module b/translate_set.module
index a451e8d..6ed0fc0 100644
--- a/translate_set.module
+++ b/translate_set.module
@@ -25,7 +25,11 @@ function translate_set_permission() {
     'administer languages for translate set per user' => array(
       'title' => t('Administer languages for translate set per user'),
       'description' => t('Restrict the languages this user can translate strings to. You can configure this on user/x/translate-set'),
-    )
+    ),
+    'translate set import and export' => array(
+      'title' => t('Import and export'),
+      'description' => t('Import and export .po files containing the strings in the translate set.'),
+    ),
   );
 }
 
@@ -74,7 +78,18 @@ function translate_set_menu() {
     'access arguments' => array('manage translate set'),
     'file' => 'translate_set.admin.inc',
     'type' => MENU_LOCAL_TASK,
-    'weight' => 1
+    'weight' => 5
+  );
+
+  // Translate set import.
+  $items['admin/config/regional/translate/translate-set/import-export'] = array(
+    'title' => 'Import & export',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('translate_set_import_export_form'),
+    'access arguments' => array('translate set import and export'),
+    'file' => 'translate_set.export.inc',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10
   );
 
   // Translate set configuration.
@@ -85,7 +100,7 @@ function translate_set_menu() {
     'access arguments' => array('administer translate set settings'),
     'file' => 'translate_set.admin.inc',
     'type' => MENU_LOCAL_TASK,
-    'weight' => 1
+    'weight' => 15
   );
 
   // Administer languages for Translate set per user.
@@ -161,6 +176,16 @@ function translate_set_locale_delete_submit($form, $form_state) {
 }
 
 /**
+ * Implements hook_form_{FORM_ID}_alter().
+ */
+function translate_set_form_locale_translate_import_form_alter(&$form, $form_state) {
+  $form['import']['file']['#description'] .= '<br />' . t('<strong>Warning:</strong> do not import a file that has been exported with the Translate set module.');
+  if (user_access('translate set import and export')) {
+    $form['import']['file']['#description'] .= '<br />' . t('<a href="!url">Go to Translate set import & export screen.</a>', array('!url' => url('admin/config/regional/translate/translate-set/import-export')));
+  }
+}
+
+/**
  * Tracks new strings and adds it to the Translate set.
  */
 function translate_set_tracking_find_strings() {
diff --git a/translate_set.test b/translate_set.test
index 35728d0..ef92201 100644
--- a/translate_set.test
+++ b/translate_set.test
@@ -35,7 +35,7 @@ class TranslateSetTestCase extends DrupalWebTestCase {
 
   function setUp() {
     parent::setUp(array('locale', 'translate_set', 'translate_set_test'));
-    $this->admin = $this->drupalCreateUser(array('translate interface', 'translate set', 'administer languages for translate set per user', 'manage translate set', 'administer languages', 'administer users', 'administer translate set settings'));
+    $this->admin = $this->drupalCreateUser(array('translate interface', 'translate set', 'administer languages for translate set per user', 'manage translate set', 'administer languages', 'administer users', 'administer translate set settings', 'translate set import and export'));
     $this->translator = $this->drupalCreateUser(array('translate set'));
     $this->normal = $this->drupalCreateUser();
   }
@@ -47,14 +47,14 @@ class TranslateSetTestCase extends DrupalWebTestCase {
 
     // Assert 403 for all our page callbacks for a normal user.
     $this->drupalLogin($this->normal);
-    foreach (array('user/' . $this->normal->uid . '/translate-set', 'admin/config/regional/translate/translate-set', 'admin/config/regional/translate/translate-set-action', 'admin/config/regional/translate/translate-set/manage-set', 'admin/config/regional/translate/translate-set/settings') as $url) {
+    foreach (array('user/' . $this->normal->uid . '/translate-set', 'admin/config/regional/translate/translate-set', 'admin/config/regional/translate/translate-set-action', 'admin/config/regional/translate/translate-set/manage-set', 'admin/config/regional/translate/translate-set/settings', 'admin/config/regional/translate/translate-set/import-export') as $url) {
       $this->drupalGet($url);
       $this->assertResponse(403);
     }
 
     // Assert 403 for per user url and group action for the translator.
     $this->drupalLogin($this->translator);
-    foreach (array('user/' . $this->normal->uid . '/translate-set', 'admin/config/regional/translate/translate-set-action', 'admin/config/regional/translate/translate-set/manage-set', 'admin/config/regional/translate/translate-set/settings') as $url) {
+    foreach (array('user/' . $this->normal->uid . '/translate-set', 'admin/config/regional/translate/translate-set-action', 'admin/config/regional/translate/translate-set/manage-set', 'admin/config/regional/translate/translate-set/settings', 'admin/config/regional/translate/translate-set/import-export') as $url) {
       $this->drupalGet($url);
       $this->assertResponse(403);
     }
