diff --git a/core/modules/language/js/language.switcher.js b/core/modules/language/js/language.switcher.js
new file mode 100644
index 0000000..e03da3a
--- /dev/null
+++ b/core/modules/language/js/language.switcher.js
@@ -0,0 +1,25 @@
+/**
+ * @file
+ * Attaches behaviors for the Language switch block.
+ */
+(function ($, Drupal, drupalSettings) {
+
+  "use strict";
+
+  Drupal.behaviors.language_switcher = {
+    // @todo: We need to add the active class to the active language.
+    attach: function (context) {
+      if (!drupalSettings.path.isFront) {
+        var $context = $(context);
+        // Set the destination parameter on each of the contextual links.
+        var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath);
+        $context.find('.language-switcher a').each(function () {
+          var url = this.getAttribute('href');
+          var glue = (url.indexOf('?') === -1) ? '?' : '&';
+          this.setAttribute('href', url + glue + destination);
+        });
+      }
+    }
+  }
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/language/language.libraries.yml b/core/modules/language/language.libraries.yml
index 0c4cc8c..75c4c86 100644
--- a/core/modules/language/language.libraries.yml
+++ b/core/modules/language/language.libraries.yml
@@ -9,3 +9,12 @@ drupal.language.admin:
     - core/jquery
     - core/drupal
     - core/jquery.once
+
+drupal.language.switcher:
+  version: VERSION
+  js:
+    js/language.switcher.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupalSettings
diff --git a/core/modules/language/language.routing.yml b/core/modules/language/language.routing.yml
index 84740ee..ec73a8f 100644
--- a/core/modules/language/language.routing.yml
+++ b/core/modules/language/language.routing.yml
@@ -85,3 +85,10 @@ language.content_settings_page:
     _form: 'Drupal\language\Form\ContentLanguageSettingsForm'
   requirements:
     _permission: 'administer languages'
+
+language.redirect:
+  path: '/language-redirect'
+  defaults:
+    _controller: '\Drupal\language\Controller\LanguageSwitcherController::languageSwitcherRedirect'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml
index b9de79a..3ea2682 100644
--- a/core/modules/language/language.services.yml
+++ b/core/modules/language/language.services.yml
@@ -24,3 +24,6 @@ services:
     tags:
       - { name: paramconverter }
     lazy: true
+  language.redirect:
+    class: Drupal\language\Controller\LanguageSwitcherController
+    arguments: ['@current_route_match', '@renderer', '@redirect.destination']
diff --git a/core/modules/language/src/Controller/LanguageSwitcherController.php b/core/modules/language/src/Controller/LanguageSwitcherController.php
new file mode 100644
index 0000000..b863d48
--- /dev/null
+++ b/core/modules/language/src/Controller/LanguageSwitcherController.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\language\Controller\LanguageSwitcherController.
+ */
+
+namespace Drupal\language\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\RedirectDestinationInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Defines a controller for language switching.
+ */
+class LanguageSwitcherController extends ControllerBase implements ContainerInjectionInterface {
+
+  /**
+   * The current route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+
+  /**
+   * The redirect destination.
+   *
+   * @var \Drupal\Core\Routing\RedirectDestinationInterface
+   */
+  protected $redirectDestination;
+
+  /**
+   * Constructs an LanguageSwitcherController object.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
+   * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
+   *   The redirect destination.
+   */
+  public function __construct(RouteMatchInterface $route_match, RendererInterface $renderer, RedirectDestinationInterface $redirect_destination) {
+    $this->routeMatch = $route_match;
+    $this->renderer = $renderer;
+    $this->redirectDestination = $redirect_destination;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('current_route_match'),
+      $container->get('renderer'),
+      $container->get('redirect.destination')
+    );
+  }
+
+  public function setLanguageSwitchDestination(array $element, array $context) {
+    $destination = $this->routeMatch->getRouteName() ? Url::fromRouteMatch($this->routeMatch)->getInternalPath() : '';
+
+    $callback =  'language.redirect:setLanguageSwitchDestination';
+    $placeholder = $this->renderer->generateCachePlaceholder($callback, $context);
+
+    // @todo: We need to add the active class to the active language.
+
+    // @todo This is working. But why?
+    $placeholder = str_replace('%2F', '/', urlencode($placeholder));
+
+    $element['#markup'] = str_replace($placeholder, $destination, $element['#markup']);
+    return $element;
+  }
+
+  public function languageSwitcherRedirect(Request $request) {
+    $destination = $this->redirectDestination->get();
+    // Avoid redirect loop if route is <current> or if there is no destination.
+    if (!isset($destination) || $destination === $this->getUrlGenerator()->generateFromRoute('<current>'))  {
+      $destination = $this->getUrlGenerator()->generateFromRoute('<front>');
+    }
+
+    return new RedirectResponse($destination, 301);
+  }
+
+}
diff --git a/core/modules/language/src/Plugin/Block/LanguageBlock.php b/core/modules/language/src/Plugin/Block/LanguageBlock.php
index 7aa9bb8..c63b1b3 100644
--- a/core/modules/language/src/Plugin/Block/LanguageBlock.php
+++ b/core/modules/language/src/Plugin/Block/LanguageBlock.php
@@ -8,7 +8,11 @@
 namespace Drupal\language\Plugin\Block;
 
 use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Path\PathMatcherInterface;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
@@ -35,11 +39,25 @@ class LanguageBlock extends BlockBase implements ContainerFactoryPluginInterface
   protected $languageManager;
 
   /**
-   * The path matcher.
+   * The current user.
    *
-   * @var \Drupal\Core\Path\PathMatcherInterface
+   * @var \Drupal\Core\Session\AccountInterface
    */
-  protected $pathMatcher;
+  protected $currentUser;
+
+  /**
+   * The current route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * The renderer service.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
 
   /**
    * Constructs an LanguageBlock object.
@@ -52,16 +70,21 @@ class LanguageBlock extends BlockBase implements ContainerFactoryPluginInterface
    *   The plugin implementation definition.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
-   * @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
-   *   The path matcher.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager, PathMatcherInterface $path_matcher) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, LanguageManagerInterface $language_manager, AccountInterface $current_user, RouteMatchInterface $route_match, RendererInterface $renderer) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->languageManager = $language_manager;
-    $this->pathMatcher = $path_matcher;
+    $this->currentUser = $current_user;
+    $this->routeMatch = $route_match;
+    $this->renderer = $renderer;
   }
 
-
   /**
    * {@inheritdoc}
    */
@@ -71,7 +94,9 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_id,
       $plugin_definition,
       $container->get('language_manager'),
-      $container->get('path.matcher')
+      $container->get('current_user'),
+      $container->get('current_route_match'),
+      $container->get('renderer')
     );
   }
 
@@ -88,9 +113,23 @@ protected function blockAccess(AccountInterface $account) {
    */
   public function build() {
     $build = array();
-    $route_name = $this->pathMatcher->isFrontPage() ? '<front>' : '<current>';
     $type = $this->getDerivativeId();
-    $links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRoute($route_name));
+
+    $context = array(
+      'path' =>  $this->routeMatch->getRouteName() ? Url::fromRouteMatch($this->routeMatch)->getInternalPath() : '',
+      'type' => $type,
+    );
+
+    // For authenticated users, we just rely on Javascript for enhancing the
+    // block with the destination. In case of anonymous, we use
+    // post_render_cache for replacing a placeholder.
+    $switch_links_options = [];
+    if ($this->currentUser->isAnonymous()) {
+      $callback = 'language.redirect:setLanguageSwitchDestination';
+      $switch_links_options['destination'] = $this->renderer->generateCachePlaceholder($callback, $context);
+    }
+
+    $links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRoute('language.redirect', $switch_links_options));
 
     if (isset($links->links)) {
       $build = array(
@@ -99,21 +138,44 @@ public function build() {
         '#attributes' => array(
           'class' => array(
             "language-switcher-{$links->method_id}",
+            "language-switcher"
           ),
         ),
         '#set_active_class' => TRUE,
       );
+
+      if ($this->currentUser->isAuthenticated()) {
+        $build['#attached']['library'][] = 'language/drupal.language.switcher';
+      }
+      else {
+        $build['#post_render_cache']['language.redirect:setLanguageSwitchDestination'] = array(
+          $context
+        );
+      }
     }
+
     return $build;
   }
 
   /**
    * {@inheritdoc}
-   *
-   * @todo Make cacheable in https://drupal.org/node/2232375.
+   */
+  public function getCacheContexts() {
+    // The "Language switcher" block must be cached per derivative, as we can
+    // have several blocks controlling different negotiation types.
+    return [
+      'languages:' . $this->getDerivativeId(),
+      'user.roles:anonymous'
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function getCacheMaxAge() {
-    return 0;
+    // @todo Ensure whenever language settings change, that appropriate cache
+    //   tags are invalidated, that allows us to cache this block forever.
+    return Cache::PERMANENT;
   }
 
 }
