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..beb97bf
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareCombinationTest.php
@@ -0,0 +1,355 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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');
+
+  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);
+  }
+
+  /**
+   * Tests the node_access() function with multiple node languages.
+   */
+  function testNodeAccessLanguageAwareCombination() {
+    $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 Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is public, non language aware
+    // node_access module public.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    $this->assertNodeAccess($expected_node_access, $node, $web_user);
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is public, non language aware
+    // node_access module private.
+    // Even the non language aware node_access module (private) prevents access,
+    // still the settings of the language aware module (field_private) will
+    // be used. Because it generates grants also when access is granted.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    // language aware node_access module Mmdule specificly grants access to
+    // languages versions of nodes. That is why non language aware node_access
+    // module cannot prevent access from it.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user);
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is public, non language aware
+    // node_access module public.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is private, non language aware
+    // node_access module public.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    $this->assertNodeAccess($expected_node_access, $node, $web_user);
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is private, non language aware
+    // node_access module public.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is private, non language aware
+    // node_access module private.
+    $node = $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,
+    ));
+    $this->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user);
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'hu');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'ca');
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+  }
+
+  /**
+   * Tests db_select() with a 'node_access' tag and langcode metadata.
+   */
+  function testNodeAccessLanguageAwareQueryTag() {
+    $web_user = $this->drupalCreateUser(array('access content'));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is public, non language aware
+    // node_access module public.
+    $node_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,
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is public, non language aware
+    // node_access module private.
+    // Even the non language aware node_access module (private) prevents access,
+    // still the settings of the language aware module (field_private) will
+    // be used. Because it generates grants also when access is granted.
+    $node_both_public_non_language_aware_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' => 0)),
+      ),
+      'private' => TRUE,
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is public, non language aware
+    // node_access module public.
+    $node_only_hu_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' => 1)),
+      ),
+      'private' => FALSE,
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is private, non language aware
+    // node_access module public.
+    $node_only_ca_public = $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,
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is private, non language aware
+    // node_access module public.
+    // Even the non language aware node_access module (private) grants access,
+    // still the settings of the language aware module (field_private) will
+    // be used. Because the non language aware node_access module (private)
+    // does only create grants when it is settings is private.
+    $node_no_public_non_language_aware_public = $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,
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is private, non language aware
+    // node_access module public.
+    $node_no_public = $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,
+    ));
+
+    // Query the nodes table as readonly user with 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');
+
+    // Because no langcode is given it will use the fallback language
+    // (which is hungarian).
+    $this->assertEqual(count($nids), 3, 'db_select returns 3 nodes');
+    $this->assertTrue(array_key_exists($node_both_public->nid, $nids), 'Returned node ID is full public node.');
+    $this->assertTrue(array_key_exists($node_only_hu_public->nid, $nids), 'Returned node ID is Hungarian public only node.');
+    $this->assertTrue(array_key_exists($node_both_public_non_language_aware_private->nid, $nids), 'Returned node ID is both public non language aware private only node.');
+
+    // Query the nodes table as readonly user with Node Access Tag and
+    // Hungarian langcode
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $web_user)
+    ->addMetaData('langcode', 'hu')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    $this->assertEqual(count($nids), 3, 'db_select returns 3 nodes');
+    $this->assertTrue(array_key_exists($node_both_public->nid, $nids), 'Returned node ID is both public node.');
+    $this->assertTrue(array_key_exists($node_only_hu_public->nid, $nids), 'Returned node ID is Hungarian public only node.');
+    $this->assertTrue(array_key_exists($node_both_public_non_language_aware_private->nid, $nids), 'Returned node ID is both public non language aware private only node.');
+
+    // Query the nodes table as readonly user with Node Access Tag and
+    // Catalan langcode
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $web_user)
+    ->addMetaData('langcode', 'ca')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    $this->assertEqual(count($nids), 3, 'db_select returns 3 nodes');
+    $this->assertTrue(array_key_exists($node_both_public->nid, $nids), 'Returned node ID is both public node.');
+    $this->assertTrue(array_key_exists($node_only_ca_public->nid, $nids), 'Returned node ID is Catalan public only node.');
+    $this->assertTrue(array_key_exists($node_both_public_non_language_aware_private->nid, $nids), 'Returned node ID is both public non language aware private only node.');
+
+
+    // Query the nodes table as readonly User with 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 all created nodes are not in German, no Nodes are returned.
+    $this->assertTrue(empty($nids), 'db_select returns empty result');
+
+
+    // Query the nodes table as User 1 (full access) with Node Access Tag and no
+    // specific langcode.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Both nodes are returned.
+    $this->assertEqual(count($nids), 6, 'db_select returns all nodes');
+
+    // Query the nodes table as User 1 (full access) with 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');
+
+    // Both nodes are returned because node access tag is not invoked 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..46d200f
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessLanguageAwareTest.php
@@ -0,0 +1,289 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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');
+
+  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();
+
+    // Add Hungarian and Catalan.
+    $language = new Language(array(
+      'langcode' => 'hu',
+    ));
+    language_save($language);
+    $language = new Language(array(
+      'langcode' => 'ca',
+    ));
+    language_save($language);
+  }
+
+  /**
+   * Tests node_access() with multiple node languages.
+   */
+  function testNodeAccessLanguageAware() {
+    $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 Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is public.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    // Tests that access to default language (Hungarian) is granted.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user);
+
+    // Tests that access to hungarian language is granted.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+
+    // Tests that access to catalan language is granted.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca');
+
+    // Tests that access to english language is granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is public.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    // Tests that access to default language (Hungarian) is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user);
+
+    // Tests that access to hungarian language is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'hu');
+
+    // Tests that access to catalan language is granted.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'ca');
+
+    // Tests that access to english language is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is private.
+    $node = $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->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    // Tests that access to default language (Hungarian) is granted.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user);
+
+    // Tests that access to hungarian language is granted.
+    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+
+    // Tests that access to catalan language is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'ca');
+
+    // Tests that access to english language is granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is private.
+    $node = $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)),
+      )
+    ));
+    $this->assertTrue($node->langcode == 'hu', t('Node created as Hungarian.'));
+
+    // Tests that access to default language (Hungarian) is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user);
+
+    // Tests that access to hungarian language is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'hu');
+
+    // Tests that access to catalan language is not granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'ca');
+
+    // Tests that access to english language is granted.
+    $this->assertNodeAccess($expected_node_access_no_access, $node, $web_user, 'en');
+  }
+
+  /**
+   * Tests db_select() with a 'node_access' tag and langcode metadata.
+   */
+  function testNodeAccessLanguageAwareQueryTag() {
+    $web_user = $this->drupalCreateUser(array('access content'));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is public.
+    $node_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)),
+      )
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is public.
+    $node_only_hu_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' => 1)),
+      )
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is public, Catalan is private.
+    $node_only_ca_public = $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)),
+      )
+    ));
+
+    // Creating a Node with default langcode Hungarian and translation Catalan.
+    // Hungarian language is private, Catalan is private.
+    $node_no_public = $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)),
+      )
+    ));
+
+    // Query the nodes table as readonly user with 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');
+
+    // Because no langcode is given it will use the fallback language
+    // (which is hungarian).
+    $this->assertEqual(count($nids), 2, 'db_select returns 2 nodes');
+    $this->assertTrue(array_key_exists($node_both_public->nid, $nids), 'Returned node ID is full public node.');
+    $this->assertTrue(array_key_exists($node_only_hu_public->nid, $nids), 'Returned node ID is Hungarian public only node.');
+
+    // Query the nodes table as readonly user with Node Access Tag and
+    // Hungarian langcode
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $web_user)
+    ->addMetaData('langcode', 'hu')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Same nodes as witouth langcode should be returned.
+    $this->assertEqual(count($nids), 2, 'db_select returns 2 nodes');
+    $this->assertTrue(array_key_exists($node_both_public->nid, $nids), 'Returned node ID is both public node.');
+    $this->assertTrue(array_key_exists($node_only_hu_public->nid, $nids), 'Returned node ID is Hungarian public only node.');
+
+    // Query the nodes table as readonly user with Node Access Tag and
+    // Catalan langcode
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addMetaData('account', $web_user)
+    ->addMetaData('langcode', 'ca')
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Only the Catalan and the Full Public node should be returned.
+    $this->assertEqual(count($nids), 2, 'db_select returns 2 nodes');
+    $this->assertTrue(array_key_exists($node_both_public->nid, $nids), 'Returned node ID is both public node.');
+    $this->assertTrue(array_key_exists($node_only_ca_public->nid, $nids), 'Returned node ID is Catalan public only node.');
+
+
+    // Query the nodes table as readonly User with 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 all created nodes are not in German, no Nodes are returned.
+    $this->assertTrue(empty($nids), 'db_select returns empty result');
+
+
+    // Query the nodes table as User 1 (full access) with Node Access Tag and no
+    // specific langcode.
+    $select = db_select('node', 'n')
+    ->fields('n', array('nid'))
+    ->addTag('node_access');
+    $nids = $select->execute()->fetchAllAssoc('nid');
+
+    // Both nodes are returned.
+    $this->assertEqual(count($nids), 4, 'db_select returns all nodes');
+
+    // Query the nodes table as User 1 (full access) with 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');
+
+    // Both nodes are returned because node access tag is not invoked 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..d379334 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,190 @@ function testNodeAccess() {
       'langcode' => 'ca',
     ));
     language_save($language);
+  }
 
-    // Tests the default access provided for a published Hungarian node.
+  /**
+   * Tests node_access() with multiple node languages and no private nodes.
+   */
+  function testNodeAccess() {
     $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 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, $web_user, 'hu');
+    $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');
 
     // 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_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 Hungarian is still accessible.
-    $this->assertNodeAccess($expected_node_access, $node, $web_user, 'hu');
+    // 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 not accessible anymore.
-    $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node, $web_user, 'ca');
+    // 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'));
+
+    $expected_node_access = array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE);
+    $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_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 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 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'));
+
+    // 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 readonly user with 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 itwill 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 readonly User with 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 empty result');
+
+    // Query the nodes table as User 1 (full access) with 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 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 c139952..9624dd8 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -231,11 +231,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',
@@ -243,15 +248,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.
@@ -268,8 +272,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',
@@ -277,6 +282,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
@@ -289,6 +295,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 f9c30a5..3ba3d30 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',
@@ -740,6 +754,35 @@ function node_update_8011() {
 }
 
 /**
+ * Add language.langcode and fallback field to node_access table.
+ */
+function node_update_8012() {
+  // 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 a6a64ae..22f4931 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -2781,7 +2781,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
@@ -2827,9 +2844,13 @@ 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);
+      $nids = db_and()
+        ->condition('nid', $node->nid)
+        ->condition('langcode', $langcode);
       if ($node->status) {
-        $nids->condition('nid', 0);
+        $nids = db_or()
+          ->condition($nids)
+          ->condition('nid', 0);
       }
       $query->condition($nids);
       $query->range(0, 1);
@@ -3065,6 +3086,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 $acount has a global view grant (i.e.,
@@ -3128,6 +3152,13 @@ function node_query_node_access_alter(AlterableInterface $query) {
         $subquery->condition($grant_conditions);
       }
       $subquery->condition('na.grant_' . $op, 1, '>=');
+      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");
@@ -3178,11 +3209,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.
@@ -3204,7 +3232,7 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
 
   // 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'));
     foreach ($grants as $grant) {
       if ($realm && $realm != $grant['realm']) {
         continue;
@@ -3212,6 +3240,16 @@ function _node_access_write_grants(Node $node, $grants, $realm = NULL, $delete =
       // Only write grants; denies are implicit.
       if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
         $grant['nid'] = $node->nid;
+        if (!isset($grant['langcode'])) {
+          $grant['langcode'] = $node->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);
       }
     }
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..b5c7880
--- /dev/null
+++ b/core/modules/node/tests/modules/node_access_test_language/node_access_test_language.module
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Test module with a language-aware node access implementation.
+ *
+ * This module implements language-aware node access hooks to test the node
+ * access API. This module restricts view permission to those with a special
+ * 'node test view' permission.
+ */
+
+use Drupal\node\Plugin\Core\Entity\Node;
+
+/**
+ * Implements hook_node_grants().
+ */
+function node_access_test_language_node_grants($account, $op) {
+  $grants = array();
+  // First grant a grant to the author for own content.
+  $grants['node_access_test_language_author'] = array($account->uid);
+  if ($op == 'view' && user_access('node access language test view', $account)) {
+    $grants['node_access_language_test'] = array(7888, 7889);
+  }
+
+  $no_access_uid = state()->get('node_access_language_test.no_access_uid') ?: 0;
+  if ($op == 'view' && $account->uid == $no_access_uid) {
+    $grants['node_access_language_all'] = array(0);
+  }
+  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 ($node->field_private[$langcode][0]['value']) {
+      $grants[] = array(
+        'realm' => 'node_access_language_test',
+        'gid' => 7888,
+        'grant_view' => 1,
+        'grant_update' => 0,
+        'grant_delete' => 0,
+        'priority' => 0,
+        'langcode' => $langcode,
+      );
+      $grants[] = array(
+        'realm' => 'node_access_language_test',
+        'gid' => 7889,
+        'grant_view' => 1,
+        'grant_update' => 0,
+        'grant_delete' => 0,
+        'priority' => 0,
+        'langcode' => $langcode,
+      );
+      // For the author realm, the GID is equivalent to a UID, which
+      // means there are many many groups of just 1 user.
+      $grants[] = array(
+        'realm' => 'node_access_test_language_author',
+        'gid' => $node->uid,
+        'grant_view' => 1,
+        'grant_update' => 1,
+        'grant_delete' => 1,
+        'priority' => 0,
+        'langcode' => $langcode,
+      );
+    } else {
+      $grants[] = array('realm' => 'all', 'gid' => 0, 'grant_view' => 1, 'grant_update' => 0, 'grant_delete' => 0, 'langcode' => $langcode);
+    }
+  }
+  return $grants;
+}
+
+/**
+ * Implements hook_permission().
+ *
+ * Sets up permissions for this module.
+ */
+function node_access_test_language_permission() {
+  return array('node access language test view' => array('title' => 'View content'));
+}
+
+/**
+ * Implements hook_disable().
+ */
+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'));
+}
