diff --git a/core/modules/locale/lib/Drupal/locale/LocaleLookup.php b/core/modules/locale/lib/Drupal/locale/LocaleLookup.php
index 897eaa1..5199f2a 100644
--- a/core/modules/locale/lib/Drupal/locale/LocaleLookup.php
+++ b/core/modules/locale/lib/Drupal/locale/LocaleLookup.php
@@ -8,6 +8,8 @@
 namespace Drupal\locale;
 
 use Drupal\Core\Utility\CacheArray;
+use Drupal\locale\LocaleSource;
+use Drupal\locale\LocaleTranslation;
 
 /**
  * Extends CacheArray to allow for dynamic building of the locale cache.
@@ -44,36 +46,22 @@ class LocaleLookup extends CacheArray {
    * Overrides DrupalCacheArray::resolveCacheMiss().
    */
   protected function resolveCacheMiss($offset) {
-    $translation = db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array(
-      ':language' => $this->langcode,
-      ':source' => $offset,
-      ':context' => $this->context,
-    ))->fetchObject();
+    $translation = LocaleTranslation::loadBySource($this->langcode, $offset, $this->context, array('lid', 'version'), array('translation'));
     if ($translation) {
-      if ($translation->version != VERSION) {
-        // This is the first use of this string under current Drupal version.
-        // Update the {locales_source} table to indicate the string is current.
-        db_update('locales_source')
-          ->fields(array('version' => VERSION))
-          ->condition('lid', $translation->lid)
-          ->execute();
-      }
+      $translation->checkVersion();
       $value = !empty($translation->translation) ? $translation->translation : TRUE;
     }
     else {
       // We don't have the source string, update the {locales_source} table to
       // indicate the string is not translated.
-      db_merge('locales_source')
-        ->insertFields(array(
-          'location' => request_uri(),
-          'version' => VERSION,
-        ))
-        ->key(array(
-          'source' => $offset,
-          'context' => $this->context,
-        ))
-        ->execute();
-        $value = TRUE;
+      $source = new LocaleSource(array(
+        'source' => $offset,
+        'context' => $this->context,
+        'location' => request_uri(),
+        'version' => VERSION
+      ));
+      $source->save();
+      $value = TRUE;
     }
     $this->storage[$offset] = $value;
     // Disabling the usage of string caching allows a module to watch for
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleSource.php b/core/modules/locale/lib/Drupal/locale/LocaleSource.php
new file mode 100644
index 0000000..09082ab
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/LocaleSource.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\LocaleSource.
+ */
+
+namespace Drupal\locale;
+
+use Drupal\locale\LocaleString;
+use PDO;
+
+/**
+ * Defines the locale source class.
+ */
+class LocaleSource extends LocaleString {
+
+  /**
+   * Load by locale id.
+   */
+  public static function loadById($lid) {
+    $strings = self::loadMultiple(array('lid' => $lid));
+    return reset($strings);
+  }
+
+  /**
+   * Load by context, source.
+   */
+  public static function loadBySource($source, $context = '') {
+    $strings = self::loadMultiple(array('source' => $source, 'context' => $context));
+    return reset($strings);
+  }
+
+  /**
+   * Load by multiple conditions.
+   */
+  public static function loadMultiple($conditions) {
+    $query = db_select('locales_source', 's')
+      ->fields('s');
+    foreach ($conditions as $field => $value) {
+      $query->conditions('s.' . $field, $value);
+    }
+    $result =$query->execute();
+    $result->setFetchMode(PDO::FETCH_CLASS, 'Drupal\locale\LocaleSource');
+    return $result->fetchAll();
+  }
+
+  /**
+   * Implementation of LocaleString::getSource().
+   */
+  public function getSource() {
+    return $this;
+  }
+
+  /**
+   * Implementation of LocaleString::getString().
+   */
+  public function getString() {
+    return $this->source;
+  }
+
+  /**
+   * Implementation of LocaleString::save().
+   */
+  public function save() {
+    $key_fields = isset($this->lid) ? 'lid' : array();
+    drupal_write_record('locales_source', $this, $key_fields);
+    return $this;
+  }
+
+  /**
+   * Implementation of LocaleString::delete().
+   */
+  public function delete() {
+    if (isset($this->lid)) {
+      $query = db_delete('locales_source')
+        ->condition('lid', $this->lid)
+        ->execute();
+      unset($this->lid);
+    }
+    return $this;
+  }
+}
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleString.php b/core/modules/locale/lib/Drupal/locale/LocaleString.php
new file mode 100644
index 0000000..b1dedf9
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/LocaleString.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\LocaleString.
+ */
+
+namespace Drupal\locale;
+
+/**
+ * Defines the locale string class.
+ *
+ * This is the base class for LocaleSource and LocaleTranslation.
+ */
+abstract class LocaleString {
+  /**
+   * The locale ID.
+   *
+   * @var integer
+   */
+  public $lid;
+
+  /**
+   * The string location.
+   *
+   * @var string
+   */
+  public $location = '';
+
+  /**
+   * The source string.
+   *
+   * @var string
+   */
+  public $source;
+
+  /**
+   * The string context.
+   *
+   * @var string
+   */
+  public $context = '';
+
+  /**
+   * The string version.
+   *
+   * @var string
+   */
+  public $version;
+
+  /**
+   * Constructs a new locale source object.
+   *
+   * @param $values
+   *   Object or array with initial values.
+   */
+  public function __construct($values = array()) {
+    // Set initial values.
+    $this->setValues((array)$values);
+  }
+
+  /**
+   * Constructs a new locale source object.
+   */
+  public function setValues(array $values = array()) {
+    foreach ($values as $key => $value) {
+      if (property_exists($this, $key)) {
+        $this->$key = $value;
+      }
+    }
+    return $this;
+  }
+
+  /**
+   * Check whether this string version matches a given version.
+   */
+  public function checkVersion($version) {
+    if ($this->version != $version) {
+      // This is the first use of this string under current Drupal version.
+      // Update the {locales_source} table to indicate the string is current.
+      $source = $this->getSource();
+      $source->version = $version;
+      $source->save();
+    }
+  }
+
+  /**
+   * Save string object to database.
+   */
+  public abstract function save();
+
+  /**
+   * Delete string object from database.
+   */
+  public abstract function delete();
+
+  /**
+   * Split string to work with plural values.
+   */
+  public function getPlurals() {
+    return explode(LOCALE_PLURAL_DELIMITER, $this->getString());
+  }
+
+  /**
+   * Helper function to explode plurals.
+   */
+
+  /**
+   * Get source string object.
+   */
+  public abstract function getSource();
+
+  /**
+   * Get plain string contained in this object.
+   */
+  public abstract function getString();
+
+}
\ No newline at end of file
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleTranslation.php b/core/modules/locale/lib/Drupal/locale/LocaleTranslation.php
new file mode 100644
index 0000000..b7c4284
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/LocaleTranslation.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\LocaleTranslation.
+ */
+
+namespace Drupal\locale;
+
+use Drupal\locale\LocaleString;
+
+/**
+ * Defines the locale translation class.
+ */
+class LocaleTranslation extends LocaleString {
+  /**
+   * The language code.
+   *
+   * @var string
+   */
+  public $language;
+
+  /**
+   * The string translation.
+   *
+   * @var string
+   */
+  public $translation;
+
+  /**
+   * Boolean indicating whether this string is customized.
+   */
+  public $customized;
+
+  /**
+   * Boolean indicating whether the target object exists.
+   */
+  protected $target_exists = FALSE;
+
+  /**
+   * Load by context, source.
+   */
+  public static function loadBySource($langcode, $source, $context = '', $source_fields = array(), $target_fields = NULL) {
+    $translation = self::getBaseQuery($langcode, array('source' => $source, 'context' => $context))
+      ->fields('s', $source_fields)
+      ->fields('t', $target_fields)
+      ->execute()
+      ->fetchObject('Drupal\locale\LocaleTranslation');
+    // Set values we already know, they were used as conditions.
+    if ($translation) {
+      $field = reset($target_fields);
+      $translation->setValues(array(
+        'target_exists' => isset($translation->$field),
+        'language' => $langcode,
+        'source' => $source,
+        'context' => $context,
+      ));
+    }
+    return $translation;
+  }
+
+  /**
+   * Load by context, source.
+   */
+  public static function loadById($langcode, $lid, $source_fields = array(), $target_fields = array()) {
+    // Query from admin pages.
+    $translation = self::getBaseQuery($langcode, array('lid' => $lid))
+      ->fields('s', $source_fields)
+      ->fields('t', $target_fields)
+      ->execute()
+      ->fetchObject('Drupal\locale\LocaleTranslation');
+    // Set values we already know, they were used as conditions.
+    if ($translation) {
+      $field = reset($target_fields);
+      $translation->setValues(array(
+        'target_exists' => isset($translation->$field),
+        'language' => $langcode,
+        'lid' => $lid,
+       ));
+    }
+    return $translation;
+  }
+
+  /**
+   * Load by multiple conditions.
+   */
+  public static function getBaseQuery($langcode, $source_conditions = array(), $target_conditions = array()) {
+    $query = db_select('locales_source', 's');
+    $query->join('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array(':langcode' => $langcode));
+    foreach ($source_conditions as $field => $value) {
+      $query->condition('s.' . $field, $value);
+    }
+    foreach ($target_conditions as $field => $value) {
+      $query->condition('t.' . $field, $value);
+    }
+    return $query;
+  }
+
+  /**
+   * Implementation of LocaleString::getSource().
+   */
+  public function getSource() {
+    return LocaleSource::loadById($this->lid);
+  }
+
+  /**
+   * Implementation of LocaleString::getString().
+   */
+  public function getString() {
+    return $this->translation;
+  }
+
+  /**
+   * Implementation of LocaleString::save().
+   */
+  public function save() {
+    $key_fields = $this->target_exists ? array('lid', 'language') : array();
+    drupal_write_record('locales_target', $this, $key_fields);
+    $this->target_exists = TRUE;
+    return $this;
+  }
+
+  /**
+   * Implementation of LocaleString::delete().
+   */
+  public function delete() {
+    if ($this->target_exists) {
+      $query = db_delete('locales_target')
+        ->condition('lid', $this->lid)
+        ->condition('language', $this->langcode)
+        ->execute();
+      $this->target_exists = FALSE;
+    }
+    return $this;
+  }
+}
diff --git a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
index 33f05d9..8f8100f 100644
--- a/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
+++ b/core/modules/locale/lib/Drupal/locale/PoDatabaseWriter.php
@@ -11,6 +11,8 @@ use Drupal\Component\Gettext\PoHeader;
 use Drupal\Component\Gettext\PoItem;
 use Drupal\Component\Gettext\PoReaderInterface;
 use Drupal\Component\Gettext\PoWriterInterface;
+use Drupal\locale\LocaleSource;
+use Drupal\locale\LocaleTranslation;
 
 /**
  * Gettext PO writer working with the locale module database.
@@ -226,12 +228,7 @@ class PoDatabaseWriter implements PoWriterInterface {
 
 
     // Look up the source string and any existing translation.
-    $string = db_query("SELECT s.lid, t.customized FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.source = :source AND s.context = :context", array(
-      ':source' => $source,
-      ':context' => $context,
-      ':language' => $this->_langcode,
-        ))
-        ->fetchObject();
+    $string = LocaleTranslation::loadBySource($this->_langcode, $source, $context, array('lid'), array('customized'));
 
     if (!empty($translation)) {
       // Skip this string unless it passes a check for dangerous code.
@@ -243,27 +240,19 @@ class PoDatabaseWriter implements PoWriterInterface {
       elseif (isset($string->lid)) {
         if (!isset($string->customized)) {
           // No translation in this language.
-          db_insert('locales_target')
-            ->fields(array(
-              'lid' => $string->lid,
-              'language' => $this->_langcode,
+          $string->setValues(array(
               'translation' => $translation,
               'customized' => $customized,
-            ))
-            ->execute();
+          ))->save();
 
           $this->_report['additions']++;
         }
         elseif ($overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
           // Translation exists, only overwrite if instructed.
-          db_update('locales_target')
-            ->fields(array(
+          $string->setValues(array(
               'translation' => $translation,
               'customized' => $customized,
-            ))
-            ->condition('language', $this->_langcode)
-            ->condition('lid', $string->lid)
-            ->execute();
+          ))->save();
 
           $this->_report['updates']++;
         }
@@ -271,33 +260,22 @@ class PoDatabaseWriter implements PoWriterInterface {
       }
       else {
         // No such source string in the database yet.
-        $lid = db_insert('locales_source')
-          ->fields(array(
-            'source' => $source,
-            'context' => $context,
-          ))
-          ->execute();
-
-        db_insert('locales_target')
-          ->fields(array(
-            'lid' => $lid,
-            'language' => $this->_langcode,
-            'translation' => $translation,
-            'customized' => $customized,
-          ))
-          ->execute();
+        $source = new LocaleSource(array('source' => $source, 'context' => $context));
+        $source->save();
+        $target = new LocaleTranslation($source);
+        $target->setValues(array(
+          'language' => $this->_langcode,
+          'translation' => $translation,
+          'customized' => $customized,
+        ))->save();
 
         $this->_report['additions']++;
-        return $lid;
+        return $source->lid;
       }
     }
     elseif (isset($string->lid) && isset($string->customized) && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
       // Empty translation, remove existing if instructed.
-      db_delete('locales_target')
-        ->condition('language', $this->_langcode)
-        ->condition('lid', $string->lid)
-        ->execute();
-
+      $string->delete();
       $this->_report['deletes']++;
       return $string->lid;
     }
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 5fdba76..d0cc58f 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -5,6 +5,8 @@
  * Interface translation summary, editing and deletion user interfaces.
  */
 
+use Drupal\locale\LocaleSource;
+use Drupal\locale\LocaleTranslation;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
@@ -23,10 +25,9 @@ function locale_translate_page() {
 function locale_translate_query() {
   $filter_values = locale_translate_filter_values();
 
-  $sql_query = db_select('locales_source', 's');
   // Language is sanitized to be one of the possible options in
   // locale_translate_filter_values().
-  $sql_query->leftJoin('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array(':langcode' => $filter_values['langcode']));
+  $sql_query = LocaleTranslation::getBaseQuery($filter_values['langcode']);
   $sql_query->fields('s', array('source', 'location', 'context', 'lid'));
   $sql_query->fields('t', array('translation', 'language', 'customized'));
 
@@ -53,7 +54,9 @@ function locale_translate_query() {
   }
 
   $sql_query = $sql_query->extend('Drupal\Core\Database\Query\PagerSelectExtender')->limit(30);
-  return $sql_query->execute();
+  $result = $sql_query->execute();
+  $result->setFetchMode(PDO::FETCH_CLASS, 'Drupal\locale\LocaleTranslation');
+  return $result;
 }
 
 /**
@@ -270,14 +273,16 @@ function locale_translate_edit_form($form, &$form_state) {
   );
 
   if (isset($langcode)) {
-    $strings = locale_translate_query();
+    $strings = locale_translate_query()->fetchAll();
 
     $plural_formulas = variable_get('locale_translation_plurals', array());
 
     foreach ($strings as $string) {
+      // Cast into source string, will do for our purposes.
+      $source = new LocaleSource($string);
       // Split source to work with plural values.
-      $source_array = explode(LOCALE_PLURAL_DELIMITER, $string->source);
-      $translation_array = explode(LOCALE_PLURAL_DELIMITER, $string->translation);
+      $source_array = $source->getPlurals();
+      $translation_array = $string->getPlurals();
       if (count($source_array) == 1) {
         // Add original string value and mark as non-plural.
         $form['strings'][$string->lid]['plural'] = array(
@@ -391,8 +396,9 @@ function locale_translate_edit_form_submit($form, &$form_state) {
   $langcode = $form_state['values']['langcode'];
   foreach ($form_state['values']['strings'] as $lid => $translations) {
     // Serialize plural variants in one string by LOCALE_PLURAL_DELIMITER.
+    $target = LocaleTranslation::loadById($langcode, $lid, array(), array('translation'));
     $translation_new = implode(LOCALE_PLURAL_DELIMITER, $translations['translations']);
-    $translation_old = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+    $translation_old = $target->translation;
     // No translation when all strings are empty.
     $has_translation = FALSE;
     foreach ($translations['translations'] as $string) {
@@ -403,33 +409,15 @@ 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_old) && $translation_old != $translation_new) {
-        db_update('locales_target')
-          ->fields(array(
-            'translation' => $translation_new,
-            'customized' => LOCALE_CUSTOMIZED,
-          ))
-          ->condition('lid', $lid)
-          ->condition('language', $langcode)
-          ->execute();
-      }
-      if (empty($translation_old)) {
-        db_insert('locales_target')
-          ->fields(array(
-            'lid' => $lid,
-            'translation' => $translation_new,
-            'language' => $langcode,
-            'customized' => LOCALE_CUSTOMIZED,
-          ))
-          ->execute();
-      }
+      $target->setValues(array(
+        'translation' => $translation_new,
+        'customized' => LOCALE_CUSTOMIZED
+      ))
+      ->save();
     }
     elseif (!empty($translation_old)) {
       // Empty translation entered: remove existing entry from database.
-      db_delete('locales_target')
-        ->condition('lid', $lid)
-        ->condition('language', $langcode)
-        ->execute();
+      $target->delete();
     }
 
   }
