diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php
new file mode 100644
index 0000000..5d8701e
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php
@@ -0,0 +1,267 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\node\Tests\NodeAccessLanguageAwareCombinationTest.
+ */
+
+namespace Drupal\node\Tests;
+
+use Drupal\Core\Language\Language;
+
+/**
+ * Tests node access with multiple languages and access control modules.
+ */
+class NodeAccessLanguageAwareCombinationTest extends NodeTestBase {
+
+  /**
+   * Enable language and two node access modules.
+   *
+   * @var array
+   */
+  public static $modules = array('language', 'node_access_test_language', 'node_access_test');
+
+  /**
+   * A set of nodes to use in testing.
+   *
+   * @var array
+   */
+  protected $nodes = array();
+
+  /**
+   * A normal authenticated user.
+   *
+   * @var \Drupal\user\Plugin\Core\Entity\User.
+   */
+  protected $web_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Node access language-aware combination',
+      'description' => 'Tests node access functionality with multiple languages and two node access modules.',
+      'group' => 'Node',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    node_access_rebuild();
+
+    // Clear permissions for authenticated users.
+    db_delete('role_permission')
+      ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+      ->execute();
+
+    // Add Hungarian and Catalan.
+    $language = new Language(array(
+      'langcode' => 'hu',
+    ));
+    language_save($language);
+    $language = new Language(array(
+      'langcode' => 'ca',
+    ));
+    language_save($language);
+
+    $this->web_user = $this->drupalCreateUser(array('access content'));
+
+    // The node_access_test_language module allows individual translations of
+    // a node to be marked private (not viewable by normal users), and the
+    // node_access_test module allows whole nodes to be marked private. (In a
+    // real-world implementation, hook_node_access_records_alter() might be
+    // implemented by one or both modules to enforce that private nodes or
+    // translations are always private, but we want to test the default,
+    // additive behavior of node access).
+
+    // Create six Hungarian nodes with Catalan translations:
+    // 1. One public with neither language marked as private.
+    // 2. One private with neither language marked as private.
+    // 3. One public with only the Hungarian translation private.
+    // 4. One public with only the Catalan translation private.
+    // 5. One public with both the Hungarian and Catalan translations private.
+    // 6. One private with both the Hungarian and Catalan translations private.
+    $this->nodes['public_both_public'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 0)),
+        'ca' => array(0 => array('value' => 0)),
+      ),
+      'private' => FALSE,
+    ));
+    $this->nodes['private_both_public'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 0)),
+        'ca' => array(0 => array('value' => 0)),
+      ),
+      'private' => TRUE,
+    ));
+    $this->nodes['public_hu_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 1)),
+        'ca' => array(0 => array('value' => 0)),
+      ),
+      'private' => FALSE,
+    ));
+    $this->nodes['public_ca_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 0)),
+        'ca' => array(0 => array('value' => 1)),
+      ),
+      'private' => FALSE,
+    ));
+    $this->nodes['public_both_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 1)),
+        'ca' => array(0 => array('value' => 1)),
+      ),
+      'private' => FALSE,
+    ));
+    $this->nodes['private_both_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 1)),
+        'ca' => array(0 => array('value' => 1)),
+      ),
+      'private' => TRUE,
+    ));
+  }
+
+  /**
+   * Tests node_access() and node access queries with multiple node languages.
+   */
+  function testNodeAccessLanguageAwareCombination() {
+
+    $expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
+    $expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
+
+    // When the node and both translations are public, access should only be
+    // denied when a translation that does not exist is requested.
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_both_public'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_public'], $this->web_user, 'en');
+
+    // If the node is marked private but both existing translations are not,
+    // access should still be granted, because the grants are additive.
+    $this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $this->nodes['private_both_public'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_public'], $this->web_user, 'en');
+
+    // If the node is marked public, any existing translations should be
+    // accessible, regardless of whether they are marked private. Access should
+    // only be denied when a translation that does not exist is specifically
+    // requested.
+    // With the Hungarian translation marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_hu_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_hu_private'], $this->web_user, 'en');
+
+    // With the Catalan translation marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access, $this->nodes['public_ca_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_ca_private'], $this->web_user, 'en');
+
+    // With both translations marked as private, but the node public:
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['public_both_private'], $this->web_user, 'en');
+
+    // If the node and both its existing translations are private, access
+    // should be denied in all cases.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['private_both_private'], $this->web_user, 'en');
+
+    // Query the node table with the node access tag in several languages.
+
+    // Query with no language specified. The fallback (hu) will be used.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Three nodes should be returned (with public Hungarian translations).
+    $this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes when no langcode is specified.');
+    $this->assertTrue(array_key_exists($this->nodes['public_both_public']->nid, $nids), 'Returned node ID is full public node.');
+    $this->assertTrue(array_key_exists($this->nodes['public_ca_private']->nid, $nids), 'Returned node ID is Hungarian public only node.');
+    $this->assertTrue(array_key_exists($this->nodes['private_both_public']->nid, $nids), 'Returned node ID is both public non-language-aware private only node.');
+
+    // Query with Hungarian (hu) specified.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addMetaData('langcode', 'hu')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // The results should be the same as the for default.
+    $this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.');
+    $this->assertTrue(array_key_exists($this->nodes['public_both_public']->nid, $nids), 'Returned node ID is both public node.');
+    $this->assertTrue(array_key_exists($this->nodes['public_ca_private']->nid, $nids), 'Returned node ID is Hungarian public only node.');
+    $this->assertTrue(array_key_exists($this->nodes['private_both_public']->nid, $nids), 'Returned node ID is both public non-language-aware private only node.');
+
+    // Query with Catalan (ca) specified.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addMetaData('langcode', 'ca')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Three nodes should be returned (with public Catalan translations).
+    $this->assertEqual(count($nids), 3, 'db_select() returns 3 nodes.');
+    $this->assertTrue(array_key_exists($this->nodes['public_both_public']->nid, $nids), 'Returned node ID is both public node.');
+    $this->assertTrue(array_key_exists($this->nodes['public_hu_private']->nid, $nids), 'Returned node ID is Catalan public only node.');
+    $this->assertTrue(array_key_exists($this->nodes['private_both_public']->nid, $nids), 'Returned node ID is both public non-language-aware private only node.');
+
+    // Query with German (de) specified.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addMetaData('langcode', 'de')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // There are no nodes with German translations, so no results are returned.
+    $this->assertTrue(empty($nids), 'db_select() returns an empty result.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and no specific langcode.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // All nodes are returned.
+    $this->assertEqual(count($nids), 6, 'db_select() returns all nodes.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and langcode de.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('langcode', 'de')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Even though there is no German translation, all nodes are returned
+    // because node access filtering does not occr when the user is user 1.
+    $this->assertEqual(count($nids), 6, 'db_select() returns all nodes.');
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php
new file mode 100644
index 0000000..f89c191
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php
@@ -0,0 +1,235 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\node\Tests\NodeAccessLanguageAwareTest.
+ */
+
+namespace Drupal\node\Tests;
+
+use Drupal\Core\Language\Language;
+
+/**
+ * Tests node access functionality for multiple languages.
+ */
+class NodeAccessLanguageAwareTest extends NodeTestBase {
+
+  /**
+   * Enable language and a language-aware node access module.
+   *
+   * @var array
+   */
+  public static $modules = array('language', 'node_access_test_language');
+
+  /**
+   * A set of nodes to use in testing.
+   *
+   * @var array
+   */
+  protected $nodes = array();
+
+  /**
+   * A normal authenticated user.
+   *
+   * @var \Drupal\user\Plugin\Core\Entity\User.
+   */
+  protected $web_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Node access language-aware',
+      'description' => 'Test node_access and db_select() with node_access tag functionality with multiple languages with node_access_test_language which is language-aware.',
+      'group' => 'Node',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    node_access_rebuild();
+
+    // Clear permissions for authenticated users.
+    db_delete('role_permission')
+      ->condition('rid', DRUPAL_AUTHENTICATED_RID)
+      ->execute();
+
+    // Create a normal authenticated user.
+    $this->web_user = $this->drupalCreateUser(array('access content'));
+
+    // Add Hungarian and Catalan.
+    $language = new Language(array(
+      'langcode' => 'hu',
+    ));
+    language_save($language);
+    $language = new Language(array(
+      'langcode' => 'ca',
+    ));
+    language_save($language);
+
+    // The node_access_test_language module allows individual translations of
+    // a node to be marked private (not viewable by normal users).
+
+    // Create four Hungarian nodes with Catalan translations:
+    // 1. One with neither language marked as private.
+    // 2. One with only the Hungarian translation private.
+    // 3. One with only the Catalan translation private.
+    // 4. One with both the Hungarian and Catalan translations private.
+    $this->nodes['both_public'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 0)),
+        'ca' => array(0 => array('value' => 0)),
+      )
+    ));
+    $this->nodes['ca_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 0)),
+        'ca' => array(0 => array('value' => 1)),
+      )
+    ));
+    $this->nodes['hu_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 1)),
+        'ca' => array(0 => array('value' => 0)),
+      )
+    ));
+    $this->nodes['both_private'] = $this->drupalCreateNode(array(
+      'body' => array('hu' => array(array())),
+      'langcode' => 'hu',
+      'field_private' => array(
+        'hu' => array(0 => array('value' => 1)),
+        'ca' => array(0 => array('value' => 1)),
+      )
+    ));
+  }
+
+  /**
+   * Tests node_access() and node access queries with multiple node languages.
+   */
+  function testNodeAccessLanguageAware() {
+    // The node_access_test_language module only grants view access.
+    $expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
+    $expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
+
+    // When both Hungarian and Catalan are marked as public:
+    // Access to the Hungarian translation should be granted when no language
+    // is specified or when the Hungarian translation is specified explicitly.
+    $this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->web_user, 'hu');
+    // Access to the Catalan translation should also be granted.
+    $this->assertNodeAccess($expected_node_access, $this->nodes['both_public'], $this->web_user, 'ca');
+    // There is no English translation, so a request to access the English
+    // translation is denied.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_public'], $this->web_user, 'en');
+
+    // When Hungarian is marked as private:
+    // Access to the Hungarian translation should be denied when no language
+    // is specified or when the Hungarian translation is specified explicitly.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->web_user, 'hu');
+    // Access to the Catalan translation should be granted.
+    $this->assertNodeAccess($expected_node_access, $this->nodes['hu_private'], $this->web_user, 'ca');
+    // There is no English translation, so a request to access the English
+    // translation is denied.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['hu_private'], $this->web_user, 'en');
+
+    // When Catalan is marked as private:
+    // Access to the Hungarian translation should be granted when no language
+    // is specified or when the Hungarian translation is specified explicitly.
+    $this->assertNodeAccess($expected_node_access, $this->nodes['ca_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access, $this->nodes['ca_private'], $this->web_user, 'hu');
+    // Access to the Catalan translation should be granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['ca_private'], $this->web_user, 'ca');
+    // There is no English translation, so a request to access the English
+    // translation is denied.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['ca_private'], $this->web_user, 'en');
+
+    // When both translations are marked as private, access should be denied
+    // regardless of the language specified.
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $this->nodes['both_private'], $this->web_user, 'en');
+
+    // Query the node table with the node access tag in several languages.
+
+    // Query with no language specified. The fallback (hu) will be used.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Two nodes should be returned: The node with both translations public,
+    // and the node with only the Catalan translation marked as private.
+    $this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when no langcode is specified.');
+    $this->assertTrue(array_key_exists($this->nodes['both_public']->nid, $nids), 'The node with both translations public is returned.');
+    $this->assertTrue(array_key_exists($this->nodes['ca_private']->nid, $nids), 'The node with only the Catalan translation private is returned.');
+
+    // Query with Hungarian (hu) specified.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addMetaData('langcode', 'hu')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // The results should be the same as the for default.
+    $this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.');
+    $this->assertTrue(array_key_exists($this->nodes['both_public']->nid, $nids), 'The node with both translations public is returned.');
+    $this->assertTrue(array_key_exists($this->nodes['ca_private']->nid, $nids), 'The node with only the Catalan translation private is returned.');
+
+    // Query with Catalan (ca) specified.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addMetaData('langcode', 'ca')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Two nodes should be returned: The node with both translations public,
+    // and the node with only the Hungarian translation marked as private.
+    $this->assertEqual(count($nids), 2, 'db_select() returns 2 nodes when the hu langcode is specified.');
+    $this->assertTrue(array_key_exists($this->nodes['both_public']->nid, $nids), 'The node with both translations public is returned.');
+    $this->assertTrue(array_key_exists($this->nodes['hu_private']->nid, $nids), 'The node with only the Hungarian translation private is returned.');
+
+    // Query with German (de) specified.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $this->web_user)
+    ->addMetaData('langcode', 'de')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // There are no nodes with German translations, so no results are returned.
+    $this->assertTrue(empty($nids), 'db_select() returns an empty result when the de langcode is specified.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and no specific langcode.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // All nodes are returned.
+    $this->assertEqual(count($nids), 4, 'db_select() returns all nodes.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and langcode de.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('langcode', 'de')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Even though there is no German translation, all nodes are returned
+    // because node access filtering does not occr when the user is user 1.
+    $this->assertEqual(count($nids), 4, 'db_select() returns all nodes.');
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php
index 6ff6ae0..33a67be 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageTest.php
@@ -24,34 +24,24 @@ class NodeAccessLanguageTest extends NodeTestBase {
   public static function getInfo() {
     return array(
       'name' => 'Node access language',
-      'description' => 'Test node_access functionality with multiple languages.',
+      'description' => 'Test node_access and db_select() with node_access tag functionality with multiple languages with a test node access module that is not language-aware.',
       'group' => 'Node',
     );
   }
 
-  /**
-   * Asserts node_access correctly grants or denies access.
-   */
-  function assertNodeAccess($ops, $node, $account, $langcode = NULL) {
-    foreach ($ops as $op => $result) {
-      $msg = t("node_access returns @result with operation '@op', language code @langcode.", array('@result' => $result ? 'true' : 'false', '@op' => $op, '@langcode' => !empty($langcode) ? "'$langcode'" : 'empty'));
-      $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg);
-    }
-  }
-
   function setUp() {
     parent::setUp();
 
+    node_access_rebuild();
+
     // Clear permissions for authenticated users.
     db_delete('role_permission')
       ->condition('rid', DRUPAL_AUTHENTICATED_RID)
       ->execute();
-  }
 
-  /**
-   * Runs tests for node_access function with multiple languages.
-   */
-  function testNodeAccess() {
+    // Enable the private node feature of node_access_test module.
+    state()->set('node_access_test.private', TRUE);
+
     // Add Hungarian and Catalan.
     $language = new Language(array(
       'langcode' => 'hu',
@@ -61,31 +51,189 @@ function testNodeAccess() {
       'langcode' => 'ca',
     ));
     language_save($language);
+  }
+
+  /**
+   * Tests node_access() with multiple node languages and no private nodes.
+   */
+  function testNodeAccess() {
+    $web_user = $this->drupalCreateUser(array('access content'));
+
+    $expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
+    $expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
+
+    // Creating a public node with langcode Hungarian, will be saved as
+    // the fallback in node access table.
+    $node_public = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => FALSE));
+    $this->assertTrue($node_public->langcode == 'hu', 'Node created as Hungarian.');
+
+    // Tests the default access provided for the public Hungarian node.
+    $this->assertNodeAccess($expected_node_access, $node_public, $web_user);
+
+    // Tests that Hungarian provided specifically results in the same.
+    $this->assertNodeAccess($expected_node_access, $node_public, $web_user, 'hu');
+
+    // There is no specific Catalan version of this node and Croatian is not
+    // even set up on the system in this scenario, so the user will not get
+    // access to these nodes.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'hr');
+
+    // Creating a public node with no special langcode, like when no language
+    // Module enabled.
+    $node_public_no_language = $this->drupalCreateNode(array('private' => FALSE));
+    $this->assertTrue($node_public_no_language->langcode == LANGUAGE_NOT_SPECIFIED, 'Node created with not specified language.');
+
+    // Tests that access provided if requested with no language.
+    $this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
+
+    // Tests that access not provided if requested with Hungarian language.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hu');
 
-    // Tests the default access provided for a published Hungarian node.
+    // There is no specific Catalan version of this node and Croatian is not
+    // even set up on the system in this scenario, so the user will not get
+    // access to these nodes.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hr');
+
+    // Reset the node access cache and turn on our test node_access() code.
+    drupal_static_reset('node_access');
+    variable_set('node_access_test_secret_catalan', 1);
+
+    // Tests that access provided if requested with no language.
+    $this->assertNodeAccess($expected_node_access, $node_public_no_language, $web_user);
+
+    // Tests that Hungarian is still not accessible.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'hu');
+
+    // Tests that Catalan is still not accessible.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public_no_language, $web_user, 'ca');
+  }
+
+  /**
+   * Tests node_access() with multiple node languages and private nodes.
+   */
+  function testNodeAccessPrivate() {
     $web_user = $this->drupalCreateUser(array('access content'));
-    $node = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu'));
-    $this->assertTrue($node->langcode == 'hu', 'Node created as Hungarian.');
+
     $expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
-    $this->assertNodeAccess($expected_node_access, $node, $web_user);
+    $expected_node_access_no_access = array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE);
+
+    // Creating a private node with langcode Hungarian, will be saved as
+    // the fallback in node access table.
+    $node_public = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => TRUE));
+    $this->assertTrue($node_public->langcode == 'hu', 'Node created as Hungarian.');
+
+    // Tests the default access is not provided for the private Hungarian node.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user);
 
     // Tests that Hungarian provided specifically results in the same.
-    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'hu');
 
     // There is no specific Catalan version of this node and Croatian is not
-    // even set up on the system in this scenario, so these languages will not
-    // play a role in the node's permissions.
-    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca');
-    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hr');
+    // even set up on the system in this scenario, so the user will not get
+    // access to these nodes.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node_public, $web_user, 'hr');
+
+    // Creating a private node with no special langcode, like when no language
+    // Module enabled.
+    $node_private_no_language = $this->drupalCreateNode(array('private' => TRUE));
+    $this->assertTrue($node_private_no_language->langcode == LANGUAGE_NOT_SPECIFIED, 'Node created with not specified language.');
+
+    // Tests that access not provided if requested with no language.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user);
+
+    // Tests that access not provided if requested with Hungarian language.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'hu');
+
+    // There is no specific Catalan version of this node and Croatian is not
+    // even set up on the system in this scenario, so the user will not get
+    // access to these nodes.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'hr');
 
     // Reset the node access cache and turn on our test node_access() code.
     drupal_static_reset('node_access');
     variable_set('node_access_test_secret_catalan', 1);
 
-    // Tests that Hungarian is still accessible.
-    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+    // Tests that access not provided if requested with no language.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user);
+
+    // Tests that Hungarian is still not accessible.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'hu');
+
+    // Tests that Catalan is still not accessible.
+    $this->assertNodeAccess($expected_node_access_no_access, $node_private_no_language, $web_user, 'ca');
+  }
+
+  /**
+   * Tests db_select() with a 'node_access' tag and langcode metadata.
+   */
+  function testNodeAccessQueryTag() {
+    $web_user = $this->drupalCreateUser(array('access content'));
 
-    // Tests that Catalan is not accessible anymore.
-    $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node, $web_user, 'ca');
+    // Creating a private node with langcode Hungarian, will be saved as
+    // the fallback in node access table.
+    $node_private = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => TRUE));
+    $this->assertTrue($node_private->langcode == 'hu', 'Node created as Hungarian.');
+
+    // Creating a public node with langcode Hungarian, will be saved as
+    // the fallback in node access table.
+    $node_public = $this->drupalCreateNode(array('body' => array('hu' => array(array())), 'langcode' => 'hu', 'private' => FALSE));
+    $this->assertTrue($node_public->langcode == 'hu', 'Node created as Hungarian.');
+
+    // Creating a public node with no special langcode, like when no language
+    // Module enabled.
+    $node_no_language = $this->drupalCreateNode(array('private' => FALSE));
+    $this->assertTrue($node_no_language->langcode == LANGUAGE_NOT_SPECIFIED, 'Node created with not specified language.');
+
+    // Query the nodes table as the web user with the node access tag and no
+    // specific langcode.
+    $select = db_select('node', 'n')
+      ->fields('n', array('nid'))
+      ->addMetaData('account', $web_user)
+      ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // The public node and no language node should be returned. Because no
+    // langcode is given it will use the fallback node.
+    $this->assertEqual(count($nids), 2, 'db_select() returns 2 node');
+    $this->assertTrue(array_key_exists($node_public->nid, $nids), 'Returned node ID is public node.');
+    $this->assertTrue(array_key_exists($node_no_language->nid, $nids), 'Returned node ID is no language node.');
+
+    // Query the nodes table as the web user with the node access tag and
+    // langcode de.
+    $select = db_select('node', 'n')
+      ->fields('n', array('nid'))
+      ->addMetaData('account', $web_user)
+      ->addMetaData('langcode', 'de')
+      ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Because no nodes are created in German, no nodes are returned.
+    $this->assertTrue(empty($nids), 'db_select() returns an empty result.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and no specific langcode.
+    $select = db_select('node', 'n')
+      ->fields('n', array('nid'))
+      ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // All nodes are returned.
+    $this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.');
+
+    // Query the nodes table as user 1 (full access) with the node access tag
+    // and langcode de.
+    $select = db_select('node', 'n')
+      ->fields('n', array('nid'))
+      ->addMetaData('langcode', 'de')
+      ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // All nodes are returned because node access tag is not invoked when
+    // the user is user 1.
+    $this->assertEqual(count($nids), 3, 'db_select() returns all three nodes.');
   }
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php
index 4f87348..73b3a44 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php
@@ -23,16 +23,6 @@ public static function getInfo() {
     );
   }
 
-  /**
-   * Asserts node_access() correctly grants or denies access.
-   */
-  function assertNodeAccess($ops, $node, $account) {
-    foreach ($ops as $op => $result) {
-      $msg = t("node_access returns @result with operation '@op'.", array('@result' => $result ? 'true' : 'false', '@op' => $op));
-      $this->assertEqual($result, node_access($op, $node, $account), $msg);
-    }
-  }
-
   function setUp() {
     parent::setUp();
     // Clear permissions for authenticated users.
@@ -76,4 +66,5 @@ function testNodeAccess() {
     $node5 = $this->drupalCreateNode();
     $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3);
   }
+
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php
index 1bba558..40121eb 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php
@@ -30,4 +30,35 @@ function setUp() {
       $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
     }
   }
+
+  /**
+   * Asserts that node_access() correctly grants or denies access.
+   *
+   * @param array $ops
+   *   An associative array of the expected node access grants for the node
+   *   and account, with each key as the name of an operation (e.g. 'view',
+   *  'delete') and each value a Boolean indicating whether access to that
+   *   operation should be granted.
+   * @param \Drupal\node\Plugin\Core\Entity\Node $node
+   *   The node object to check.
+   * @param \Drupal\user\Plugin\Core\Entity\User $account
+   *   The user account for which to check access.
+   * @param string|null $langcode
+   *   (optional) The language code indicating which translation of the node
+   *   to check. If NULL, the untranslated (fallback) access is checked.
+   */
+  function assertNodeAccess(array $ops, $node, $account, $langcode = NULL) {
+    foreach ($ops as $op => $result) {
+      $msg = format_string(
+        "node_access() returns @result with operation %op, language code %langcode.",
+        array(
+          '@result' => $result ? 'true' : 'false',
+          '%op' => $op,
+          '%langcode' => !empty($langcode) ? $langcode : 'empty'
+        )
+      );
+      $this->assertEqual($result, node_access($op, $node, $account, $langcode), $msg);
+    }
+  }
+
 }
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 5e5b270..7f508d5 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -234,11 +234,16 @@ function hook_node_grants($account, $op) {
  *   of this gid within this realm can edit this node.
  * - 'grant_delete': If set to 1 a user that has been identified as a member
  *   of this gid within this realm can delete this node.
- *
- *
- * When an implementation is interested in a node but want to deny access to
- * everyone, it may return a "deny all" grant:
- *
+ * - langcode: (optional) The language code of a specific translation of the
+ *   node, if any. Modules may add this key to grant different access to
+ *   different translations of a node, such that (e.g.) a particular group
+ *   is granted access to edit the Catalan version of the node, but not the
+ *   Hungarian version. If no value is provided, the langcode is set
+ *   set automatically from the $node parameter and the node's original
+ *   language (if specified) is used as a fallback.
+ *
+ * A "deny all" grant may be used to deny all access to a particular node or
+ * node translation:
  * @code
  * $grants[] = array(
  *   'realm' => 'all',
@@ -246,15 +251,14 @@ function hook_node_grants($account, $op) {
  *   'grant_view' => 0,
  *   'grant_update' => 0,
  *   'grant_delete' => 0,
- *   'priority' => 1,
+ *   'langcode' => 'ca',
  * );
  * @endcode
- *
- * Setting the priority should cancel out other grants. In the case of a
- * conflict between modules, it is safer to use hook_node_access_records_alter()
- * to return only the deny grant.
- *
- * Note: a deny all grant is not written to the database; denies are implicit.
+ * Note that another module node access module could override this by granting
+ * access to one or more nodes, since grants are additive. To enforce that
+ * access is denied in a particular case, use hook_node_access_records_alter().
+ * Also node that a deny all grant is not written to the database; denies are
+ * implicit.
  *
  * @param Drupal\node\Node $node
  *   The node that has just been saved.
@@ -271,8 +275,9 @@ function hook_node_access_records(Drupal\node\Node $node) {
   // treated just like any other node and we completely ignore it.
   if ($node->private) {
     $grants = array();
-    // Only published nodes should be viewable to all users. If we allow access
-    // blindly here, then all users could view an unpublished node.
+    // Only published Catalan translations of private nodes should be viewable
+    // to all users. If we fail to check $node->status, all users would be able
+    // to view an unpublished node.
     if ($node->status) {
       $grants[] = array(
         'realm' => 'example',
@@ -280,6 +285,7 @@ function hook_node_access_records(Drupal\node\Node $node) {
         'grant_view' => 1,
         'grant_update' => 0,
         'grant_delete' => 0,
+        'langcode' => 'ca'
       );
     }
     // For the example_author array, the GID is equivalent to a UID, which
@@ -292,6 +298,7 @@ function hook_node_access_records(Drupal\node\Node $node) {
       'grant_view' => 1,
       'grant_update' => 1,
       'grant_delete' => 1,
+      'langcode' => 'ca'
     );
 
     return $grants;
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index 12a6945..552a5a0 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -149,6 +149,20 @@ function node_schema() {
         'not null' => TRUE,
         'default' => 0,
       ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of this node.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'fallback' => array(
+        'description' => 'Boolean indicating whether this record should be used as a fallback if a language condition is not provided.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 1,
+      ),
       'gid' => array(
         'description' => "The grant ID a user must possess in the specified realm to gain this row's privileges on the node.",
         'type' => 'int',
@@ -188,7 +202,7 @@ function node_schema() {
         'size' => 'tiny',
       ),
     ),
-    'primary key' => array('nid', 'gid', 'realm'),
+    'primary key' => array('nid', 'gid', 'langcode', 'realm'),
     'foreign keys' => array(
       'affected_node' => array(
         'table' => 'node',
@@ -707,6 +721,34 @@ function node_update_8013() {
 }
 
 /**
+ * Add language.langcode and fallback field to node_access table.
+ */
+function node_update_8014() {
+  // Add the langcode field.
+  $langcode_field = array(
+    'type' => 'varchar',
+    'length' => 12,
+    'not null' => TRUE,
+    'default' => '',
+    'description' => 'The {language}.langcode of this node.',
+  );
+  db_add_field('node_access', 'langcode', $langcode_field);
+
+  // Add the fallback field.
+  $fallback_field = array(
+    'description' => 'Boolean indicating whether this record should be used as a fallback if a language condition is not provided.',
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'default' => 1,
+  );
+  db_add_field('node_access', 'fallback', $fallback_field);
+
+  db_drop_primary_key('node_access');
+  db_add_primary_key('node_access', array('nid', 'gid', 'langcode', 'realm'));
+}
+
+/**
  * @} End of "addtogroup updates-7.x-to-8.x"
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index b64a1dc..bbd6fa0 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -2606,7 +2606,24 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
   // to an empty langcode if a node type was requested. The latter is purely
   // for caching purposes.
   if (empty($langcode)) {
-    $langcode = (is_object($node) && isset($node->nid)) ? $node->langcode : '';
+    // Initialize the langcode as an empty string.
+    $langcode = '';
+    if (is_object($node) && isset($node->nid)) {
+      // Default to the node's default langcode.
+      $langcode = $node->langcode;
+      // If the Language module is enabled, try to use the language from
+      // content negotiation.
+      if (module_exists('language')) {
+        // Load languages the node exists in.
+        $node_translations = $node->getTranslationLanguages();
+        // Load the language from content negotiation.
+        $content_negotiation_langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
+        // If there is a translation available, use it.
+        if (isset($node_translations[$content_negotiation_langcode])) {
+          $langcode = $content_negotiation_langcode;
+        }
+      }
+    }
   }
 
   // If we've already checked access for this node, user and op, return from
@@ -2652,9 +2669,16 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
       $query = db_select('node_access');
       $query->addExpression('1');
       $query->condition('grant_' . $op, 1, '>=');
-      $nids = db_or()->condition('nid', $node->nid);
+      // Check if grant is given for the node in this language, so we check that grant.
+      $nids = db_and()
+        ->condition('nid', $node->nid)
+        ->condition('langcode', $langcode);
+      // But if the node is published, we take into account the default for this grant,
+      // which is saved with nid = 0.
       if ($node->status) {
-        $nids->condition('nid', 0);
+        $nids = db_or()
+          ->condition($nids)
+          ->condition('nid', 0);
       }
       $query->condition($nids);
       $query->range(0, 1);
@@ -2909,6 +2933,9 @@ function node_query_node_access_alter(AlterableInterface $query) {
   if (!$op = $query->getMetaData('op')) {
     $op = 'view';
   }
+  if (!$langcode = $query->getMetaData('langcode')) {
+    $langcode = FALSE;
+  }
 
   // If $account can bypass node access, or there are no node access modules,
   // or the operation is 'view' and the $account has a global view grant
@@ -2956,8 +2983,8 @@ function node_query_node_access_alter(AlterableInterface $query) {
        ->fields('na', array('nid'));
 
       $grant_conditions = db_or();
-      // If any grant exists for the specified user, then user has access
-      // to the node for the specified operation.
+      // If any grant exists for the specified user,
+      // then user has access to the node for the specified operation.
       foreach ($grants as $realm => $gids) {
         foreach ($gids as $gid) {
           $grant_conditions->condition(db_and()
@@ -2972,6 +2999,15 @@ function node_query_node_access_alter(AlterableInterface $query) {
         $subquery->condition($grant_conditions);
       }
       $subquery->condition('na.grant_' . $op, 1, '>=');
+      // If no langcode is given, add a condition for checking the fallback language. If the language is given,
+      // just use it as a condition.
+      if ($langcode === FALSE) {
+        $subquery->condition('na.fallback', 1, '=');
+      }
+      else {
+        $subquery->condition('na.langcode', $langcode, '=');
+      }
+
       $field = 'nid';
       // Now handle entities.
       $subquery->where("$nalias.$field = na.nid");
@@ -3022,11 +3058,8 @@ function node_access_acquire_grants(Node $node, $delete = TRUE) {
  * @param Drupal\node\Node $node
  *   The node whose grants are being written.
  * @param $grants
- *   A list of grants to write. Each grant is an array that must contain the
- *   following keys: realm, gid, grant_view, grant_update, grant_delete.
- *   The realm is specified by a particular module; the gid is as well, and
- *   is a module-defined id to define grant privileges. each grant_* field
- *   is a boolean value.
+ *   A list of grants to write. See hook_node_access_records() for the
+ *   expected structure of the grants array.
  * @param $realm
  *   (optional) If provided, read/write grants for that realm only. Defaults to
  *   NULL.
@@ -3045,18 +3078,35 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
     }
     $query->execute();
   }
-
   // Only perform work when node_access modules are active.
   if (!empty($grants) && count(module_implements('node_grants'))) {
-    $query = db_insert('node_access')->fields(array('nid', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
+    $query = db_insert('node_access')->fields(array('nid', 'langcode', 'fallback', 'realm', 'gid', 'grant_view', 'grant_update', 'grant_delete'));
+    // If we have defined a granted langcode, use it. But if not, add a grant for
+    // every language this node is translated to.
     foreach ($grants as $grant) {
       if ($realm && $realm != $grant['realm']) {
         continue;
       }
-      // Only write grants; denies are implicit.
-      if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
-        $grant['nid'] = $node->nid;
-        $query->values($grant);
+      if (isset($grant['langcode'])) {
+        $grant_languages = array($grant['langcode'] => language_load($grant['langcode']));
+      }
+      else {
+        $grant_languages = $node->getTranslationLanguages(TRUE);
+      }
+      foreach ($grant_languages as $grant_langcode => $grant_language) {
+        // Only write grants; denies are implicit.
+        if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
+          $grant['nid'] = $node->nid;
+          $grant['langcode'] = $grant_langcode;
+          // The record with the original langcode is used as the fallback.
+          if ($grant['langcode'] == $node->langcode) {
+            $grant['fallback'] = 1;
+          }
+          else {
+            $grant['fallback'] = 0;
+          }
+          $query->values($grant);
+        }
       }
     }
     $query->execute();
diff --git a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info
new file mode 100644
index 0000000..e8f72c5
--- /dev/null
+++ b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.info
@@ -0,0 +1,7 @@
+name = "Node module access tests language"
+description = "Support module for language-aware node access testing."
+package = Testing
+version = VERSION
+core = 8.x
+dependencies[] = options
+hidden = TRUE
diff --git a/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module
new file mode 100644
index 0000000..27e5883
--- /dev/null
+++ b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Test module with a language-aware node access implementation.
+ *
+ * The module adds a 'private' field to page nodes that allows each translation
+ * of the node to be marked as private (viewable only by administrators).
+ */
+
+use Drupal\node\Plugin\Core\Entity\Node;
+
+/**
+ * Implements hook_node_grants().
+ *
+ * This module defines a single grant realm. All users belong to this group.
+ */
+function node_access_test_language_node_grants($account, $op) {
+  $grants['node_access_language_test'] = array(7888);
+  return $grants;
+}
+
+/**
+ * Implements hook_node_access_records().
+ */
+function node_access_test_language_node_access_records(Node $node) {
+  $grants = array();
+
+  // Create grants for each translation of the node.
+  foreach ($node->getTranslationLanguages() as $langcode => $language) {
+    // If the translation is not marked as private, grant access.
+    $grants[] = array(
+      'realm' => 'node_access_language_test',
+      'gid' => 7888,
+      'grant_view' => empty($node->field_private[$langcode][0]['value']) ? 1 : 0,
+      'grant_update' => 0,
+      'grant_delete' => 0,
+      'priority' => 0,
+      'langcode' => $langcode,
+    );
+  }
+  return $grants;
+}
+
+/**
+ * Implements hook_enable().
+ *
+ * Creates the 'private' field, which allows the node to be marked as private
+ * (restricted access) in a given translation.
+ */
+function node_access_test_language_enable() {
+  $field_private = array(
+    'field_name' => 'field_private',
+    'type' => 'list_boolean',
+    'cardinality' => 1,
+    'translatable'  => TRUE,
+    'settings' => array(
+      'allowed_values' => array(0 => 'Not private', 1 => 'Private'),
+    ),
+  );
+  $field_private = field_create_field($field_private);
+
+  $instance = array(
+    'field_name' => $field_private['field_name'],
+    'entity_type' => 'node',
+    'bundle' => 'page',
+    'widget' => array(
+      'type' => 'options_buttons',
+    ),
+  );
+  $instance = field_create_instance($instance);
+}
+
+/**
+ * Implements hook_disable().
+ */
+function node_access_test_language_disable() {
+  field_delete_instance(field_read_instance('node', 'field_private', 'page'));
+}
