diff --git a/config/install/l10n_client.settings.yml b/config/install/l10n_client.settings.yml
new file mode 100644
index 0000000..ff07afd
--- /dev/null
+++ b/config/install/l10n_client.settings.yml
@@ -0,0 +1,3 @@
+l10n_client_disabled_paths: ''
+l10n_client_use_server: false
+l10n_client_server: 'http://localize.drupal.org'
diff --git a/config/schema/l10n_client.schema.yml b/config/schema/l10n_client.schema.yml
new file mode 100644
index 0000000..25e874d
--- /dev/null
+++ b/config/schema/l10n_client.schema.yml
@@ -0,0 +1,10 @@
+l10n_client.settings:
+  type: mapping
+  label: Settings
+  mapping:
+    l10n_client_disabled_paths:
+      type: string
+    l10n_client_use_server:
+      type: boolean
+    l10n_client_server:
+      type: string
diff --git a/l10n_client.admin.inc b/l10n_client.admin.inc
index 899e6f6..a036c10 100644
--- a/l10n_client.admin.inc
+++ b/l10n_client.admin.inc
@@ -16,18 +16,18 @@ function l10n_client_settings_form() {
     '#title'         => t('Disable on-page translation on the following system paths'),
     '#type'          => 'textarea',
     '#description'   => t('One per line. Wildcard-enabled. Examples: system/ajax, admin*'),
-    '#default_value' => variable_get('l10n_client_disabled_paths', ''),
+    '#default_value' => \Drupal::config('l10n_client.settings')->get('l10n_client_disabled_paths'),
   );
   $form['l10n_client_use_server'] = array(
     '#title'         => t('Enable sharing translations with server'),
     '#type'          => 'checkbox',
-    '#default_value' => variable_get('l10n_client_use_server', FALSE),
+    '#default_value' => \Drupal::config('l10n_client.settings')->get('l10n_client_use_server'),
   );
   $form['l10n_client_server'] = array(
     '#title'         => t('Address of localization server to use'),
     '#type'          => 'textfield',
     '#description'   => t('Each translation submission will also be submitted to this server. We suggest you enter <a href="@localize">http://localize.drupal.org</a> to share with the greater Drupal community. Make sure you set up an API-key in the user account settings for each user that will participate in the translations.', array('@localize' => 'http://localize.drupal.org')),
-    '#default_value' => variable_get('l10n_client_server', 'http://localize.drupal.org'),
+    '#default_value' => \Drupal::config('l10n_client.settings')->get('l10n_client_server'),
   );
   return system_settings_form($form);
 }
diff --git a/l10n_client.info.yml b/l10n_client.info.yml
new file mode 100644
index 0000000..4dc00f8
--- /dev/null
+++ b/l10n_client.info.yml
@@ -0,0 +1,7 @@
+name: 'Localization client'
+description: 'Provides on-page localization'
+dependencies:
+  - locale
+core: 8.x
+package: Multilingual
+type: module
diff --git a/l10n_client.install b/l10n_client.install
index 7516ffc..30d2e1f 100644
--- a/l10n_client.install
+++ b/l10n_client.install
@@ -9,12 +9,12 @@
  * Update for new l10n_client_server default value.
  */
 function l10n_client_update_7001() {
-  $server_setting = variable_get('l10n_client_server', NULL);
-  if (!variable_get('l10n_client_use_server', FALSE) && empty($server_setting) && !is_null($server_setting)) {
+  $server_setting = \Drupal::config('l10n_client.settings')->get('l10n_client_server');
+  if (!\Drupal::config('l10n_client.settings')->get('l10n_client_use_server') && empty($server_setting) && !is_null($server_setting)) {
     // User saved empty translation server setting, and has sharing disabled now.
     // Drop the setting to fall back on the new localize.drupal.org default.
     // User will get new default when she enables sharing.
-    variable_del('l10n_client_server');
+    \Drupal::config('l10n_client.settings')->clear('l10n_client_server')->save();
   }
 }
 
@@ -30,6 +30,6 @@ function l10n_client_update_7002() {
  * Implements hook_uninstall().
  */
 function l10n_client_uninstall() {
-  variable_del('l10n_client_server');
-  variable_del('l10n_client_use_server');
+  \Drupal::config('l10n_client.settings')->clear('l10n_client_server')->save();
+  \Drupal::config('l10n_client.settings')->clear('l10n_client_use_server')->save();
 }
diff --git a/l10n_client.links.menu.yml b/l10n_client.links.menu.yml
new file mode 100644
index 0000000..49d7954
--- /dev/null
+++ b/l10n_client.links.menu.yml
@@ -0,0 +1,3 @@
+l10n_client.translate_page:
+  route_name: l10n_client.translate_page
+  title: 'Translate strings'
diff --git a/l10n_client.links.task.yml b/l10n_client.links.task.yml
new file mode 100644
index 0000000..138e550
--- /dev/null
+++ b/l10n_client.links.task.yml
@@ -0,0 +1,9 @@
+l10n_client.translate_page:
+  route_name: l10n_client.translate_page
+  title: Untranslated
+  weight: -10
+  base_route: l10n_client.translate_page
+l10n_client.settings_form:
+  route_name: l10n_client.settings_form
+  title: Client
+  weight: 40
diff --git a/l10n_client.module b/l10n_client.module
index 3e34050..ca986b2 100644
--- a/l10n_client.module
+++ b/l10n_client.module
@@ -98,27 +98,67 @@ function l10n_client_init() {
     // That would go against our goal of displaying only strings used on the page
     // and would hang browsers. Drops any string used for the page before this point.
     drupal_static_reset('locale');
-    drupal_add_css(drupal_get_path('module', 'l10n_client') .'/l10n_client.css');
-    drupal_add_js(drupal_get_path('module', 'l10n_client') .'/jquery.hotkeys.js');
-    drupal_add_js('misc/jquery.cookie.js');
-    drupal_add_js(drupal_get_path('module', 'l10n_client') .'/l10n_client.js');
+    // @FIXME
+// The Assets API has totally changed. CSS, JavaScript, and libraries are now
+// attached directly to render arrays using the #attached property.
+// 
+// 
+// @see https://www.drupal.org/node/2169605
+// @see https://www.drupal.org/node/2408597
+// drupal_add_css(drupal_get_path('module', 'l10n_client') .'/l10n_client.css');
+
+    // @FIXME
+// The Assets API has totally changed. CSS, JavaScript, and libraries are now
+// attached directly to render arrays using the #attached property.
+// 
+// 
+// @see https://www.drupal.org/node/2169605
+// @see https://www.drupal.org/node/2408597
+// drupal_add_js(drupal_get_path('module', 'l10n_client') .'/jquery.hotkeys.js');
+
+    // @FIXME
+// The Assets API has totally changed. CSS, JavaScript, and libraries are now
+// attached directly to render arrays using the #attached property.
+// 
+// 
+// @see https://www.drupal.org/node/2169605
+// @see https://www.drupal.org/node/2408597
+// drupal_add_js('misc/jquery.cookie.js');
+
+    // @FIXME
+// The Assets API has totally changed. CSS, JavaScript, and libraries are now
+// attached directly to render arrays using the #attached property.
+// 
+// 
+// @see https://www.drupal.org/node/2169605
+// @see https://www.drupal.org/node/2408597
+// drupal_add_js(drupal_get_path('module', 'l10n_client') .'/l10n_client.js');
+
     // We use textareas to be able to edit long text, which need resizing.
-    drupal_add_js('misc/textarea.js');
+    // @FIXME
+// The Assets API has totally changed. CSS, JavaScript, and libraries are now
+// attached directly to render arrays using the #attached property.
+// 
+// 
+// @see https://www.drupal.org/node/2169605
+// @see https://www.drupal.org/node/2408597
+// drupal_add_js('misc/textarea.js');
+
   }
 }
 
 /**
  * Detects whether a user can access l10n_client.
  */
-function l10n_client_access($account = NULL) {
+function l10n_client_access(Drupal\Core\Session\AccountInterface $account = NULL) {
   if (!isset($account)) {
-    global $user;
+    $user = \Drupal::currentUser();
     $account = $user;
   }
   $user_enabled = empty($account->data['l10n_client_disabled']);
-  $user_access = user_access('use on-page translation', $account);
-  $paths = variable_get('l10n_client_disabled_paths', '');
-  $paths_disabled = !empty($paths) && drupal_match_path(current_path(), $paths);
+  $user_access = $account->hasPermission('use on-page translation');
+  $paths = \Drupal::config('l10n_client.settings')->get('l10n_client_disabled_paths');
+  $paths_disabled = !empty($paths) && drupal_match_path(\Drupal\Core\Url::fromRoute("<current>")->toString(), $paths);
   return $user_access && $user_enabled && !$paths_disabled;
 }
 
@@ -141,7 +181,7 @@ function l10n_client_access($account = NULL) {
  *   Boolean indicating whether translation of strings via the l10n_client UI is allowed.
  */
 function l10n_client_translate_page($display_translated = FALSE, $textgroup = 'default', $allow_translation = TRUE) {
-  global $language;
+  $language = \Drupal::languageManager()->getCurrentLanguage();
 
   $table = array();
 
@@ -184,8 +224,8 @@ function l10n_client_translate_page($display_translated = FALSE, $textgroup = 'd
       }
     }
   }
-  $pager = theme('pager');
-  return $pager . theme('table', array('header' => $header, 'rows' => $table, 'empty' => t('No strings found to translate.'))) . $pager;
+  $pager = _theme('pager');
+  return $pager . _theme('table', array('header' => $header, 'rows' => $table, 'empty' => t('No strings found to translate.'))) . $pager;
 }
 
 /**
@@ -212,10 +252,10 @@ function l10n_client_page_alter(&$page) {
     // Include string selector on page.
     $string_list = _l10n_client_string_list($l10n_strings);
     // Include editing form on page.
-    $form = drupal_get_form('l10n_client_form', $l10n_strings);
+    $form = \Drupal::formBuilder()->getForm('l10n_client_form', $l10n_strings);
     $l10n_form = drupal_render($form);
     // Include search form on page.
-    $form = drupal_get_form('l10n_client_search_form');
+    $form = \Drupal::formBuilder()->getForm('l10n_client_search_form');
     $l10n_search = drupal_render($form);
     // Generate HTML wrapper with strings data.
     $l10n_dom = _l10n_client_dom_strings($l10n_strings);
@@ -287,7 +327,7 @@ function l10_client_add_string_to_page($source = NULL, $translation = NULL, $tex
  *   - The strings stored by the locale function (not for for this module's own pages).
  */
 function _l10n_client_page_strings() {
-  global $language;
+  $language = \Drupal::languageManager()->getCurrentLanguage();
 
   // Get the page strings stored by this or other modules.
   $strings = l10_client_add_string_to_page();
@@ -360,11 +400,11 @@ function _l10n_client_string_list($strings) {
       // of the HTML tags. Truncate first, so we do not truncate in
       // the middle of an already escaped HTML tag, thus possibly
       // breaking the page.
-      $string = htmlspecialchars(truncate_utf8($original, 78, TRUE, TRUE), ENT_NOQUOTES, 'UTF-8');
+      $string = htmlspecialchars(\Drupal\Component\Utility\Unicode::truncate($original, 78, TRUE, TRUE), ENT_NOQUOTES, 'UTF-8');
     }
     else {
       // Truncate and add ellipsis if too long.
-      $string = truncate_utf8($string, 78, TRUE, TRUE);
+      $string = \Drupal\Component\Utility\Unicode::truncate($string, 78, TRUE, TRUE);
     }
 
     $select_list[] = "<li class='$str_class'>$string</li>";
@@ -381,11 +421,14 @@ function _l10n_client_string_list($strings) {
  *   This form has nothing to do with different plural versions yet.
  */
 function l10n_client_form($form_id, $strings) {
-  global $language;
+  $language = \Drupal::languageManager()->getCurrentLanguage();
 
   // Selector and editing form.
   $form = array();
-  $form['#action'] = url('l10n_client/save');
+  // @FIXME
+// url() expects a route name or an external URI.
+// $form['#action'] = url('l10n_client/save');
+
 
   $form['target'] = array(
     '#title' => t('Translation to %language', array('%language' => $language->native)),
@@ -430,7 +473,7 @@ function l10n_client_form($form_id, $strings) {
  * Search form for string list
  */
 function l10n_client_search_form() {
-  global $language;
+  $language = \Drupal::languageManager()->getCurrentLanguage();
   // Selector and editing form.
   $form = array();
   $form['search'] = array(
@@ -466,41 +509,47 @@ function l10n_client_save_string() {
         _locale_invalidate_js($language->language);
 
         if (!empty($report['skips'])) {
-          $message = theme('l10n_client_message', array('message' => t('Not saved locally due to invalid HTML content.')));
+          $message = _theme('l10n_client_message', array('message' => t('Not saved locally due to invalid HTML content.')));
         }
         elseif (!empty($report['additions']) || !empty($report['updates'])) {
-          $message = theme('l10n_client_message', array('message' => t('Translation saved locally.'), 'level' => WATCHDOG_INFO));
+          $message = _theme('l10n_client_message', array('message' => t('Translation saved locally.'), 'level' => WATCHDOG_INFO));
         }
         elseif (!empty($report['deletes'])) {
-          $message = theme('l10n_client_message', array('message' => t('Translation successfuly removed locally.'), 'level' => WATCHDOG_INFO));
+          $message = _theme('l10n_client_message', array('message' => t('Translation successfuly removed locally.'), 'level' => WATCHDOG_INFO));
         }
         else {
-          $message = theme('l10n_client_message', array('message' => t('Unknown error while saving translation locally.')));
+          $message = _theme('l10n_client_message', array('message' => t('Unknown error while saving translation locally.')));
         }
 
         // Submit to remote server if enabled.
-        if (empty($report['skips']) && variable_get('l10n_client_use_server', FALSE) && user_access('submit translations to localization server') && ($_POST['textgroup'] == 'default')) {
+        if (empty($report['skips']) && \Drupal::config('l10n_client.settings')->get('l10n_client_use_server') && \Drupal::currentUser()->hasPermission('submit translations to localization server') && ($_POST['textgroup'] == 'default')) {
           if (!empty($user->data['l10n_client_key'])) {
             $remote_result = l10n_client_submit_translation($language->language, $_POST['source'], $_POST['target'], $user->data['l10n_client_key'], l10n_client_user_token($user));
-            $message .= theme('l10n_client_message', array('message' => $remote_result[1], 'level' => $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR));
+            $message .= _theme('l10n_client_message', array('message' => $remote_result[1], 'level' => $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR));
           }
           else {
-            $server_url = variable_get('l10n_client_server', 'http://localize.drupal.org');
-            $user_edit_url = url('user/'. $user->uid .'/edit', array('absolute' => TRUE));
-            $message .= theme('l10n_client_message', array('message' => t('You could share your work with !l10n_server if you set your API key at !user_link.', array('!l10n_server' => l($server_url, $server_url), '!user_link' => l($user_edit_url, 'user/'. $user->uid .'/edit'))), 'level' => WATCHDOG_WARNING));
+            $server_url = \Drupal::config('l10n_client.settings')->get('l10n_client_server');
+            // @FIXME
+// url() expects a route name or an external URI.
+// $user_edit_url = url('user/'. $user->uid .'/edit', array('absolute' => TRUE));
+
+            // @FIXME
+// l() expects a Url object, created from a route name or external URI.
+// $message .= theme('l10n_client_message', array('message' => t('You could share your work with !l10n_server if you set your API key at !user_link.', array('!l10n_server' => l($server_url, $server_url), '!user_link' => l($user_edit_url, 'user/'. $user->uid .'/edit'))), 'level' => WATCHDOG_WARNING));
+
           }
         }
       }
       else {
-        $message = theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
+        $message = _theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
       }
     }
     else {
-      $message = theme('l10n_client_message', array('message' => t('Not saved due to missing form values.')));
+      $message = _theme('l10n_client_message', array('message' => t('Not saved due to missing form values.')));
     }
   }
   else {
-    $message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
+    $message = _theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
   }
   drupal_json_output($message);
   exit;
@@ -534,20 +583,23 @@ function l10n_client_form_user_profile_form_alter(&$form, &$form_state) {
   if ($form['#user_category'] == 'account') {
     $items = array();
     $account = $form['#user'];
-    $use_server = variable_get('l10n_client_use_server', FALSE);
-    $server_root = variable_get('l10n_client_server', 'http://localize.drupal.org');
+    $use_server = \Drupal::config('l10n_client.settings')->get('l10n_client_use_server');
+    $server_root = \Drupal::config('l10n_client.settings')->get('l10n_client_server');
 
-    if ($use_server && user_access('submit translations to localization server', $account)) {
+    if ($use_server && $account->hasPermission('submit translations to localization server')) {
       // Build link to retrieve user key.
       $server_link = $server_root . '?q=translate/remote/userkey/' . l10n_client_user_token($account);
-      $items['l10n_client_key'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Your API key for @server', array('@server' => $server_root)),
-        '#default_value' => !empty($account->data['l10n_client_key']) ? $account->data['l10n_client_key'] : '',
-        '#description' => t('This is a unique key that will allow you to send translations to the remote server. To get your API key go to !server-link.', array('!server-link' => l($server_link, $server_link))),
-      );
+      // @FIXME
+// l() expects a Url object, created from a route name or external URI.
+// $items['l10n_client_key'] = array(
+//         '#type' => 'textfield',
+//         '#title' => t('Your API key for @server', array('@server' => $server_root)),
+//         '#default_value' => !empty($account->data['l10n_client_key']) ? $account->data['l10n_client_key'] : '',
+//         '#description' => t('This is a unique key that will allow you to send translations to the remote server. To get your API key go to !server-link.', array('!server-link' => l($server_link, $server_link))),
+//       );
+
     }
-    if (user_access('use on-page translation', $account)) {
+    if ($account->hasPermission('use on-page translation')) {
       // Add an item to let the user disable the on-page tool.
       $items['l10n_client_disabled'] = array(
         '#type' => 'checkbox',
@@ -570,10 +622,16 @@ function l10n_client_form_user_profile_form_alter(&$form, &$form_state) {
 
 /**
  * Implement hook_user_presave().
- *
+ * 
  * Save the l10n_client_key value in the user object.
+ * 
+ * @FIXME
+ * The $edit and $category parameters are gone in Drupal 8. They have been left
+ * here in order to prevent 'undefined variable' errors, but they will never
+ * actually be passed to this hook. You'll need to modify this function and
+ * remove every reference to them.
  */
-function l10n_client_user_presave(&$edit, $account, $category) {
+function l10n_client_user_presave(\Drupal\user\UserInterface $account, $category, $edit = NULL) {
   if (isset($edit['l10n_client_key'])) {
     $edit['data']['l10n_client_key'] = trim($edit['l10n_client_key']);
   }
@@ -584,7 +642,7 @@ function l10n_client_user_presave(&$edit, $account, $category) {
  * Get user based semi unique token. This will ensure user keys are unique for each client.
  */
 function l10n_client_user_token($account = NULL) {
-  global $user;
+  $user = \Drupal::currentUser();
   $account = isset($account) ? $account : $user;
   return md5('l10n_client'. $account->uid . drupal_get_private_key());
 }
@@ -595,7 +653,7 @@ function l10n_client_user_token($account = NULL) {
 function l10n_client_submit_translation($langcode, $source, $translation, $user_key, $user_token) {
   $server_uid = current(explode(':', $user_key));
   $signature = md5($user_key . $langcode . $source . $translation . $user_token);
-  $server_url = variable_get('l10n_client_server', 'http://localize.drupal.org');
+  $server_url = \Drupal::config('l10n_client.settings')->get('l10n_client_server');
 
   $response = xmlrpc(
     $server_url .'/xmlrpc.php',
@@ -612,16 +670,16 @@ function l10n_client_submit_translation($langcode, $source, $translation, $user_
   if (!empty($response) && isset($response['status'])) {
     if ($response['status']) {
       $message = t('Translation sent and accepted by @server.', array('@server' => $server_url));
-      watchdog('l10n_client', 'Translation sent and accepted by @server.', array('@server' => $server_url));
+      \Drupal::logger('l10n_client')->notice('Translation sent and accepted by @server.', array('@server' => $server_url));
     } else {
       $message = t('Translation rejected by @server. Reason: %reason', array('%reason' => $response['reason'], '@server' => $server_url));
-      watchdog('l10n_client', 'Translation rejected by @server. Reason: %reason', array('%reason' => $response['reason'], '@server' => $server_url), WATCHDOG_WARNING);
+      \Drupal::logger('l10n_client')->warning('Translation rejected by @server. Reason: %reason', array('%reason' => $response['reason'], '@server' => $server_url));
     }
     return array($response['status'], $message);
   }
   else {
     $message = t('The connection with @server failed with the following error: %error_code: %error_message.', array('%error_code' => xmlrpc_errno(), '%error_message' => xmlrpc_error_msg(), '@server' => $server_url));
-    watchdog('l10n_client', 'The connection with @server failed with the following error: %error_code: %error_message.', array('%error_code' => xmlrpc_errno(), '%error_message' => xmlrpc_error_msg(), '@server' => $server_url), WATCHDOG_ERROR);
+    \Drupal::logger('l10n_client')->error('The connection with @server failed with the following error: %error_code: %error_message.', array('%error_code' => xmlrpc_errno(), '%error_message' => xmlrpc_error_msg(), '@server' => $server_url));
     return array(FALSE, $message);
   }
 }
diff --git a/l10n_client.permissions.yml b/l10n_client.permissions.yml
new file mode 100644
index 0000000..0411421
--- /dev/null
+++ b/l10n_client.permissions.yml
@@ -0,0 +1,6 @@
+'use on-page translation':
+  title: 'Use on-page translation'
+  description: 'Makes it possible to translate the Drupal interface on-page.'
+'submit translations to localization server':
+  title: 'Submit translations to localization server'
+  description: 'Allows users to submit translations to a remote localization server.'
diff --git a/l10n_client.routing.yml b/l10n_client.routing.yml
new file mode 100644
index 0000000..ffaf79b
--- /dev/null
+++ b/l10n_client.routing.yml
@@ -0,0 +1,21 @@
+l10n_client.save_string:
+  path: /l10n_client/save
+  defaults:
+    _title: 'Save string'
+    _controller: '\Drupal\l10n_client\Controller\DefaultController::l10n_client_save_string'
+  requirements:
+    _custom_access: '\Drupal\l10n_client\Controller\DefaultController::l10n_client_access'
+l10n_client.translate_page:
+  path: /locale
+  defaults:
+    _title: 'Translate strings'
+    _controller: '\Drupal\l10n_client\Controller\DefaultController::l10n_client_translate_page'
+  requirements:
+    _custom_access: '\Drupal\l10n_client\Controller\DefaultController::l10n_client_access'
+l10n_client.settings_form:
+  path: /admin/config/regional/translate/client
+  defaults:
+    _title: Client
+    _form: \Drupal\l10n_client\Form\L10nClientSettingsForm
+  requirements:
+    _permission: 'administer languages'
diff --git a/l10n_client.services.yml b/l10n_client.services.yml
new file mode 100644
index 0000000..f7a5b24
--- /dev/null
+++ b/l10n_client.services.yml
@@ -0,0 +1,6 @@
+services:
+  init_subscriber:
+    class: Drupal\l10n_client\EventSubscriber\InitSubscriber
+    tags:
+      -
+        name: event_subscriber
diff --git a/l10n_client.test b/l10n_client.test
index 64a1e3b..2c66bf6 100644
--- a/l10n_client.test
+++ b/l10n_client.test
@@ -27,8 +27,11 @@ class L10nSubmitTranslationTestCase extends DrupalWebTestCase {
    */
   function testTranslationSubmission() {
     // Enable usage of remote server and set the url to the l10n_server.
-    variable_set('l10n_client_server', url('l10n-client-test', array('absolute' => TRUE)));
-    variable_set('l10n_client_use_server', TRUE);
+    // @FIXME
+// url() expects a route name or an external URI.
+// variable_set('l10n_client_server', url('l10n-client-test', array('absolute' => TRUE)));
+
+    \Drupal::configFactory()->getEditable('l10n_client.settings')->set('l10n_client_use_server', TRUE)->save();
 
     // Enable the german language.
     locale_add_language('de');
@@ -38,51 +41,69 @@ class L10nSubmitTranslationTestCase extends DrupalWebTestCase {
       ->execute();
 
     // Enable URL detection method.
-    variable_set('language_negotiation_language', array (
-      'locale-url' => array(
-        'callbacks' => array(
-          'language' => 'locale_language_from_url',
-          'switcher' => 'locale_language_switcher_url',
-          'url_rewrite' => 'locale_language_url_rewrite_url',
-        ),
-        'file' => 'includes/locale.inc',
-      ),
-      'language-default' => array(
-        'callbacks' => array(
-          'language' => 'language_from_default',
-        ),
-      ),
-    ));
-    variable_set('language_negotiation_language_content', array(
-      'locale-interface' => array(
-        'callbacks' => array(
-          'language' => 'locale_language_from_interface',
-        ),
-        'file' => 'includes/locale.inc',
-      ),
-    ));
-    variable_set('language_negotiation_language_url', array(
-      'locale-url' => array(
-        'callbacks' => array(
-          'language' => 'locale_language_from_url',
-          'switcher' => 'locale_language_switcher_url',
-          'url_rewrite' => 'locale_language_url_rewrite_url',
-        ),
-        'file' => 'includes/locale.inc',
-      ),
-      'locale-url-fallback' => array(
-        'callbacks' => array(
-          'language' => 'locale_language_url_fallback',
-        ),
-        'file' => 'includes/locale.inc',
-      ),
-    ));
+    // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// variable_set('language_negotiation_language', array (
+//       'locale-url' => array(
+//         'callbacks' => array(
+//           'language' => 'locale_language_from_url',
+//           'switcher' => 'locale_language_switcher_url',
+//           'url_rewrite' => 'locale_language_url_rewrite_url',
+//         ),
+//         'file' => 'includes/locale.inc',
+//       ),
+//       'language-default' => array(
+//         'callbacks' => array(
+//           'language' => 'language_from_default',
+//         ),
+//       ),
+//     ));
+
+    // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// variable_set('language_negotiation_language_content', array(
+//       'locale-interface' => array(
+//         'callbacks' => array(
+//           'language' => 'locale_language_from_interface',
+//         ),
+//         'file' => 'includes/locale.inc',
+//       ),
+//     ));
+
+    // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// variable_set('language_negotiation_language_url', array(
+//       'locale-url' => array(
+//         'callbacks' => array(
+//           'language' => 'locale_language_from_url',
+//           'switcher' => 'locale_language_switcher_url',
+//           'url_rewrite' => 'locale_language_url_rewrite_url',
+//         ),
+//         'file' => 'includes/locale.inc',
+//       ),
+//       'locale-url-fallback' => array(
+//         'callbacks' => array(
+//           'language' => 'locale_language_url_fallback',
+//         ),
+//         'file' => 'includes/locale.inc',
+//       ),
+//     ));
+
 
     // Create user that is authorized to use the l10n client.
     $admin_user = $this->drupalCreateUser(array('use on-page translation', 'submit translations to localization server'));
     $this->drupalLogin($admin_user);
-    $user = user_load(2);
-    user_save($user, array('data' => array('l10n_client_key' => drupal_get_token('l10n-client-test'))));
+    $user = \Drupal::entityManager()->getStorage('user')->load(2);
+    // @FIXME
+// user_save() is now a method of the user entity.
+// user_save($user, array('data' => array('l10n_client_key' => drupal_get_token('l10n-client-test'))));
+
 
     // Open the site in german to load the localization client form.
     $this->drupalGet('de');
@@ -102,7 +123,10 @@ class L10nSubmitTranslationTestCase extends DrupalWebTestCase {
     );
 
     // Execute the submission using curl.
-    $action = url('l10n_client/save', array('absolute' => TRUE));
+    // @FIXME
+// url() expects a route name or an external URI.
+// $action = url('l10n_client/save', array('absolute' => TRUE));
+
     $response = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post));
     $this->refreshVariables();
 
@@ -123,7 +147,11 @@ class L10nSubmitTranslationTestCase extends DrupalWebTestCase {
     // Check response of the server.
     $this->assertTrue(strpos($response, 'Translation sent and accepted by') > 0, 'Translation sent and accepted by the server.');
     // Get returned data that is mocked in the l10n_client_test module.
-    $saved = variable_get('l10n_client_test_mock_request');
+    // @FIXME
+// Could not extract the default value because it is either indeterminate, or
+// not scalar. You'll need to provide a default value in
+// config/install/l10n_client.settings.yml and config/schema/l10n_client.schema.yml.
+$saved = \Drupal::config('l10n_client.settings')->get('l10n_client_test_mock_request');
 
     $saved_xml = new SimpleXMLElement($saved);
     // Assert basic structure of the saved data.
diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php
new file mode 100644
index 0000000..e7731a3
--- /dev/null
+++ b/src/Controller/DefaultController.php
@@ -0,0 +1,171 @@
+<?php /**
+ * @file
+ * Contains \Drupal\l10n_client\Controller\DefaultController.
+ */
+
+namespace Drupal\l10n_client\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Default controller for the l10n_client module.
+ */
+class DefaultController extends ControllerBase {
+
+  public function l10n_client_access(Drupal\Core\Session\AccountInterface $account = NULL) {
+    if (!isset($account)) {
+      $user = \Drupal::currentUser();
+      $account = $user;
+    }
+    $user_enabled = empty($account->data['l10n_client_disabled']);
+    $user_access = $account->hasPermission('use on-page translation');
+    $paths = \Drupal::config('l10n_client.settings')->get('l10n_client_disabled_paths');
+    $paths_disabled = !empty($paths) && drupal_match_path(\Drupal\Core\Url::fromRoute("<current>")->toString(), $paths);
+    return $user_access && $user_enabled && !$paths_disabled;
+  }
+
+  public function l10n_client_save_string() {
+    global $user, $language;
+
+    if (l10n_client_access()) {
+      if (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['textgroup']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
+        // Ensure we have this source string before we attempt to save it.
+        $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", [
+          ':source' => $_POST['source'],
+          ':context' => $_POST['context'],
+          ':textgroup' => $_POST['textgroup'],
+        ])->fetchField();
+
+        if (!empty($lid)) {
+          include_once 'includes/locale.inc';
+          $report = [
+            'skips' => 0,
+            'additions' => 0,
+            'updates' => 0,
+            'deletes' => 0,
+          ];
+          _locale_import_one_string_db($report, $language->language, $_POST['context'], $_POST['source'], $_POST['target'], $_POST['textgroup'], NULL, LOCALE_IMPORT_OVERWRITE);
+          cache_clear_all('locale:', 'cache', TRUE);
+          _locale_invalidate_js($language->language);
+
+          if (!empty($report['skips'])) {
+            $message = _theme('l10n_client_message', [
+              'message' => t('Not saved locally due to invalid HTML content.')
+              ]);
+          }
+          elseif (!empty($report['additions']) || !empty($report['updates'])) {
+            $message = _theme('l10n_client_message', [
+              'message' => t('Translation saved locally.'),
+              'level' => WATCHDOG_INFO,
+            ]);
+          }
+          elseif (!empty($report['deletes'])) {
+            $message = _theme('l10n_client_message', [
+              'message' => t('Translation successfuly removed locally.'),
+              'level' => WATCHDOG_INFO,
+            ]);
+          }
+          else {
+            $message = _theme('l10n_client_message', [
+              'message' => t('Unknown error while saving translation locally.')
+              ]);
+          }
+
+          // Submit to remote server if enabled.
+          if (empty($report['skips']) && \Drupal::config('l10n_client.settings')->get('l10n_client_use_server') && \Drupal::currentUser()->hasPermission('submit translations to localization server') && ($_POST['textgroup'] == 'default')) {
+            if (!empty($user->data['l10n_client_key'])) {
+              $remote_result = l10n_client_submit_translation($language->language, $_POST['source'], $_POST['target'], $user->data['l10n_client_key'], l10n_client_user_token($user));
+              $message .= _theme('l10n_client_message', [
+                'message' => $remote_result[1],
+                'level' => $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR,
+              ]);
+            }
+            else {
+              $server_url = \Drupal::config('l10n_client.settings')->get('l10n_client_server');
+              // @FIXME
+              // url() expects a route name or an external URI.
+              // $user_edit_url = url('user/'. $user->uid .'/edit', array('absolute' => TRUE));
+
+              // @FIXME
+              // l() expects a Url object, created from a route name or external URI.
+              // $message .= theme('l10n_client_message', array('message' => t('You could share your work with !l10n_server if you set your API key at !user_link.', array('!l10n_server' => l($server_url, $server_url), '!user_link' => l($user_edit_url, 'user/'. $user->uid .'/edit'))), 'level' => WATCHDOG_WARNING));
+
+            }
+          }
+        }
+        else {
+          $message = _theme('l10n_client_message', [
+            'message' => t('Not saved due to source string missing.')
+            ]);
+        }
+      }
+      else {
+        $message = _theme('l10n_client_message', [
+          'message' => t('Not saved due to missing form values.')
+          ]);
+      }
+    }
+    else {
+      $message = _theme('l10n_client_message', [
+        'message' => t('Not saved due to insufficient permissions.')
+        ]);
+    }
+    drupal_json_output($message);
+    exit;
+  }
+
+  public function l10n_client_translate_page($display_translated = FALSE, $textgroup = 'default', $allow_translation = TRUE) {
+    $language = \Drupal::languageManager()->getCurrentLanguage();
+
+    $table = [];
+
+    $query = db_select('locales_source', 's')->extend('PagerDefault');
+    $query->leftJoin('locales_target', 't', "s.lid = t.lid AND t.language = :lang", [
+      ':lang' => $language->language
+      ]);
+    $query->fields('s', ['source'])
+      ->fields('t', ['translation', 'language'])
+      ->orderBy('s.source');
+
+    if ($display_translated) {
+      $header = [t('Source string'), t('Translation')];
+      $query->condition('t.translation', '', '!=');
+    }
+    else {
+      $header = [t('Source string')];
+      $query->isNull('t.lid');
+    }
+    if (!empty($textgroup)) {
+      $query->condition('s.textgroup', $textgroup);
+    }
+
+    // For the 'default' textgroup and English language we don't allow translation.
+    $allow_translation = (($textgroup == 'default') && ($language->language == 'en')) ? FALSE : $allow_translation;
+
+    $result = $query
+      ->limit(L10N_CLIENT_STRINGS)
+      ->execute();
+
+    foreach ($result as $data) {
+      if ($display_translated) {
+        $table[] = [check_plain($data->source), check_plain($data->translation)];
+        if ($allow_translation) {
+          l10_client_add_string_to_page($data->source, $data->translation, $textgroup);
+        }
+      }
+      else {
+        $table[] = [check_plain($data->source)];
+        if ($allow_translation) {
+          l10_client_add_string_to_page($data->source, TRUE, $textgroup);
+        }
+      }
+    }
+    $pager = _theme('pager');
+    return $pager . _theme('table', [
+      'header' => $header,
+      'rows' => $table,
+      'empty' => t('No strings found to translate.'),
+    ]) . $pager;
+  }
+
+}
diff --git a/src/EventSubscriber/InitSubscriber.php b/src/EventSubscriber/InitSubscriber.php
new file mode 100644
index 0000000..ab0bf10
--- /dev/null
+++ b/src/EventSubscriber/InitSubscriber.php
@@ -0,0 +1,81 @@
+<?php /**
+ * @file
+ * Contains \Drupal\l10n_client\EventSubscriber\InitSubscriber.
+ */
+
+namespace Drupal\l10n_client\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class InitSubscriber implements EventSubscriberInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    return [KernelEvents::REQUEST => ['onEvent', 0]];
+  }
+
+  public function onEvent() {
+    global $conf, $language;
+
+    if (l10n_client_access()) {
+      // Turn off the short string cache *in this request*, so we will
+    // have an accurate picture of strings used to assemble the page.
+      $conf['locale_cache_strings'] = 0;
+      // Reset locale cache. If any hook_init() implementation was invoked before
+      // this point, that would normally result in all strings loaded into memory.
+      // That would go against our goal of displaying only strings used on the page
+      // and would hang browsers. Drops any string used for the page before this point.
+      drupal_static_reset('locale');
+      // @FIXME
+      // The Assets API has totally changed. CSS, JavaScript, and libraries are now
+      // attached directly to render arrays using the #attached property.
+      // 
+      // 
+      // @see https://www.drupal.org/node/2169605
+      // @see https://www.drupal.org/node/2408597
+      // drupal_add_css(drupal_get_path('module', 'l10n_client') .'/l10n_client.css');
+
+      // @FIXME
+      // The Assets API has totally changed. CSS, JavaScript, and libraries are now
+      // attached directly to render arrays using the #attached property.
+      // 
+      // 
+      // @see https://www.drupal.org/node/2169605
+      // @see https://www.drupal.org/node/2408597
+      // drupal_add_js(drupal_get_path('module', 'l10n_client') .'/jquery.hotkeys.js');
+
+      // @FIXME
+      // The Assets API has totally changed. CSS, JavaScript, and libraries are now
+      // attached directly to render arrays using the #attached property.
+      // 
+      // 
+      // @see https://www.drupal.org/node/2169605
+      // @see https://www.drupal.org/node/2408597
+      // drupal_add_js('misc/jquery.cookie.js');
+
+      // @FIXME
+      // The Assets API has totally changed. CSS, JavaScript, and libraries are now
+      // attached directly to render arrays using the #attached property.
+      // 
+      // 
+      // @see https://www.drupal.org/node/2169605
+      // @see https://www.drupal.org/node/2408597
+      // drupal_add_js(drupal_get_path('module', 'l10n_client') .'/l10n_client.js');
+
+      // We use textareas to be able to edit long text, which need resizing.
+      // @FIXME
+      // The Assets API has totally changed. CSS, JavaScript, and libraries are now
+      // attached directly to render arrays using the #attached property.
+      // 
+      // 
+      // @see https://www.drupal.org/node/2169605
+      // @see https://www.drupal.org/node/2408597
+      // drupal_add_js('misc/textarea.js');
+
+    }
+  }
+
+}
diff --git a/src/Form/L10nClientSettingsForm.php b/src/Form/L10nClientSettingsForm.php
new file mode 100644
index 0000000..b800cc2
--- /dev/null
+++ b/src/Form/L10nClientSettingsForm.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\l10n_client\Form\L10nClientSettingsForm.
+ */
+
+namespace Drupal\l10n_client\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+class L10nClientSettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'l10n_client_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $config = $this->config('l10n_client.settings');
+
+    foreach (Element::children($form) as $variable) {
+      $config->set($variable, $form_state->getValue($form[$variable]['#parents']));
+    }
+    $config->save();
+
+    if (method_exists($this, '_submitForm')) {
+      $this->_submitForm($form, $form_state);
+    }
+
+    parent::submitForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return ['l10n_client.settings'];
+  }
+
+  public function buildForm(array $form, \Drupal\Core\Form\FormStateInterface $form_state) {
+    $form = [];
+    $form['l10n_client_disabled_paths'] = [
+      '#title' => t('Disable on-page translation on the following system paths'),
+      '#type' => 'textarea',
+      '#description' => t('One per line. Wildcard-enabled. Examples: system/ajax, admin*'),
+      '#default_value' => \Drupal::config('l10n_client.settings')->get('l10n_client_disabled_paths'),
+    ];
+    $form['l10n_client_use_server'] = [
+      '#title' => t('Enable sharing translations with server'),
+      '#type' => 'checkbox',
+      '#default_value' => \Drupal::config('l10n_client.settings')->get('l10n_client_use_server'),
+    ];
+    $form['l10n_client_server'] = [
+      '#title' => t('Address of localization server to use'),
+      '#type' => 'textfield',
+      '#description' => t('Each translation submission will also be submitted to this server. We suggest you enter <a href="@localize">http://localize.drupal.org</a> to share with the greater Drupal community. Make sure you set up an API-key in the user account settings for each user that will participate in the translations.', [
+        '@localize' => 'http://localize.drupal.org'
+        ]),
+      '#default_value' => \Drupal::config('l10n_client.settings')->get('l10n_client_server'),
+    ];
+    return parent::buildForm($form, $form_state);
+  }
+
+  public function validateForm(array &$form, \Drupal\Core\Form\FormStateInterface &$form_state) {
+    if ($form_state->getValue(['l10n_client_use_server'])) {
+
+      if (!empty($form_state->getValue(['l10n_client_server']))) {
+        // Try to invoke the remote string submission with a test request.
+        $response = xmlrpc($form_state->getValue(['l10n_client_server']) . '/xmlrpc.php', [
+          'l10n.server.test' => [
+            '2.0'
+            ]
+          ]);
+        if ($response && !empty($response['name']) && !empty($response['version'])) {
+          if (empty($response['supported']) || !$response['supported']) {
+            $form_state->setErrorByName('l10n_client_server', t('The given server could not handle the v2.0 remote submission API.'));
+          }
+          else {
+            drupal_set_message(t('Verified that the specified server can handle remote string submissions. Supported languages: %languages.', [
+              '%languages' => $response['languages']
+              ]));
+          }
+        }
+        else {
+          $form_state->setErrorByName('l10n_client_server', t('Invalid localization server address specified. Make sure you specified the right server address.'));
+        }
+      }
+      else {
+        $form_state->setErrorByName('l10n_client_server', t('You should provide a server address, such as http://localize.drupal.org'));
+      }
+    }
+  }
+
+}
diff --git a/src/Tests/L10nSubmitTranslationTestCase.php b/src/Tests/L10nSubmitTranslationTestCase.php
new file mode 100644
index 0000000..2d4f4fc
--- /dev/null
+++ b/src/Tests/L10nSubmitTranslationTestCase.php
@@ -0,0 +1,174 @@
+<?php
+namespace Drupal\l10n_client\Tests;
+
+/**
+ * Test the submission of translation suggestions.
+ *
+ * @group l10n_client
+ */
+class L10nSubmitTranslationTestCase extends \Drupal\simpletest\WebTestBase {
+
+  protected $profile = 'standard';
+
+  public static function getInfo() {
+    return [
+      'name' => 'localization client test',
+      'description' => 'Test the submission of translation suggestions.',
+      'group' => 'Localization client',
+    ];
+  }
+
+  protected function setUp() {
+    parent::setUp('l10n_client', 'l10n_client_test');
+  }
+
+  public /**
+   * Test if submitted translations are saved correctly.
+   */
+  function testTranslationSubmission() {
+    // Enable usage of remote server and set the url to the l10n_server.
+    // @FIXME
+// url() expects a route name or an external URI.
+// variable_set('l10n_client_server', url('l10n-client-test', array('absolute' => TRUE)));
+
+    \Drupal::configFactory()->getEditable('l10n_client.settings')->set('l10n_client_use_server', TRUE)->save();
+
+    // Enable the german language.
+    locale_add_language('de');
+    db_update('languages')
+      ->fields(['plurals' => 2])
+      ->condition('language', 'de')
+      ->execute();
+
+    // Enable URL detection method.
+    // @FIXME
+    // // @FIXME
+    // // This looks like another module's variable. You'll need to rewrite this call
+    // // to ensure that it uses the correct configuration object.
+    // variable_set('language_negotiation_language', array (
+    //       'locale-url' => array(
+    //         'callbacks' => array(
+    //           'language' => 'locale_language_from_url',
+    //           'switcher' => 'locale_language_switcher_url',
+    //           'url_rewrite' => 'locale_language_url_rewrite_url',
+    //         ),
+    //         'file' => 'includes/locale.inc',
+    //       ),
+    //       'language-default' => array(
+    //         'callbacks' => array(
+    //           'language' => 'language_from_default',
+    //         ),
+    //       ),
+    //     ));
+
+    // @FIXME
+    // // @FIXME
+    // // This looks like another module's variable. You'll need to rewrite this call
+    // // to ensure that it uses the correct configuration object.
+    // variable_set('language_negotiation_language_content', array(
+    //       'locale-interface' => array(
+    //         'callbacks' => array(
+    //           'language' => 'locale_language_from_interface',
+    //         ),
+    //         'file' => 'includes/locale.inc',
+    //       ),
+    //     ));
+
+    // @FIXME
+    // // @FIXME
+    // // This looks like another module's variable. You'll need to rewrite this call
+    // // to ensure that it uses the correct configuration object.
+    // variable_set('language_negotiation_language_url', array(
+    //       'locale-url' => array(
+    //         'callbacks' => array(
+    //           'language' => 'locale_language_from_url',
+    //           'switcher' => 'locale_language_switcher_url',
+    //           'url_rewrite' => 'locale_language_url_rewrite_url',
+    //         ),
+    //         'file' => 'includes/locale.inc',
+    //       ),
+    //       'locale-url-fallback' => array(
+    //         'callbacks' => array(
+    //           'language' => 'locale_language_url_fallback',
+    //         ),
+    //         'file' => 'includes/locale.inc',
+    //       ),
+    //     ));
+
+
+    // Create user that is authorized to use the l10n client.
+    $admin_user = $this->drupalCreateUser([
+      'use on-page translation',
+      'submit translations to localization server',
+    ]);
+    $this->drupalLogin($admin_user);
+    $user = \Drupal::entityManager()->getStorage('user')->load(2);
+    // @FIXME
+    // user_save() is now a method of the user entity.
+    // user_save($user, array('data' => array('l10n_client_key' => drupal_get_token('l10n-client-test'))));
+
+
+    // Open the site in german to load the localization client form.
+    $this->drupalGet('de');
+    // Parse the form token.
+    $token_input = $this->xpath("//form[@id='l10n-client-form']//input[@name='form_token']");
+    $form_token = (string) $token_input[0]['value'];
+
+    // Post data that will be sent to the server.
+    $post = [
+      // Source string that gets translated.
+      'source' => 'Content',
+      // Translation suggestion.
+      'target' => 'Translation suggestion for Content',
+      'textgroup' => 'default',
+      'context' => '',
+      'form_token' => $form_token,
+    ];
+
+    // Execute the submission using curl.
+    // @FIXME
+    // url() expects a route name or an external URI.
+    // $action = url('l10n_client/save', array('absolute' => TRUE));
+
+    $response = $this->curlExec([
+      CURLOPT_URL => $action,
+      CURLOPT_POST => TRUE,
+      CURLOPT_POSTFIELDS => $post,
+    ]);
+    $this->refreshVariables();
+
+    // Get id of the source string.
+    $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", [
+      ':source' => $post['source'],
+      ':context' => $post['context'],
+      ':textgroup' => $post['textgroup'],
+    ])
+      ->fetchField();
+    // Get the saved translation.
+    $translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid", [
+      ':lid' => $lid
+      ])
+      ->fetchField();
+    // Check if translation was saved successfully saved in local DB.
+    $this->assertEqual($translation, $post['target'], 'Translation was successfully saved in local DB.');
+
+    // Check response of the server.
+    $this->assertTrue(strpos($response, 'Translation sent and accepted by') > 0, 'Translation sent and accepted by the server.');
+    // Get returned data that is mocked in the l10n_client_test module.
+    // @FIXME
+    // Could not extract the default value because it is either indeterminate, or
+    // not scalar. You'll need to provide a default value in
+    // config/install/l10n_client.settings.yml and config/schema/l10n_client.schema.yml.
+    $saved = \Drupal::config('l10n_client.settings')->get('l10n_client_test_mock_request');
+
+    $saved_xml = new SimpleXMLElement($saved);
+    // Assert basic structure of the saved data.
+    $this->assertEqual((string) $saved_xml->methodName, 'l10n.submit.translation', 'Right methodname was returned.');
+    $this->assertEqual(count($saved_xml->params->param), 6, 'Response XML contains right amount of parameters.');
+    // Assert values in saved data.
+    $this->assertEqual((string) $saved_xml->params[0]->param[0]->value->string, 'en', 'Source language parameter is correct.');
+    $this->assertEqual((string) $saved_xml->params[0]->param[1]->value->string, $post['source'], 'Source string parameter is correct.');
+    $this->assertEqual((string) $saved_xml->params[0]->param[2]->value->string, $post['target'], 'Suggestion string parameter is correct.');
+  }
+
+}
