diff --git a/core/includes/gettext.sketch.inc b/core/includes/gettext.sketch.inc
new file mode 100644
index 0000000..07ca70f
--- /dev/null
+++ b/core/includes/gettext.sketch.inc
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * @file
+ * Experimental "sketchy" code for gettext parsing and generating API.
+ * For a world without failures, exceptions and certainly without users ;)
+ */
+use Drupal\Core\Gettext\GettextFileInterface;
+use Drupal\Core\Gettext\PoWriter;
+use Drupal\Core\Gettext\PoReader;
+
+/**
+ * Example code to import a po file into the database.
+ *
+ * Import a po file into the Database.
+ * The file may be local, remote (via stream wrapper) or even XML.
+ * The import has a small memory footprint.
+ * The import may be split up to enable batch handling.
+ */
+$file = new GettextFileInterface('public://source.po.txt');
+gettext_import($source, $langcode);
+
+/**
+ * Imports translations from an external source into the database.
+ */
+function gettext_import($file, $langcode) {
+  // Setup a reader to fetch po formatted data from the file.
+  $source = new PoReader($file);
+
+  // Set up a (read/)write interface to write data into the database.
+  // To make the architecture more consistent this would be:
+  //   $database = new GettextDatabaseInterface();
+  //   $destination = new DatabaseWriter($database);
+  $destination = new GettextDatabase($langcode);
+
+  // Transfer meta data and translations from the file to the database.
+  gettext_transfer($source, $destination);
+}
+
+/**
+ * Example code to export translations to a po file.
+ *
+ * Export gettext data to a PO file.
+ * The file may be local, remote (via stream wrapper) or even XML.
+ * The import has a small memory footprint.
+ * The import may be split up to enable batch handling.
+ */
+$file = new GettextFileInterface('public://destination.po.txt');
+gettext_export($file, $langcode);
+
+/**
+ * Exports translations from the database to a po file.
+ */
+function gettext_export($file, $langcode) {
+  $destination = new PoWriter($file, $langcode);
+
+  // Set up a read(/write) interface to read data from the database.
+  // To make the architecture more consistent this would be:
+  //   $database = new GettextDatabaseInterface();
+  //   $destination = new DatabaseReader($database);
+  $source = new GettextDatabase($langcode);
+
+  // Transfer meta data and translations from the database to the file.
+  gettext_transfer($source, $destination);
+}
+
+/**
+ * Transfers gettext data from source to destination.
+ */
+function gettext_transfer($source, $destination, $langcode) {
+  // Transfer translations.
+  // Use batch processing if both source and destination support it and the source
+  // is large enough. If not, process in once. The threshold for batch processing
+  // could dynamically and determined by source and target. But for simplicity it is fixed.
+  if ($source->supportBatch() && $destination->supportBatch() && $source->size() > $threshold) {
+    // Transfer data in batches.
+    // Built and execute batch.
+    $batch = gettext_transfer_batch_setup($source, $destination);
+    batch_set($batch);
+  }
+  else {
+    // Transfer all translations without interruption.
+    // Process the translations one by one to keep the memory footprint low.
+    while ($translation = $source->read()) {
+      $destination->write($translation);
+    }
+  }
+}
+
+/**
+ * Set up a batch process to transfer Gettext data.
+ */
+function gettext_transfer_batch_setup($source, $destination) {
+  $batch = array(
+    'operations' => array(
+      array('gettext_transfer_batch_op', array($source, $destination)),
+    ),
+    'finished' => 'gettext_transfer_batch_finished',
+  );
+  return $batch;
+}
+
+/**
+ * Batch operation transferring gettext data.
+ *
+ * @todo Add source filter conditions (context, language(?)) as parameter.
+ */
+function gettext_transfer_batch_op($source, $destination) {
+  // Initialize sandbox for batch reading.
+  if (empty($context['sandbox'])) {
+    $context['sandbox']['transfer'] = array(
+      'chunk' => min($source->chunkSize(), $destination->chunkSize()),
+    );
+  }
+
+  // Execute one transfer cycle, which will process bite size 'chunks'.
+  $success = gettext_transfer_execute($source, $destination, $context['sandbox']['transfer']);
+
+  // After a number of cycles executing gettext_transfer_execute() the transfer
+  // is completed.
+  // See gettextapi_import_batch_op() for details.
+  if ($context['sandbox']['transfer']['finished']) {
+    $context['finished'] = 1;
+
+    if ($success) {
+      // Collect statistics from $context['sandbox']['transfer']
+      // and tell the world about what great work we did.
+    }
+  }
+}
+
+/**
+ * Batch wrap-up for gettext data transfer.
+ */
+function gettext_transfer_batch_finished($success, $results, $operations) {
+  if ($success) {
+    // Call post processing handler defined by the destination object.
+    // Can we get the callback and arguments via $results?
+    $post_process_callback = $results['post_process_callback'];
+    $arguments = $results['post_process_arguments'];
+    $post_process_callback($arguments);
+
+    // We did it, Magoo! :)
+  }
+  else {
+    // Sadly reporting where it went wrong.
+  }
+}
+
+/**
+ * Post processing for gettext language import into the database.
+ *
+ * To be called after successfull (batch) import.
+ */
+function gettext_post_process_import($langcode) {
+  // After succesfull import we refresh all affected parts of the system.
+  _locale_invalidate_js($langcode);
+  cache_clear_all('locale:', 'cache', TRUE);
+  menu_rebuild();
+}
+
+/**
+ * Transfer gettext data from source to destination in bite size chunks.
+ */
+function gettext_transfer_execute($source, $destination, &$transfer) {
+  // If transfer is not yet started we set the header data.
+  // The header (if supported) is written before the first translation is written.
+  if (!$destination->inProgress()) {
+    // Transfer translation header data.
+    $destination->setMetaData($source->getMetaData(), $langcode);
+  }
+
+  $chunk = $transfer['chunk'];
+  // Transfer translations as long as valid data is available and
+  // the bite size chunk is not yet swallowed.
+  while ($source->valid() && $chunk > 0) {
+    // Get one translation from source, write it to destination.
+    $translation = $source->read();
+    $destination->write($translation);
+    $chunk--;
+  }
+
+  // Report the percentage of completion for progress reporting.
+  // Ai, user interface. Why do we bother ;)
+  $transfer['state']['percentage_of_completion'] = $source->poc();
+
+  // Close connections when we are done and report the results.
+  if ($source->finished()) {
+    $destination->finish();
+    $transfer['finished'] = TRUE;
+  }
+
+  // Report statistics including errors and error log.
+  $transfer['result'] = $destination->statistics();
+}
diff --git a/core/lib/Drupal/Core/Gettext/GettextFileInterface.php b/core/lib/Drupal/Core/Gettext/GettextFileInterface.php
new file mode 100644
index 0000000..14a8247
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/GettextFileInterface.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Gettext\GettextFileInterface.
+ */
+
+namespace Drupal\Core\Gettext;
+
+use Drupal\Core\Gettext\GettextInterface;
+use Drupal\Core\StreamWrapper\PublicStream;
+
+/**
+ * Defines a Gettext data interface.
+ */
+class GettextFileInterface implements GettextInterface {
+
+  private $file_uri;
+  private $stream;
+
+  public function __construct($uri) {
+    $this->file_uri = $uri;
+  }
+
+  /**
+   * Opens the file stream.
+   */
+  function open() {
+    $opened_path = '';
+    $this->stream = new PublicStream();
+    $this->stream->stream_open($this->file_uri, 'wb', STREAM_REPORT_ERRORS, $opened_path);
+    // @todo handle errors.
+  }
+
+  /**
+   * Closes the file stream.
+   */
+  function close() {
+    $this->stream->stream_close();
+  }
+
+  /**
+   * Reads single string from the file.
+   */
+  function read() {
+    return $this->stream->stream_read(1);
+  }
+
+  /**
+   * Writes strings to the file.
+   */
+  function write($data) {
+    $this->stream->stream_write($data);
+  }
+
+  public function size() {
+
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Gettext/GettextInterface.php b/core/lib/Drupal/Core/Gettext/GettextInterface.php
new file mode 100644
index 0000000..176bf07
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/GettextInterface.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Definition of a Gettext Data Interface.
+ */
+
+namespace Drupal\Core\Gettext;
+
+/**
+ * Defines a Gettext data interface.
+ */
+interface GettextInterface {
+
+  /**
+   * Opens the interface connection.
+   */
+  function open();
+
+  /**
+   * Closes the interface connection.
+   */
+  function close();
+
+  /**
+   * Read single element from the data source.
+   *
+   * Reads for example a row from a file or a record from a database.
+   */
+  function read();
+
+  /**
+   * Write single element to the data source.
+   *
+   * Writes for example a row to a file or a record to a database
+   */
+  function write($data);
+
+  /**
+   * Gets the size of the data source in number of translations.
+   *
+   * Where an exact size is not available or costly (e.g. reading and parsing
+   * a file) an estimated size will do. A higher estimate is better than a
+   * lower one.
+   */
+  function size();
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Gettext/PoInfiniteProducer.php b/core/lib/Drupal/Core/Gettext/PoInfiniteProducer.php
new file mode 100644
index 0000000..d740e5b
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/PoInfiniteProducer.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\Core\Gettext;
+
+class PoInfiniteProducer extends PoProducer {
+
+  private $_starttime;
+
+  function __construct() {
+    parent::__construct();
+    $this->rewind();
+  }
+
+  public function getCurrentState() {
+    return $_state;
+  }
+
+  public function getHeader() {
+    return $this->_header;
+  }
+
+  public function setCurrentState($state) {
+    $this->_state = $state;
+  }
+
+  public function setHeader($header) {
+    $this->_header = $header;
+  }
+
+  public function next() {
+    $now = 1 + microtime(TRUE) - $this->_starttime;
+    $this->_key = "key_" . $now;
+    $this->_value = (object) array(
+          'source' => 'random:' . $now,
+          'translation' => 'translates:' . $now,
+          'plural' => 0,
+          'context' => '',
+    );
+  }
+
+  public function rewind() {
+    $this->_starttime = microtime(TRUE);
+  }
+
+  public function valid() {
+    return TRUE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Gettext/PoProducer.php b/core/lib/Drupal/Core/Gettext/PoProducer.php
new file mode 100644
index 0000000..7091d83
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/PoProducer.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Core\Gettext;
+
+abstract class PoProducer implements \Iterator {
+
+  protected $_key;
+  protected $_value;
+
+  abstract function setHeader($header);
+
+  abstract function getHeader();
+
+  abstract function setCurrentState($state);
+
+  abstract function getCurrentState();
+
+  function __construct() {
+    $this->_key = NULL;
+    $this->_value = NULL;
+  }
+
+  /* Iterator implementation */
+
+  public function current() {
+    return $this->_value;
+  }
+
+  public function key() {
+    return $this->_key;
+  }
+
+  function next() {
+    ;
+  }
+
+  function rewind() {
+    ;
+  }
+
+  function valid() {
+    ;
+  }
+
+}
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Gettext/PoReader.php b/core/lib/Drupal/Core/Gettext/PoReader.php
new file mode 100644
index 0000000..e9414eb
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/PoReader.php
@@ -0,0 +1,413 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Gettext\Reader.
+ */
+
+namespace Drupal\Core\Gettext;
+
+use Drupal\Core\Gettext\Reader;
+
+/**
+ * Defines a Gettext reader for PO format.
+ */
+class PoReader extends Reader {
+  private $lineno = 0;            // Source line number being parsed.
+  private $context = '';          // The context of the translation being parsed.
+
+  /**
+   * Implements magic function __construct().
+   */
+  public function __construct(GettextInterface $interface) {
+    $this->gettextInterface = $interface;
+  }
+
+  /**
+   * Implements magic function __destruct().
+   */
+  public function __destruct() {
+    $this->gettextInterface->close();
+  }
+
+  /**
+   * Return a translation object (singular or plural)
+   *
+   * @todo Define a translation object for this purpose?
+   *       Or use a standard class for better performance?
+   */
+  public function read() {
+    // If we have not read any data before, we open the connection,
+    // read the PO header and set the flag that we are in progress.
+    if (!$this->inProgress) {
+      $this->gettextInterface->open();
+      // @todo Handle initialisation error.
+      $this->sourceSize = $this->gettextInterface->size();
+      // Read the PO header and store the data.
+      $this->readHeader();
+      // @todo Handle read errors.
+      $this->inProgress = TRUE;
+    }
+
+    $translation = $this->readTranslation($this->filter);
+    // @todo Handle read errors.
+    $this->valid = TRUE;
+    $this->index++;
+
+    // @todo detect EOF
+    if ($eof) {
+      $this->finished = TRUE;
+    }
+
+    return $translation;
+  }
+
+  private function readTranslation($filter) {
+    $string = $this->gettextInterface->read();
+  }
+
+  private function readHeader() {
+    $string = $this->gettextInterface->read();
+  }
+
+  /**
+   * Parses the PO header and stores it as meta data.
+   */
+  public function parseHeader() {
+    $this->MetaData = array();
+  }
+
+  private function readLine() {
+    /*
+     * The parser context. Can be:
+     *  - 'COMMENT' (#)
+     *  - 'MSGID' (msgid)
+     *  - 'MSGID_PLURAL' (msgid_plural)
+     *  - 'MSGCTXT' (msgctxt)
+     *  - 'MSGSTR' (msgstr or msgstr[])
+     *  - 'MSGSTR_ARR' (msgstr_arg)
+     */
+    $this->context = 'COMMENT';
+
+    // Current entry being read.
+    $current = array();
+
+    // Current plurality for 'msgstr[]'.
+    $plural = 0;
+
+    // Current line.
+    $this->lineno = 0;
+
+    // Read one line from the interface.
+    $line = $this->gettextInterface->read();
+
+    if (!$this->finished) {
+
+      if ($this->lineno == 0) {
+        // The first line might come with a UTF-8 BOM, which should be removed.
+        $line = str_replace("\xEF\xBB\xBF", '', $line);
+      }
+
+      $this->lineno++;
+
+      // Trim away the linefeed.
+      $line = trim(strtr($line, array("\\\n" => "")));
+
+      if (!strncmp('#', $line, 1)) {
+        // Lines starting with '#' are comments.
+
+        if ($this->context == 'COMMENT') {
+          // Already in comment token, insert the comment.
+          $current['#'][] = substr($line, 1);
+        }
+        elseif (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
+          // We are currently in string token, close it out.
+          $this->saveOneString($current);
+
+          // Start a new entry for the comment.
+          $current = array();
+          $current['#'][]  = substr($line, 1);
+
+          $this->context = 'COMMENT';
+        }
+        else {
+          // A comment following any other token is a syntax error.
+          $this->log('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $this->lineno);
+          return FALSE;
+        }
+      }
+      elseif (!strncmp('msgid_plural', $line, 12)) {
+        // A plural form for the current message.
+
+        if ($this->context != 'MSGID') {
+          // A plural form cannot be added to anything else but the id directly.
+          $this->log('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Remove 'msgid_plural' and trim away whitespace.
+        $line = trim(substr($line, 12));
+        // At this point, $line should now contain only the plural form.
+
+        $quoted = $this->parseQuoted($line);
+        if ($quoted === FALSE) {
+          // The plural form must be wrapped in quotes.
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Append the plural form to the current entry.
+        if (is_string($current['msgid'])) {
+          // The first value was stored as string. Now we know the context is
+          // plural, it is converted to array.
+          $current['msgid'] = array($current['msgid']);
+        }
+        $current['msgid'][] = $quoted;
+
+        $this->context = 'MSGID_PLURAL';
+      }
+      elseif (!strncmp('msgid', $line, 5)) {
+        // Starting a new message.
+
+        if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
+          // We are currently in a message string, close it out.
+          $this->saveOneString($current);
+
+          // Start a new context for the id.
+          $current = array();
+        }
+        elseif ($this->context == 'MSGID') {
+          // We are currently already in the context, meaning we passed an id with no data.
+          $this->log('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Remove 'msgid' and trim away whitespace.
+        $line = trim(substr($line, 5));
+        // At this point, $line should now contain only the message id.
+
+        $quoted = $this->parseQuoted($line);
+        if ($quoted === FALSE) {
+          // The message id must be wrapped in quotes.
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        $current['msgid'] = $quoted;
+        $this->context = 'MSGID';
+      }
+      elseif (!strncmp('msgctxt', $line, 7)) {
+        // Starting a new context.
+
+        if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
+          // We are currently in a message, start a new one.
+          $this->saveOneString($current);
+          $current = array();
+        }
+        elseif (!empty($current['msgctxt'])) {
+          // A context cannot apply to another context.
+          $this->log('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Remove 'msgctxt' and trim away whitespaces.
+        $line = trim(substr($line, 7));
+        // At this point, $line should now contain the context.
+
+        $quoted = $this->parseQuoted($line);
+        if ($quoted === FALSE) {
+          // The context string must be quoted.
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        $current['msgctxt'] = $quoted;
+
+        $this->context = 'MSGCTXT';
+      }
+      elseif (!strncmp('msgstr[', $line, 7)) {
+        // A message string for a specific plurality.
+
+        if (($this->context != 'MSGID') && ($this->context != 'MSGCTXT') && ($this->context != 'MSGID_PLURAL') && ($this->context != 'MSGSTR_ARR')) {
+          // Message strings must come after msgid, msgxtxt, msgid_plural, or other msgstr[] entries.
+          $this->log('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Ensure the plurality is terminated.
+        if (strpos($line, ']') === FALSE) {
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Extract the plurality.
+        $frombracket = strstr($line, '[');
+        $plural = substr($frombracket, 1, strpos($frombracket, ']') - 1);
+
+        // Skip to the next whitespace and trim away any further whitespace, bringing $line to the message data.
+        $line = trim(strstr($line, " "));
+
+        $quoted = $this->parseQuoted($line);
+        if ($quoted === FALSE) {
+          // The string must be quoted.
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        $current['msgstr'][$plural] = $quoted;
+
+        $this->context = 'MSGSTR_ARR';
+      }
+      elseif (!strncmp("msgstr", $line, 6)) {
+        // A string for the an id or context.
+
+        if (($this->context != 'MSGID') && ($this->context != 'MSGCTXT')) {
+          // Strings are only valid within an id or context scope.
+          $this->log('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Remove 'msgstr' and trim away away whitespaces.
+        $line = trim(substr($line, 6));
+        // At this point, $line should now contain the message.
+
+        $quoted = $this->parseQuoted($line);
+        if ($quoted === FALSE) {
+          // The string must be quoted.
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        $current['msgstr'] = $quoted;
+
+        $this->context = 'MSGSTR';
+      }
+      elseif ($line != '') {
+        // Anything that is not a token may be a continuation of a previous token.
+
+        $quoted = $this->parseQuoted($line);
+        if ($quoted === FALSE) {
+          // The string must be quoted.
+          $this->log('The translation file %filename contains a syntax error on line %line.', $this->lineno);
+          return FALSE;
+        }
+
+        // Append the string to the current context.
+        if (($this->context == 'MSGID') || ($this->context == 'MSGID_PLURAL')) {
+          $current['msgid'] .= $quoted;
+        }
+        elseif ($this->context == 'MSGCTXT') {
+          $current['msgctxt'] .= $quoted;
+        }
+        elseif ($this->context == 'MSGSTR') {
+          $current['msgstr'] .= $quoted;
+        }
+        elseif ($this->context == 'MSGSTR_ARR') {
+          $current['msgstr'][$plural] .= $quoted;
+        }
+        else {
+          // No valid context to append to.
+          $this->log('The translation file %filename contains an error: there is an unexpected string on line %line.', $this->lineno);
+          return FALSE;
+        }
+      }
+    }
+
+    // End of PO file, closed out the last entry.
+    if (($this->context == 'MSGSTR') || ($this->context == 'MSGSTR_ARR')) {
+      $this->saveOneString($current);
+    }
+    elseif ($this->context != 'COMMENT') {
+      $this->log('The translation file %filename ended unexpectedly at line %line.', $this->lineno);
+      return FALSE;
+    }
+  }
+
+  /**
+   * Sets an error message if an error occurred during locale file parsing.
+   *
+   * @param $message
+   *   The message to be translated.
+   * @param $lineno
+   *   An optional line number argument.
+   */
+  protected function log($message, $lineno = NULL) {
+    if (isset($lineno)) {
+      $vars['%line'] = $lineno;
+    }
+    $t = get_t();
+    $this->errorLog[] = $t($message, $vars);
+  }
+
+  /**
+   * Store the parsed values as translation object.
+   */
+  public function saveOneString($value) {
+    $plural = FALSE;
+
+    $comments = empty($value['#']) ? $this->shortenComments($value['#']) : '';
+
+    if (is_array($value['msgstr'])) {
+      // Sort plural variants by their form index.
+      ksort($value['msgstr']);
+      $plural = TRUE;
+    }
+
+    $translation = stdClass();
+    $translation->context = isset($value['msgctxt']) ? $value['msgctxt'] : '';
+    $translation->source = $value['msgid'];
+    $translation->translation = $value['msgstr'];
+    $translation->plural = $plural;
+    $translation->comment = $comments;
+  }
+
+  /**
+   * Parses a string in quotes.
+   *
+   * @param $string
+   *   A string specified with enclosing quotes.
+   *
+   * @return
+   *   The string parsed from inside the quotes.
+   */
+  function parseQuoted($string) {
+    if (substr($string, 0, 1) != substr($string, -1, 1)) {
+      return FALSE;   // Start and end quotes must be the same
+    }
+    $quote = substr($string, 0, 1);
+    $string = substr($string, 1, -1);
+    if ($quote == '"') {        // Double quotes: strip slashes
+      return stripcslashes($string);
+    }
+    elseif ($quote == "'") {  // Simple quote: return as-is
+      return $string;
+    }
+    else {
+      return FALSE;             // Unrecognized quote
+    }
+  }
+
+  /**
+   * Generates a short, one-string version of the passed comment array.
+   *
+   * @param $comment
+   *   An array of strings containing a comment.
+   *
+   * @return
+   *   Short one-string version of the comment.
+   */
+  private function shortenComments($comments) {
+    $comm = '';
+    while (count($comment)) {
+      $test = $comm . substr(array_shift($comment), 1) . ', ';
+      if (strlen($comm) < 130) {
+        $comm = $test;
+      }
+      else {
+        break;
+      }
+    }
+    return trim(substr($comm, 0, -2));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Gettext/PoWriter.php b/core/lib/Drupal/Core/Gettext/PoWriter.php
new file mode 100644
index 0000000..b09b682
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/PoWriter.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Gettext\PoWriter.
+ */
+
+namespace Drupal\Core\Gettext;
+
+use Drupal\Core\Gettext\Writer;
+
+/**
+ * Defines a Gettext writer.
+ */
+class PoWriter extends Writer {
+
+  /**
+   * Write translation string (singular or plural)
+   *
+   * @todo Define a translation object for this purpose?
+   *       Or use a standard class for better performance?
+   */
+  public function write($translation) {
+    // If we have not written any data before, we open the connection,
+    // write the PO header and set the flag that we are in progress.
+    if (!$this->inProgress) {
+      $this->gettextInterface->open();
+      // @todo Handle initialisation error.
+      $this->gettextInterface->write($this->compileHeader());
+      // @todo Handle write errors.
+      $this->inProgress = TRUE;
+    }
+
+    $this->gettextInterface->write($this->compileTranslation($translation), $this->writeMode);
+    // @todo Handle write errors.
+  }
+
+  /**
+   * Compile PO translations strings from a translation object.
+   *
+   * Translation object consists of:
+   *   source       string (singular) or array of strings (plural)
+   *   translation  string (singular) or array of strings (plural)
+   *   plural       TRUE: source and translation are plurals
+   *   context      source context string
+   */
+  private function compileTranslation($translation) {
+    $output = '';
+
+    // Format string context.
+    if (!empty($translation->context)) {
+      $output .= 'msgctxt ' . $this->formatString($translation->context);
+    }
+
+    // Format translation
+    if ($translation->plural) {
+      $output .= $this->formatPlural($translation);
+    }
+    else {
+      $output .= $this->formatSingular($translation);
+    }
+
+    // Add one empty line to separate the translations.
+
+    return $output;
+  }
+
+  /**
+   * Compile the PO header.
+   */
+  private function compileHeader() {
+    $output = '';
+
+    // Add language description and author as comment.
+    $languages = language_list();
+    $language_name = isset($languages[$this->langcode]) ? $languages[$this->langcode]->name : '';
+    $output .= '# ' . $language_name . ' translation of ' . variable_get('site_name', 'Drupal') . "\n";
+    if (!empty($this->metaData['authors'])) {
+      $output .= '# Generated by ' . implode("\n# ", $this->metaData['authors']) . "\n";
+    }
+    $output .= "#\n";
+
+    // Add the actual header information.
+    $output .= "msgid \"\"\n";
+    $output .= "msgstr \"\"\n";
+    $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
+    $output .= "\"POT-Creation-Date: " . $this->metaData['po_date'] . "\\n\"\n";
+    $output .= "\"PO-Revision-Date: " . $this->metaData['po_date'] . "\\n\"\n";
+    $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
+    $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
+    $output .= "\"MIME-Version: 1.0\\n\"\n";
+    $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
+    $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
+    $output .= "\"Plural-Forms: " . $this->metaData['plurals'] . "\\n\"\n";
+    $output .= "\n";
+
+    return $output;
+  }
+
+  /**
+   * Formats a plural translation.
+   */
+  private function formatPlural($translation) {
+    $output = '';
+
+    // Format source strings.
+    $output .= 'msgid ' . $this->formatString($translation->source[0]);
+    $output .= 'msgid_plural ' . $this->formatString($translation->source[1]);
+
+    // Format translation strings.
+    $plurals = variable_get('locale_translation_plurals', array());
+    // @todo What to when $plurals[$this->langcode] is not set?
+    //       This (currently) happens if a language is created manually or importing a malformed po.
+    $nplurals = $plurals[$this->langcode]['plurals'];
+    for ($i = 0; $i < $nplurals; $i++) {
+      if (isset($translation->translation[$i])) {
+        $output .= 'msgstr[' . $i . '] ' . $this->formatString($translation->translation[$i]);
+      }
+      else {
+        $output .= 'msgstr[' . $i . '] ""' . "\n";
+      }
+    }
+
+    return $output;
+  }
+
+  /**
+   * Formats a singular translation.
+   */
+  private function formatSingular($translation) {
+    $output = '';
+    $output .= 'msgid ' . $this->formatString($translation->source);
+    $output .= 'msgstr ' . $this->formatString($translation->translation);
+    return $output;
+  }
+
+  /**
+   * Formats a string for output on multiple lines.
+   */
+  private function formatString($string) {
+    // Escape characters for processing.
+    $string = addcslashes($string, "\0..\37\\\"");
+
+    // Always include a line break after the explicit \n line breaks from
+    // the source string. Otherwise wrap at 70 chars to accommodate the extra
+    // format overhead too.
+    $parts = explode("\n", wordwrap(str_replace('\n', "\\n\n", $string), 70, " \n"));
+
+    // Multiline string should be exported starting with a "" and newline to
+    // have all lines aligned on the same column.
+    if (count($parts) > 1) {
+      return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
+    }
+    // Single line strings are output on the same line.
+    else {
+      return "\"$parts[0]\"\n";
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Gettext/Reader.php b/core/lib/Drupal/Core/Gettext/Reader.php
new file mode 100644
index 0000000..93f441f
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/Reader.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Gettext\Reader.
+ */
+
+namespace Drupal\Core\Gettext;
+
+use Drupal\Core\Gettext\GettextInterface;
+
+/**
+ * Defines a Gettext reader.
+ *
+ * @todo Or implement this as traversable/Iterator ?
+ */
+abstract class Reader {
+
+  protected $gettextInterface;    // Gettext Data interface
+  protected $metaData = array();  // Gettext meta data e.g. language, plural formula.
+  protected $biteSize = 100;      // Default bite size
+  protected $langcode = '';       // Language code of the translation data.
+  //protected $language;            // Language object of selected language.
+  protected $index = 0;           // Pointer where we are reading the content, in number of translations.
+  protected $sourceSize;          // Calculated or estimated size of the data source in number of translations.
+  protected $inProgress = FALSE;  // Boolean indicating the data connection is open and transfer may have started.
+  //protected $valid = FALSE;       // Boolean indicating valid data is available. // @todo Needed?
+  protected $finished = FALSE;    // Boolean indicating the last record has been read;
+  protected $filter = array();    // Array of filter arguments used to filter translations being read.
+  protected $errorLog = array();  // Log of parsing errors.
+
+  /**
+   * Implements magic function __construct().
+   */
+  public function __construct(GettextInterface $interface) {
+    $this->gettextInterface = $interface;
+  }
+
+  /**
+   * Implements magic function __destruct().
+   */
+  public function __destruct() {
+    $this->gettextInterface->close();
+  }
+
+  /**
+   * Return a translation object (singular or plural)
+   *
+   * @todo Define a translation object for this purpose?
+   *       Or use a standard class for better performance?
+   */
+  public function read() {
+  }
+
+  /**
+   * Return header/meta data (date, plural formula, etc.)
+   */
+  public function getMetaData() {
+    return $this->metaData;
+  }
+
+  /**
+   * Return TRUE if the file is opened or the transfer has started.
+   */
+  public function inProgress() {
+    return $this->inProgress;
+  }
+
+  /**
+   * Return the name of an error callback function
+   */
+  public function errorCallback() {
+    return '';
+  }
+
+  /**
+   * Return arguments for an error callback function
+   */
+  public function errorArguments() {
+    return array();
+  }
+
+  /**
+   * Return the name of a post processing callback function
+   */
+  public function postProcessCallback() {
+    return '';
+  }
+
+  /**
+   * Return arguments for a post processing callback function
+   */
+  public function postProcessArguments() {
+    return array();
+  }
+
+  /**
+   * Return the calculated or estimated size in number of translations. Zero for unknown. To be generated without opening the connection. e.g. use file size not number of lines.
+   */
+  public function size() {
+    return $this->sourceSize;
+  }
+
+  /**
+   * Return a bite size. Based on experience and size to be transfered within a reasonable time. Size in number of source/translations pairs. e.g. database records in locales_source or locales_target tables. A set of plural's are counted as one.
+   */
+  public function biteSize() {
+    return $this->bite_size;
+  }
+
+  /**
+   * Accept a set of data filter arguments: Language, Context
+   */
+  public function setFilter($arguments) {
+    $this->filter = $arguments;
+  }
+
+  /**
+   * Return Is valid. Valid data is available, not EOF, no errors, etc.
+   */
+  public function valid() {
+    return $this->valid;
+  }
+
+  /**
+   * Get percentage of completion (read)
+   */
+  public function poc() {
+    if (!$this->$inProgress) {
+      return 0;
+    }
+    if ($this->finished) {
+      return 1;
+    }
+    // If reading is not finished, we limit the percentage to max. 95%
+    // Percentages above 100% are a result of low estimate of source size and
+    // will be suppressed.
+    return min(0.95, $this->index/$this->sourceSize);
+  }
+
+  /**
+   * Return syntax errors
+   */
+  public function getLog($category = NULL) {
+    return $this->errorLog;
+  }
+
+  /**
+   * Internal: log syntax errors
+   */
+  protected function log($line, $message) {
+    $this->errorLog[$line] = $message;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Gettext/Writer.php b/core/lib/Drupal/Core/Gettext/Writer.php
new file mode 100644
index 0000000..34c0c3d
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/Writer.php
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Gettext\Writer.
+ */
+
+namespace Drupal\Core\Gettext;
+
+use Drupal\Core\Gettext\GettextInterface;
+
+/**
+ * Defines a Gettext writer.
+ */
+abstract class Writer {
+
+  protected $gettextInterface;    // Gettext Data interface
+  protected $metaData = array();  // Gettext meta data e.g. language, plural formula.
+  protected $biteSize = 100;      // Default bite size
+  protected $langcode = '';       // Language code of the translation data.
+  protected $language;            // Language object of selected language.
+  protected $inProgress = FALSE;  // Boolean indicating the data connection is open and transfer may have started.
+  protected $valid = FALSE;       // Boolean indicating valid data is available. // @todo Needed?
+  protected $writeMode = '';      // Whether to replace or skip existing translations when writing translation.
+  protected $resultsAdded = 0;    // Number of translations added.
+  protected $resultsReplaced = 0; // Number of translations replaced.
+  protected $resultsIgnored = 0;  // Number of translations Ignored.
+  protected $resultsError = 0;    // Number of strings containing invalid html;
+  protected $errorLog = array();  // Log of parsing errors.
+
+  /**
+   * Implements magic function __construct().
+   */
+
+  public function __construct(GettextInterface $interface, $langcode) {
+    $this->gettextInterface = $interface;
+    $this->langcode = $langcode;
+
+    $languages = language_list();
+    if (isset($languages[$langcode])) {
+      $this->language = $languages[$langcode];
+    }
+    else {
+      // @todo throw error: Unknown language code.
+    }
+
+    // Set default meta data.
+    $this->metaData = array(
+      'authors' => array(),
+      'po_date' => date("Y-m-d H:iO"),
+      'plurals' => 'nplurals=2; plural=(n > 1);',
+    );
+  }
+
+  /**
+   * Implements magic function __destruct().
+   */
+  public function __destruct() {
+    $this->gettextInterface->close();
+  }
+
+  /**
+   * Write translation string (singular or plural)
+   *
+   * @todo Define a translation object for this purpose?
+   *       Or use a standard class for better performance?
+   */
+  public function write($translation) {
+
+  }
+
+  /**
+   * Set header/meta data (date, plural formula, etc.)
+   */
+  public function setMetaData(array $data) {
+    $this->metaData = array_merge($this->metaData, $data);
+  }
+
+  /**
+   * Return TRUE if the file is opened or the transfer has started.
+   */
+  public function inProgress() {
+    return $this->inProgress;
+  }
+
+  /**
+   * Return the name of an error callback function
+   */
+  public function errorCallback() {
+    return '';
+  }
+
+  /**
+   * Return arguments for an error callback function
+   */
+  public function errorArguments() {
+    return array();
+  }
+
+  /**
+   * Return the name of a post processing callback function
+   */
+  public function postProcessCallback() {
+    return '';
+  }
+
+  /**
+   * Return arguments for a post processing callback function
+   */
+  public function postProcessArguments() {
+    return array();
+  }
+
+  /**
+   * Return a bite size. Based on experience and size to be transfered within a reasonable time. Size in number of source/translations pairs. e.g. database records in locales_source or locales_target tables. A set of plural's are counted as one.
+   */
+  public function biteSize() {
+    return $this->bite_size;
+  }
+
+  /**
+   * Return the language code as defined by the data (e.g. po header). Use language filter (see below) as fallback.
+   */
+  public function langcode() {
+    return $this->langcode;
+  }
+
+  /**
+   * Accept write mode argument (replace, keep changes, skip existing)
+   *
+   * @todo Move this to __constructor()?
+   */
+  public function setWriteMode($mode) {
+    $this->writeMode = $mode;
+  }
+
+  /**
+   * Return Is valid. Valid data is available, not EOF, no errors, etc.
+   *
+   * @todo Needed for Writer?
+   */
+  public function valid() {
+    return $this->valid;
+  }
+
+  /**
+   * Get statistics (added, replaced, ignored, error, error log)
+   */
+  public function statistics() {
+    return array(
+      'added' => $this->resultsAdded,
+      'replaced' => $this->resultsReplaced,
+      'ignored' => $this->resultsIgnored,
+      'error' => $this->resultsError,
+    );
+  }
+
+  /**
+   * Return syntax errors
+   */
+  public function getLog($category = NULL) {
+    return $this->errorLog;
+  }
+
+  /**
+   * Internal: log syntax errors
+   */
+  protected function log($line, $message) {
+    $this->errorLog[$line] = $message;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Gettext/testGettext.php b/core/lib/Drupal/Core/Gettext/testGettext.php
new file mode 100644
index 0000000..7da5df2
--- /dev/null
+++ b/core/lib/Drupal/Core/Gettext/testGettext.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * Run this like:
+ * drush @drupal.d8 php-script core/lib/Drupal/Core/Gettext/testGettext.php
+ */
+use Drupal\Core\Gettext\GettextFileInterface;
+use Drupal\Core\Gettext\PoWriter;
+use Drupal\Core\Gettext\PoInfiniteProducer;
+
+/**
+ * Creates a test PO stucture
+ *
+ * TODO: the object structure is for .po so we miss the .pot format
+ *
+ * @param type $langcode
+ * @return array of objects
+ */
+function gettext_struct($langcode = 'nl') {
+  $translations = array(
+    (object) array(
+      'source' => 'home',
+      'translation' => 'thuis',
+      'plural' => 0,
+      'context' => '',
+    ),
+    (object) array(
+      'source' => 'delete',
+      'translation' => 'verwijderen',
+      'plural' => 0,
+      'context' => '',
+    ),
+    (object) array(
+      'source' => array('1 day', '@count days'),
+      'translation' => array('1 dag', '@count dagen'),
+      'plural' => 1,
+      'context' => '',
+    ),
+  );
+
+  return array(
+    'langcode' => $langcode,
+    'items' => $translations,
+  );
+}
+
+/**
+ * A simple writer of the pot structure
+ *
+ * TODO: the header is not specified enough.
+ *
+ * @param type $uri
+ */
+function testGettextWrite($uri) {
+  $struct = gettext_struct();
+
+  $file = new GettextFileInterface($uri);
+  $target = new PoWriter($file, $langcode);
+  $header = array(
+    'authors' => array('me', 'you'),
+    'po_date' => date("Y-m-d H:iO"),
+    'plurals' => 'nplurals=2; plural=(n > 1);',
+  );
+  $target->setMetaData($header, $struct['langcode']);
+
+  $translations = $struct['items'];
+  foreach ($translations as $translation) {
+    $target->write($translation);
+  }
+}
+
+/**
+ * Processes a File into a database
+ *
+ * @param type $uri
+ * @param type $dbwriter
+ * @param type $type
+ */
+function testProcessFileToDb($uri, $dbwriter, $type = 'po') {
+  // Producer
+  $producer = testFileToStruct($uri, $type);
+  // Consumer
+  $dbwriter->batchProcess($producer, $type);
+}
+
+/**
+ * Produce a file from an Iterator
+ *
+ * TODO: The implementation of Iterator fails somehow :(
+ * @param type $uri
+ * @param type $count
+ * @param type $langcode
+ */
+function testInfiniteProducer($uri, $count = 10, $langcode = 'nl') {
+  $file = new GettextFileInterface($uri);
+  $target = new PoWriter($file, $langcode);
+  $header = array(
+    'authors' => array('me', 'you'),
+    'po_date' => date("Y-m-d H:iO"),
+    'plurals' => 'nplurals=2; plural=(n > 1);',
+  );
+  $target->setMetaData($header, $struct['langcode']);
+
+  $p = new PoInfiniteProducer();
+  // Fails to work
+  reset($p);
+  //$p->rewind();
+  //$p = new Drupal\Core\Gettext\PoInfiniteProducer();
+  for ($i = 0; $i < 3; $i++) {
+    // TODO: This should be next($p);
+    $p->next();
+    // TODO: This should be current($p);
+    $po = $p->current();
+    $target->write($po);
+  }
+}
+
+// Write into public
+testGettextWrite('public://text-file.txt.po');
+testInfiniteProducer('public://infinite-text-file.txt.po', 100);
