diff --git a/core/modules/book/book.services.yml b/core/modules/book/book.services.yml
index 46fddbc..ac52aa9 100644
--- a/core/modules/book/book.services.yml
+++ b/core/modules/book/book.services.yml
@@ -1,18 +1,18 @@
 services:
   book.breadcrumb:
     class: Drupal\book\BookBreadcrumbBuilder
-    arguments: ['@entity.manager', '@current_user']
+    arguments: ['@entity.manager', '@current_user', '@language_manager']
     tags:
       - { name: breadcrumb_builder, priority: 701 }
   book.manager:
     class: Drupal\book\BookManager
-    arguments: ['@entity.manager', '@string_translation', '@config.factory', '@book.outline_storage', '@renderer']
+    arguments: ['@entity.manager', '@string_translation', '@config.factory', '@book.outline_storage', '@renderer', '@language_manager']
   book.outline:
     class: Drupal\book\BookOutline
     arguments: ['@book.manager']
   book.export:
     class: Drupal\book\BookExport
-    arguments: ['@entity.manager', '@book.manager', '@renderer']
+    arguments: ['@entity.manager', '@book.manager', '@language_manager']
   book.outline_storage:
     class: Drupal\book\BookOutlineStorage
     arguments: ['@database']
diff --git a/core/modules/book/src/BookBreadcrumbBuilder.php b/core/modules/book/src/BookBreadcrumbBuilder.php
index 90591d8..4ab0d56 100644
--- a/core/modules/book/src/BookBreadcrumbBuilder.php
+++ b/core/modules/book/src/BookBreadcrumbBuilder.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Breadcrumb\Breadcrumb;
 use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -31,6 +32,13 @@ class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface {
    */
   protected $account;
 
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
   /**
    * Constructs the BookBreadcrumbBuilder.
    *
@@ -38,10 +46,13 @@ class BookBreadcrumbBuilder implements BreadcrumbBuilderInterface {
    *   The entity manager service.
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The current user account.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
-  public function __construct(EntityManagerInterface $entity_manager, AccountInterface $account) {
+  public function __construct(EntityManagerInterface $entity_manager, AccountInterface $account, LanguageManagerInterface $language_manager) {
     $this->nodeStorage = $entity_manager->getStorage('node');
     $this->account = $account;
+    $this->languageManager = $language_manager;
   }
 
   /**
@@ -68,11 +79,17 @@ public function build(RouteMatchInterface $route_match) {
       $depth++;
     }
     $parent_books = $this->nodeStorage->loadMultiple($book_nids);
+    $langcode = $this->languageManager->getCurrentLanguage()->getId();
     if (count($parent_books) > 0) {
       $depth = 1;
       while (!empty($book['p' . ($depth + 1)])) {
         if (!empty($parent_books[$book['p' . $depth]]) && ($parent_book = $parent_books[$book['p' . $depth]])) {
           $access = $parent_book->access('view', $this->account, TRUE);
+          if ($parent_book->isTranslatable()) {
+            if ($parent_book->hasTranslation($langcode)) {
+              $parent_book = $parent_book->getTranslation($langcode);
+            }
+          }
           $breadcrumb->addCacheableDependency($access);
           if ($access->isAllowed()) {
             $breadcrumb->addCacheableDependency($parent_book);
diff --git a/core/modules/book/src/BookExport.php b/core/modules/book/src/BookExport.php
index a323370..6119e71 100644
--- a/core/modules/book/src/BookExport.php
+++ b/core/modules/book/src/BookExport.php
@@ -3,6 +3,7 @@
 namespace Drupal\book;
 
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\node\NodeInterface;
 
 /**
@@ -33,6 +34,13 @@ class BookExport {
    */
   protected $bookManager;
 
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
   /**
    * Constructs a BookExport object.
    *
@@ -40,11 +48,14 @@ class BookExport {
    *   The entity manager.
    * @param \Drupal\book\BookManagerInterface $book_manager
    *   The book manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
-  public function __construct(EntityManagerInterface $entityManager, BookManagerInterface $book_manager) {
+  public function __construct(EntityManagerInterface $entityManager, BookManagerInterface $book_manager, LanguageManagerInterface $language_manager) {
     $this->nodeStorage = $entityManager->getStorage('node');
     $this->viewBuilder = $entityManager->getViewBuilder('node');
     $this->bookManager = $book_manager;
+    $this->languageManager = $language_manager;
   }
 
   /**
@@ -107,6 +118,15 @@ protected function exportTraverse(array $tree, $callable) {
     foreach ($tree as $data) {
       // Note- access checking is already performed when building the tree.
       if ($node = $this->nodeStorage->load($data['link']['nid'])) {
+
+        // HERE
+        $langcode = $this->languageManager->getCurrentLanguage()->getId();
+        if ($node->isTranslatable()) {
+          if ($node->hasTranslation($langcode)) {
+            $node = $node->getTranslation($langcode);
+          }
+        }
+
         $children = $data['below'] ? $this->exportTraverse($data['below'], $callable) : '';
         $build[] = call_user_func($callable, $node, $children);
       }
diff --git a/core/modules/book/src/BookManager.php b/core/modules/book/src/BookManager.php
index 3db359b..06c5f01 100644
--- a/core/modules/book/src/BookManager.php
+++ b/core/modules/book/src/BookManager.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -67,15 +68,23 @@ class BookManager implements BookManagerInterface {
    */
   protected $renderer;
 
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
   /**
    * Constructs a BookManager object.
    */
-  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, BookOutlineStorageInterface $book_outline_storage, RendererInterface $renderer) {
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $translation, ConfigFactoryInterface $config_factory, BookOutlineStorageInterface $book_outline_storage, RendererInterface $renderer, LanguageManagerInterface $language_manager) {
     $this->entityManager = $entity_manager;
     $this->stringTranslation = $translation;
     $this->configFactory = $config_factory;
     $this->bookOutlineStorage = $book_outline_storage;
     $this->renderer = $renderer;
+    $this->languageManager = $language_manager;
   }
 
   /**
@@ -100,10 +109,16 @@ protected function loadBooks() {
       $nodes = $this->entityManager->getStorage('node')->loadMultiple($nids);
       // @todo: Sort by weight and translated title.
 
+      $langcode = $this->languageManager->getCurrentLanguage()->getId();
       // @todo: use route name for links, not system path.
       foreach ($book_links as $link) {
         $nid = $link['nid'];
         if (isset($nodes[$nid]) && $nodes[$nid]->status) {
+          if ($nodes[$nid]->isTranslatable()) {
+            if ($nodes[$nid]->hasTranslation($langcode)) {
+              $nodes[$nid] = $nodes[$nid]->getTranslation($langcode);
+            }
+          }
           $link['url'] = $nodes[$nid]->urlInfo();
           $link['title'] = $nodes[$nid]->label();
           $link['type'] = $nodes[$nid]->bundle();
@@ -465,7 +480,7 @@ public function deleteFromBook($nid) {
    */
   public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) {
     $tree = &drupal_static(__METHOD__, []);
-    $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+    $language_interface = $this->languageManager->getCurrentLanguage();
 
     // Use $nid as a flag for whether the data being loaded is for the whole
     // tree.
@@ -546,6 +561,7 @@ public function bookTreeOutput(array $tree) {
    */
   protected function buildItems(array $tree) {
     $items = [];
+    $langcode = $this->languageManager->getCurrentLanguage()->getId();
 
     foreach ($tree as $data) {
       $element = [];
@@ -577,6 +593,11 @@ protected function buildItems(array $tree) {
       $element['attributes'] = new Attribute();
       $element['title'] = $data['link']['title'];
       $node = $this->entityManager->getStorage('node')->load($data['link']['nid']);
+      if ($node->isTranslatable()) {
+        if ($node->hasTranslation($langcode)) {
+          $node = $node->getTranslation($langcode);
+        }
+      }
       $element['url'] = $node->urlInfo();
       $element['localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : [];
       $element['localized_options']['set_active_class'] = TRUE;
@@ -656,7 +677,7 @@ protected function bookTreeBuild($bid, array $parameters = []) {
   protected function doBookTreeBuild($bid, array $parameters = []) {
     // Static cache of already built menu trees.
     $trees = &drupal_static(__METHOD__, []);
-    $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+    $language_interface = $this->languageManager->getCurrentLanguage();
 
     // Build the cache id; sort parents to prevent duplicate storage and remove
     // default parameter values.
@@ -1005,6 +1026,12 @@ public function bookLinkTranslate(&$link) {
         $node = $this->entityManager->getStorage('node')
           ->load($link['nid']);
       }
+      if ($node->isTranslatable()) {
+        $langcode = $this->languageManager->getCurrentLanguage()->getId();
+        if ($node->hasTranslation($langcode)) {
+          $node = $node->getTranslation($langcode);
+        }
+      }
       // The node label will be the value for the current user's language.
       $link['title'] = $node->label();
       $link['options'] = [];
diff --git a/core/modules/book/src/Form/BookAdminEditForm.php b/core/modules/book/src/Form/BookAdminEditForm.php
index 66e717e..216af87 100644
--- a/core/modules/book/src/Form/BookAdminEditForm.php
+++ b/core/modules/book/src/Form/BookAdminEditForm.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Url;
 use Drupal\node\NodeInterface;
@@ -34,6 +35,13 @@ class BookAdminEditForm extends FormBase {
    */
   protected $bookManager;
 
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
   /**
    * Constructs a new BookAdminEditForm.
    *
@@ -41,10 +49,13 @@ class BookAdminEditForm extends FormBase {
    *   The custom block storage.
    * @param \Drupal\book\BookManagerInterface $book_manager
    *   The book manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
-  public function __construct(EntityStorageInterface $node_storage, BookManagerInterface $book_manager) {
+  public function __construct(EntityStorageInterface $node_storage, BookManagerInterface $book_manager, LanguageManagerInterface $language_manager) {
     $this->nodeStorage = $node_storage;
     $this->bookManager = $book_manager;
+    $this->languageManager = $language_manager;
   }
 
   /**
@@ -54,7 +65,8 @@ public static function create(ContainerInterface $container) {
     $entity_manager = $container->get('entity.manager');
     return new static(
       $entity_manager->getStorage('node'),
-      $container->get('book.manager')
+      $container->get('book.manager'),
+      $container->get('language_manager')
     );
   }
 
@@ -100,6 +112,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       $order = array_flip(array_keys($user_input['table']));
       $form['table'] = array_merge($order, $form['table']);
 
+      $langcode = $this->languageManager->getCurrentLanguage()->getId();
       foreach (Element::children($form['table']) as $key) {
         if ($form['table'][$key]['#item']) {
           $row = $form['table'][$key];
@@ -116,6 +129,11 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
           // Update the title if changed.
           if ($row['title']['#default_value'] != $values['title']) {
             $node = $this->nodeStorage->load($values['nid']);
+            if ($node->isTranslatable()) {
+              if ($node->hasTranslation($langcode)) {
+                $node = $node->getTranslation($langcode);
+              }
+            }
             $node->revision_log = $this->t('Title changed from %original to %current.', ['%original' => $node->label(), '%current' => $values['title']]);
             $node->title = $values['title'];
             $node->book['link_title'] = $values['title'];
diff --git a/core/modules/book/tests/src/Functional/BookTranslationTest.php b/core/modules/book/tests/src/Functional/BookTranslationTest.php
new file mode 100644
index 0000000..7949ce5
--- /dev/null
+++ b/core/modules/book/tests/src/Functional/BookTranslationTest.php
@@ -0,0 +1,456 @@
+<?php
+
+namespace Drupal\Tests\book\Functional;
+
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\node\Entity\Node;
+use Drupal\user\Entity\Role;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Create a book, add pages, and test book translations.
+ *
+ * @group book
+ */
+class BookTranslationTest extends BrowserTestBase {
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'book',
+    'block',
+    'node_access_test',
+    'book_test',
+    'locale',
+    'content_translation',
+    'language',
+  ];
+
+  /**
+   * A book node.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $bookcover;
+
+  /**
+   * A book node.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $bookpage1;
+
+
+  /**
+   * A user with permission to create and edit books.
+   *
+   * @var object
+   */
+  protected $bookAuthor;
+
+  /**
+   * A user with permission to create and edit books and to administer blocks.
+   *
+   * @var object
+   */
+  protected $adminUser;
+
+  /**
+   * A user with permission to view a book and access printer-friendly version.
+   *
+   * @var object
+   */
+  protected $webUser;
+
+  /**
+   * The other language available for content.
+   *
+   * @var string
+   */
+  protected $secondLanguage = 'de';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // node_access_test requires a node_access_rebuild().
+    node_access_rebuild();
+
+    // Create users.
+    $this->bookAuthor = $this->drupalCreateUser([
+      'create new books',
+      'create book content',
+      'edit own book content',
+      'add content to books',
+      'create content translations',
+    ]);
+    $this->adminUser = $this->drupalCreateUser([
+      'create new books',
+      'create book content',
+      'edit any book content',
+      'delete any book content',
+      'add content to books',
+      'administer blocks',
+      'administer permissions',
+      'administer book outlines',
+      'node test view',
+      'administer content types',
+      'administer site configuration',
+      'create content translations',
+    ]);
+    $this->webUser = $this->drupalCreateUser([
+      'access printer-friendly version',
+      'node test view',
+      'access content',
+    ]);
+  }
+
+  /**
+   * Test that the link text to the next page is translated.
+   */
+  public function testBookNextLink() {
+    $localized_title = 'DE Buch Seite 1';
+    // Preparation.
+    $this->enableSecondLanguage();
+
+    $nodes = $this->createBook();
+
+    $this->translateBookPage($this->bookcover, "DE Buch", "Buchdeckel auf Deutsch");
+    $this->translateBookPage($this->bookpage1, $localized_title, "Seite 1 auf Deutsch");
+
+    $this->drupalLogin($this->webUser);
+
+    // Go to book page.
+    $this->drupalGet($this->secondLanguage . '/node/' . $this->bookcover->id());
+
+    /* Identify hyperlink for assertions */
+    $elements = $this->xpath('//a[@rel="next"]');
+    $this->assertThat(count($elements), $this->equalTo(1), "No element found.");
+    $element = $elements[0];
+    $language = \Drupal::languageManager()->getLanguage($this->secondLanguage);
+    $elementUrl = Url::fromUri('internal:/' . $this->secondLanguage . '/node/' . $this->bookpage1->id(), ['language' => $language])->toString();
+    $this->assertEquals($elementUrl, $element->getAttribute('href'), 'Anchor points to correct node');
+    $this->assertContains($localized_title, $element->getText(), 'Text is not localized');
+  }
+
+  /**
+   * Test that the link text to the previous page is translated.
+   */
+  public function testBookPrevLink() {
+    $localized_title = 'DE Buch Deckel';
+    // Preparation.
+    $this->enableSecondLanguage();
+
+    $nodes = $this->createBook();
+
+    $this->translateBookPage($this->bookcover, $localized_title, "Buchdeckel auf Deutsch");
+    $this->translateBookPage($this->bookpage1, "DE Seite 1", "Seite 1 auf Deutsch");
+
+    $this->drupalLogin($this->webUser);
+
+    // Go to book page.
+    $this->drupalGet($this->secondLanguage . '/node/' . $this->bookpage1->id());
+
+    /* Identify hyperlink for assertions */
+    $elements = $this->xpath('//a[@rel="prev"]');
+    $this->assertThat(count($elements), $this->equalTo(1), "No element found.");
+    $element = $elements[0];
+    $language = \Drupal::languageManager()->getLanguage($this->secondLanguage);
+    $elementUrl = Url::fromUri('internal:/' . $this->secondLanguage . '/node/' . $this->bookcover->id(), ['language' => $language])->toString();
+    $this->assertEquals($elementUrl, $element->getAttribute('href'), 'Anchor points to correct node');
+    $this->assertContains($localized_title, $element->getText(), 'Text is not localized');
+  }
+
+  /**
+   * Test that the breadcrumb title is translated.
+   */
+  public function testBookBreadcrumb() {
+    $localized_title = 'DE Buch Deckel';
+    // Preparation.
+    $this->enableSecondLanguage();
+
+    $nodes = $this->createBook();
+
+    $this->translateBookPage($this->bookcover, $localized_title, "Buchdeckel auf Deutsch");
+    $this->translateBookPage($this->bookpage1, "DE Seite 1", "Seite 1 auf Deutsch");
+
+    $this->drupalPlaceBlock('system_breadcrumb_block');
+
+    $this->drupalLogin($this->webUser);
+
+    // Go to book page.
+    $this->drupalGet($this->secondLanguage . '/node/' . $this->bookpage1->id());
+
+    /* Identify hyperlink for assertions. Second hyperlink is pointing to parent page */
+    $elements = $this->xpath('(//nav[@class="breadcrumb"]//a)[2]');
+    $this->assertThat(count($elements), $this->equalTo(1), "No element found.");
+    $element = $elements[0];
+    $language = \Drupal::languageManager()->getLanguage($this->secondLanguage);
+    $elementUrl = Url::fromUri('internal:/' . $this->secondLanguage . '/node/' . $this->bookcover->id(), ['language' => $language])->toString();
+    $this->assertEquals($elementUrl, $element->getAttribute('href'), 'Anchor points to correct node');
+    $this->assertContains($localized_title, $element->getText(), 'Text is not localized');
+  }
+
+  /**
+   * Test that the title in the table of contents is translated.
+   */
+  public function testBookTableOfContents() {
+    $localized_title = 'DE Buch Seite 1';
+    // Preparation.
+    $this->enableSecondLanguage();
+
+    $nodes = $this->createBook();
+
+    $this->translateBookPage($this->bookcover, "DE Buch", "Buchdeckel auf Deutsch");
+    $this->translateBookPage($this->bookpage1, $localized_title, "Seite 1 auf Deutsch");
+
+    $this->drupalLogin($this->webUser);
+
+    // Go to book page.
+    $this->drupalGet($this->secondLanguage . '/node/' . $this->bookcover->id());
+
+    /* Identify hyperlink for assertions: first anchor in book-navigation */
+    $elements = $this->xpath('(//nav[@id="book-navigation-' . $this->bookcover->id() . '"]/ul[@class="menu"]/li/a)[1]');
+    $this->assertThat(count($elements), $this->equalTo(1), "No element found.");
+    $element = $elements[0];
+    $language = \Drupal::languageManager()->getLanguage($this->secondLanguage);
+    $elementUrl = Url::fromUri('internal:/' . $this->secondLanguage . '/node/' . $this->bookpage1->id(), ['language' => $language])->toString();
+    $this->assertEquals($elementUrl, $element->getAttribute('href'), 'Anchor points to correct node');
+    $this->assertContains($localized_title, $element->getText(), 'Text is not localized');
+  }
+
+  /**
+   * Test that the content in the export is translated.
+   */
+  public function testBookExport() {
+    $localized_cover = "DE Buch";
+    $localized_title = 'DE Buch Seite 1';
+    // Preparation.
+    $this->enableSecondLanguage();
+
+    $nodes = $this->createBook();
+
+    $this->translateBookPage($this->bookcover, $localized_cover, "Buchdeckel auf Deutsch");
+    $this->translateBookPage($this->bookpage1, $localized_title, "Seite 1 auf Deutsch");
+
+    $this->drupalLogin($this->webUser);
+
+    // Go to book page.
+    $this->drupalGet($this->secondLanguage . '/book/export/html/' . $this->bookcover->id());
+
+    /* Identify first h1 page title */
+    $elements = $this->xpath('(//article[@id="node-' . $this->bookcover->id() . '"]/h1)[1]');
+    $this->assertThat(count($elements), $this->equalTo(1), "No element found.");
+    $element = $elements[0];
+    $language = \Drupal::languageManager()->getLanguage($this->secondLanguage);
+    $this->assertEquals($localized_cover, $element->getText(), 'Text for cover is not localized');
+
+    /* Identify second h1 page title */
+    $elements = $this->xpath('(//article[@id="node-' . $this->bookpage1->id() . '"]/h1)[1]');
+    $this->assertThat(count($elements), $this->equalTo(1), "No element found.");
+    $element = $elements[0];
+    $language = \Drupal::languageManager()->getLanguage($this->secondLanguage);
+    $this->assertEquals($localized_title, $element->getText(), 'Text for page1 is not localized');
+  }
+
+  /**
+   * Install content_translation module a a second.
+   */
+  public function enableSecondLanguage() {
+    $this->drupalLogin($this->rootUser);
+    $this->addSecondLanguage();
+    $this->enableContentTypeTranslation();
+    // Rebuild the container so that the new languages are picked up by services
+    // that hold a list of languages.
+    $this->rebuildContainer();
+  }
+
+  /**
+   * Enable translation for book content type.
+   */
+  protected function enableContentTypeTranslation() {
+    \Drupal::service('content_translation.manager')->setEnabled('node', 'book', TRUE);
+    $roles = $this->adminUser->getRoles(TRUE);
+    Role::load(reset($roles))
+      ->grantPermission('create content translations')
+      ->grantPermission('translate any entity')
+      ->save();
+    $roles = $this->bookAuthor->getRoles(TRUE);
+    Role::load(reset($roles))
+      ->grantPermission('create content translations')
+      ->grantPermission('translate any entity')
+      ->save();
+    drupal_static_reset();
+    \Drupal::entityManager()->clearCachedDefinitions();
+    \Drupal::service('router.builder')->rebuild();
+    \Drupal::service('entity.definition_update_manager')->applyUpdates();
+
+    \Drupal::service('content_translation.manager')->setEnabled('node', 'book', TRUE);
+    drupal_static_reset();
+    \Drupal::entityManager()->clearCachedDefinitions();
+    \Drupal::service('router.builder')->rebuild();
+    \Drupal::service('entity.definition_update_manager')->applyUpdates();
+  }
+
+  /**
+   * Add a second content language.
+   */
+  protected function addSecondLanguage() {
+    $langcode = $this->secondLanguage;
+
+    // Check to make sure that language has not already been installed.
+    $this->drupalGet('admin/config/regional/language');
+
+    if (strpos($this->getTextContent(), 'languages[' . $langcode . ']') === FALSE) {
+      // Doesn't have language installed so add it.
+      $edit = [];
+      $edit['predefined_langcode'] = $langcode;
+      $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
+
+      // Reset language list.
+      \Drupal::languageManager()->reset();
+      $languages = \Drupal::languageManager()->getLanguages();
+      $this->assertTrue(array_key_exists($langcode, $languages), 'Language was installed successfully.');
+
+      if (array_key_exists($langcode, $languages)) {
+        $this->assertRaw(t('The language %language has been created and can now be used.', ['%language' => $languages[$langcode]->getName()]), 'Language has been created.');
+      }
+    }
+    else {
+      // It's installed. No need to do anything.
+      $this->assertTrue(TRUE, 'Language [' . $langcode . '] already installed.');
+    }
+  }
+
+  /**
+   * Install content_translation module.
+   */
+  protected function installContentTranslationModule() {
+    $edit = [
+      'modules[content_translation][enable]' => TRUE,
+      'modules[language][enable]' => TRUE,
+    ];
+    $this->drupalPostForm('admin/modules', $edit, t('Install'));
+
+    $this->assertText(t('This site has only a single language enabled. Add at least one more language in order to translate content.'));
+  }
+
+  /**
+   * Translate a book page.
+   *
+   * @param \Drupal\node\Entity\Node $node
+   *   Book page.
+   * @param string $title
+   *   The translated title.
+   * @param string $body
+   *   The translated body.
+   */
+  public function translateBookPage(Node $node, $title, $body) {
+    $this->drupalLogin($this->bookAuthor);
+
+    $langcode = $this->secondLanguage;
+    $this->drupalGet('node/' . $node->id() . '/translations/add/en/de');
+
+    $edit = [];
+    $edit["title[0][value]"] = $title;
+    $edit["body[0][value]"] = $body;
+    $this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
+    $options = [
+      'attributes' => ['hreflang' => $langcode],
+    ];
+    $link = Link::fromTextAndUrl($title, Url::fromUri('internal:/' . $langcode . '/node/' . $node->id(), $options))->toString();
+    $this->drupalLogout();
+  }
+
+  /**
+   * Creates a new book with a page hierarchy.
+   *
+   * @return \Drupal\node\NodeInterface[]
+   *   An array of book pages.
+   */
+  public function createBook() {
+    // Create new book.
+    $this->drupalLogin($this->bookAuthor);
+
+    $this->bookcover = $this->createBookNode('EN Book', 'Book cover in English', 'new');
+    $book = $this->bookcover;
+
+    /*
+     * Add page hierarchy to book.
+     * Book
+     *  |- Node 0
+     *   |- Node 1
+     *   |- Node 2
+     *  |- Node 3
+     *  |- Node 4
+     */
+    $nodes = [];
+    // Node 0.
+    $nodes[] = $this->bookpage1 = $this->createBookNode('EN Page 1', 'Book page in English', $book->id());
+    // Node 1.
+    $nodes[] = $this->createBookNode('EN Page 2', 'Book page in English', $book->id(), $nodes[0]->book['nid']);
+    // Node 2.
+    $nodes[] = $this->createBookNode('EN Page 3', 'Book page in English', $book->id(), $nodes[0]->book['nid']);
+    // Node 3.
+    $nodes[] = $this->createBookNode('EN Page 4', 'Book page in English', $book->id());
+    // Node 4.
+    $nodes[] = $this->createBookNode('EN Page 5', 'Book page in English', $book->id());
+
+    $this->drupalLogout();
+
+    return $nodes;
+  }
+
+  /**
+   * Creates a book node.
+   *
+   * @param string $title
+   *   The page title.
+   * @param string $body
+   *   The page body text.
+   * @param int|string $book_nid
+   *   A book node ID or set to 'new' to create a new book.
+   * @param int|null $parent
+   *   (optional) Parent book reference ID. Defaults to NULL.
+   *
+   * @return \Drupal\node\NodeInterface
+   *   The created node.
+   */
+  public function createBookNode($title, $body, $book_nid, $parent = NULL) {
+    // $number does not use drupal_static as it should not be reset
+    // since it uniquely identifies each call to createBookNode().
+    // Used to ensure that when sorted nodes stay in same order.
+    static $number = 0;
+
+    $edit = [];
+    $edit['title[0][value]'] = $title;
+    $edit['body[0][value]'] = $body;
+    $edit['book[bid]'] = $book_nid;
+
+    if ($parent !== NULL) {
+      $this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
+
+      $edit['book[pid]'] = $parent;
+      $this->drupalPostForm(NULL, $edit, t('Save'));
+      // Make sure the parent was flagged as having children.
+      $parent_node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($parent);
+      $this->assertFalse(empty($parent_node->book['has_children']), 'Parent node is marked as having children');
+    }
+    else {
+      $this->drupalPostForm('node/add/book', $edit, t('Save'));
+    }
+
+    // Check to make sure the book node was created.
+    $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+    $this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.');
+    $number++;
+
+    return $node;
+  }
+
+}
diff --git a/core/modules/book/tests/src/Unit/BookManagerTest.php b/core/modules/book/tests/src/Unit/BookManagerTest.php
index 98dbd5a..aa74065 100644
--- a/core/modules/book/tests/src/Unit/BookManagerTest.php
+++ b/core/modules/book/tests/src/Unit/BookManagerTest.php
@@ -39,6 +39,13 @@ class BookManagerTest extends UnitTestCase {
    */
   protected $renderer;
 
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
   /**
    * The tested book manager.
    *
@@ -62,7 +69,8 @@ protected function setUp() {
     $this->configFactory = $this->getConfigFactoryStub([]);
     $this->bookOutlineStorage = $this->getMock('Drupal\book\BookOutlineStorageInterface');
     $this->renderer = $this->getMock('\Drupal\Core\Render\RendererInterface');
-    $this->bookManager = new BookManager($this->entityManager, $this->translation, $this->configFactory, $this->bookOutlineStorage, $this->renderer);
+    $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface');
+    $this->bookManager = new BookManager($this->entityManager, $this->translation, $this->configFactory, $this->bookOutlineStorage, $this->renderer, $this->languageManager);
   }
 
   /**
