diff --git a/composer.json b/composer.json
index effce817..15714068 100644
--- a/composer.json
+++ b/composer.json
@@ -23,9 +23,6 @@
     "source": "http://git.drupal.org/project/search_api.git"
   },
   "license": "GPL-2.0+",
-  "require-dev": {
-    "drupal/search_api_autocomplete": "@dev"
-  },
   "suggest": {
     "drupal/facets": "Adds the ability to create faceted searches.",
     "drupal/search_api_autocomplete": "Allows adding autocomplete suggestions to search fields.",
diff --git a/search_api.info.yml b/search_api.info.yml
index ee63f766..2af16f9a 100644
--- a/search_api.info.yml
+++ b/search_api.info.yml
@@ -6,3 +6,6 @@ core: 8.x
 configure: search_api.overview
 dependencies:
   - drupal:system (>=8.5)
+test_dependencies:
+  - language_fallback_fix:language_fallback_fix
+  - search_api_autocomplete:search_api_autocomplete
diff --git a/search_api.views.inc b/search_api.views.inc
index 2426375c..c08c075d 100644
--- a/search_api.views.inc
+++ b/search_api.views.inc
@@ -178,6 +178,12 @@ function _search_api_views_get_handlers(FieldInterface $field) {
       $types[] = 'entity';
     }
 
+    // Special treatment for languages (as we have no specific Search API data
+    // type for those).
+    if ($definition->getSetting('views_type') === 'language') {
+      $types[] = 'language';
+    }
+
     if ($definition->getSetting('allowed_values')) {
       $types[] = 'options';
     }
@@ -290,6 +296,18 @@ function _search_api_views_handler_mapping() {
           'id' => 'search_api',
         ],
       ],
+      'language' => [
+        'argument' => [
+          'id' => 'search_api',
+          ],
+        'filter' => [
+          'id' => 'search_api_language',
+          'allow empty' => FALSE,
+          ],
+        'sort' => [
+          'id' => 'search_api',
+          ],
+        ],
       'options' => [
         'argument' => [
           'id' => 'search_api',
@@ -781,9 +799,9 @@ function _search_api_views_get_field_handler_for_property(DataDefinitionInterfac
     // Then check all the patterns defined by regular expressions, defaulting to
     // the "default" definition.
     $definition = $mappings['default'];
-    foreach (array_keys($mappings['regex']) as $regex) {
+    foreach ($mappings['regex'] as $regex => $mapping_definition) {
       if (preg_match($regex, $data_type)) {
-        $definition = $mappings['regex'][$regex];
+        $definition = $mapping_definition;
       }
     }
   }
@@ -875,6 +893,11 @@ function _search_api_views_get_field_handler_mapping() {
     $plain_mapping['boolean'] = $bool_mapping;
     $plain_mapping['field_item:boolean'] = $bool_mapping;
 
+    $language_mapping = [
+      'id' => 'language',
+    ];
+    $plain_mapping['language'] = $language_mapping;
+
     $ref_mapping = [
       'id' => 'search_api_entity',
     ];
diff --git a/src/Plugin/search_api/processor/LanguageWithFallback.php b/src/Plugin/search_api/processor/LanguageWithFallback.php
new file mode 100644
index 00000000..bec04707
--- /dev/null
+++ b/src/Plugin/search_api/processor/LanguageWithFallback.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Drupal\search_api\Plugin\search_api\processor;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\Item\ItemInterface;
+use Drupal\search_api\Processor\ProcessorPluginBase;
+use Drupal\search_api\Processor\ProcessorProperty;
+use Drupal\search_api\SearchApiException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Adds the item's language (with fallbacks) to the indexed data.
+ *
+ * @SearchApiProcessor(
+ *   id = "language_with_fallback",
+ *   label = @Translation("Language (with fallback)"),
+ *   description = @Translation("Adds the item's language to the indexed data, and considers language fallbacks."),
+ *   stages = {
+ *     "add_properties" = 0,
+ *   },
+ *   locked = true,
+ *   hidden = true,
+ * )
+ */
+class LanguageWithFallback extends ProcessorPluginBase {
+
+  /**
+   * The entity repository.
+   *
+   * @var \Drupal\Core\Entity\EntityRepositoryInterface
+   */
+  protected $entityRepository;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    /** @var static $processor */
+    $processor = parent::create($container, $configuration, $plugin_id, $plugin_definition);
+
+    $processor->setEntityRepository($container->get('entity.repository'));
+    $processor->setLanguageManager($container->get('language_manager'));
+
+    return $processor;
+  }
+
+  /**
+   * Retrieves the entity repository.
+   *
+   * @return \Drupal\Core\Entity\EntityRepositoryInterface
+   *   The entity repository.
+   */
+  public function getEntityRepository() {
+    return $this->entityRepository;
+  }
+
+  /**
+   * Sets the entity repository.
+   *
+   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository
+   *   The new entity repository.
+   *
+   * @return $this
+   */
+  public function setEntityRepository(EntityRepositoryInterface $entityRepository) {
+    $this->entityRepository = $entityRepository;
+    return $this;
+  }
+
+  /**
+   * Retrieves the language manager.
+   *
+   * @return \Drupal\Core\Language\LanguageManagerInterface
+   *   The language manager.
+   */
+  public function getLanguageManager() {
+    return $this->languageManager;
+  }
+
+  /**
+   * Sets the language manager.
+   *
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The new language manager.
+   *
+   * @return $this
+   */
+  public function setLanguageManager(LanguageManagerInterface $languageManager) {
+    $this->languageManager = $languageManager;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPropertyDefinitions(DatasourceInterface $datasource = NULL) {
+    $properties = [];
+
+    if (!$datasource) {
+      $definition = [
+        'label' => $this->t('Language (with fallback)'),
+        'description' => $this->t('The item language, or a language the item is a fallback for.'),
+        'type' => 'string',
+        'settings' => [
+          'views_type' => 'language',
+        ],
+        'processor_id' => $this->getPluginId(),
+        'is_list' => TRUE,
+      ];
+      $properties['search_api_language_with_fallback'] = new ProcessorProperty($definition);
+    }
+
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addFieldValues(ItemInterface $item) {
+    try {
+      $entity = $item->getOriginalObject()->getValue();
+    }
+    catch (SearchApiException $e) {
+      return;
+    }
+    if (!($entity instanceof ContentEntityInterface)) {
+      return;
+    }
+    $langcodes = $this->getReverseLanguageFallbacks($entity);
+
+    $fields = $item->getFields();
+    $fields = $this->getFieldsHelper()
+      ->filterForPropertyPath($fields, NULL, 'search_api_language_with_fallback');
+    foreach ($fields as $field) {
+      foreach ($langcodes as $langcode) {
+        $field->addValue($langcode);
+      }
+    }
+  }
+
+  /**
+   * Retrieves all langcodes that fall back to the given entity translation.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity translation.
+   *
+   * @return string[]
+   *   The codes of the languages for which the given entity is the fallback.
+   */
+  protected function getReverseLanguageFallbacks(ContentEntityInterface $entity) {
+    $entityLangcode = $entity->language()->getId();
+
+    $reverseFallbackLangcodes = [$entityLangcode];
+    foreach ($this->languageManager->getLanguages() as $langcode => $language) {
+      if ($langcode === $entityLangcode) {
+        continue;
+      }
+      $context = [
+        // The fallback_to_passed_entity is recognized by language_fallback_fix
+        // module and does not change anything if that is not installed.
+        // It allows to have languages without fallback and will hopefully be
+        // fixed in core this way or another.
+        // @see https://www.drupal.org/node/2951294#comment-13127796
+        'fallback_to_passed_entity' => FALSE,
+        // We use the entity_upcast operation here, as for the entity_view
+        // operation, content_translation removes fallbacks that the current
+        // user does not have access, which would lead to indexing dependent of
+        // user access.
+        // @see content_translation_language_fallback_candidates_entity_view_alter()
+        'operation' => 'entity_upcast',
+      ];
+      $fallback = $this->entityRepository->getTranslationFromContext($entity, $langcode, $context);
+      if ($fallback && $fallback->language()->getId() === $entityLangcode) {
+        $reverseFallbackLangcodes[] = $langcode;
+      }
+    }
+
+    return $reverseFallbackLangcodes;
+  }
+
+}
diff --git a/src/Plugin/views/filter/SearchApiLanguage.php b/src/Plugin/views/filter/SearchApiLanguage.php
index 6fe5703a..ba394aab 100644
--- a/src/Plugin/views/filter/SearchApiLanguage.php
+++ b/src/Plugin/views/filter/SearchApiLanguage.php
@@ -29,10 +29,11 @@ public function query() {
     // Only set the languages using $query->setLanguages() if the condition
     // would be placed directly on the query, as an AND condition.
     $query = $this->getQuery();
-    $direct_condition = $this->operator == 'in'
+    $direct_language_condition = $this->realField === 'search_api_language'
+      && $this->operator == 'in'
       && $query->getGroupType($this->options['group'])
       && $query->getGroupOperator() == 'AND';
-    if ($direct_condition) {
+    if ($direct_language_condition) {
       $query->setLanguages($this->value);
     }
     else {
diff --git a/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml b/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml
new file mode 100644
index 00000000..9d255d26
--- /dev/null
+++ b/tests/search_api_test_language_fallback/search_api_test_language_fallback.info.yml
@@ -0,0 +1,6 @@
+type: module
+name: 'Language Fallback Test'
+description: 'Provides a language fallback fr => es.'
+package: 'Search API'
+core: 8.x
+hidden: true
diff --git a/tests/search_api_test_language_fallback/search_api_test_language_fallback.module b/tests/search_api_test_language_fallback/search_api_test_language_fallback.module
new file mode 100644
index 00000000..8398a31f
--- /dev/null
+++ b/tests/search_api_test_language_fallback/search_api_test_language_fallback.module
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Provides a language fallback for tests.
+ */
+
+/**
+ * Implements hook_language_fallback_candidates_alter().
+ */
+function search_api_test_language_fallback_language_fallback_candidates_alter(array &$candidates, array $context) {
+  $attempted_langcode = $context['langcode'];
+  $candidates = [$attempted_langcode => $attempted_langcode];
+  if ($attempted_langcode === 'fr') {
+    $candidates['es'] = 'es';
+  }
+}
diff --git a/tests/src/Functional/ProcessorIntegrationTest.php b/tests/src/Functional/ProcessorIntegrationTest.php
index 0f89973c..81729e64 100644
--- a/tests/src/Functional/ProcessorIntegrationTest.php
+++ b/tests/src/Functional/ProcessorIntegrationTest.php
@@ -98,7 +98,12 @@ public function setUp() {
    */
   public function testProcessorIntegration() {
     // Some processors are always enabled.
-    $enabled = ['add_url', 'aggregated_field', 'rendered_item'];
+    $enabled = [
+      'add_url',
+      'aggregated_field',
+      'language_with_fallback',
+      'rendered_item'
+    ];
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
     $this->assertEquals($enabled, $actual_processors);
@@ -110,6 +115,8 @@ public function testProcessorIntegration() {
       $this->assertSession()->responseNotContains(Html::escape($processor_id));
     }
 
+    $this->checkLanguageWithFallbackIntegration();
+
     $this->checkAggregatedFieldsIntegration();
 
     $this->checkContentAccessIntegration();
@@ -313,6 +320,28 @@ public function testLimitProcessors() {
     $this->assertSession()->pageTextNotContains('Stopwords');
   }
 
+  /**
+   * Tests the integration of the "Language (with fallback)" processor.
+   */
+  protected function checkLanguageWithFallbackIntegration() {
+    // Test that the processor is locked.
+    $index = $this->loadIndex();
+    $index->removeProcessor('language_with_fallback');
+    $index->save();
+    $this->assertTrue($this->loadIndex()->isValidProcessor('language_with_fallback'), 'The "Language (with fallback)" processor cannot be disabled.');
+
+    // Add a language_with_fallback field.
+    $options['query']['datasource'] = '';
+    $this->drupalGet($this->getIndexPath('fields/add/nojs'), $options);
+    // See \Drupal\search_api\Tests\IntegrationTest::addField().
+    $this->assertSession()->responseContains('name="language_with_fallback"');
+    $this->submitForm([], 'language_with_fallback');
+    $args['%label'] = 'Language (with fallback)';
+    $this->assertSession()->responseContains(new FormattableMarkup('Field %label was added to the index.', $args));
+    $this->assertSession()->addressEquals($this->getIndexPath('fields'));
+    $this->assertSession()->responseContains('The field configuration was successfully saved.');
+  }
+
   /**
    * Tests the integration of the "Aggregated fields" processor.
    */
diff --git a/tests/src/Kernel/Processor/LanguageWithFallbackKernelTest.php b/tests/src/Kernel/Processor/LanguageWithFallbackKernelTest.php
new file mode 100644
index 00000000..92804b18
--- /dev/null
+++ b/tests/src/Kernel/Processor/LanguageWithFallbackKernelTest.php
@@ -0,0 +1,202 @@
+<?php
+
+namespace Drupal\Tests\search_api\Kernel\Processor;
+
+use Drupal\node\Entity\NodeType;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\node\Entity\Node;
+use Drupal\search_api\Item\Field;
+use Drupal\search_api\Utility\Utility;
+use Drupal\Tests\search_api\Kernel\PostRequestIndexingTrait;
+
+/**
+ * Tests the "Language (with fallback)" processor at a higher level.
+ *
+ * @group search_api
+ *
+ * @coversDefaultClass \Drupal\search_api\Plugin\search_api\processor\LanguageWithFallback
+ */
+class LanguageWithFallbackKernelTest extends ProcessorTestBase {
+
+  use PostRequestIndexingTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'language',
+    'search_api_test_language_fallback',
+    'language_fallback_fix',
+  ];
+
+  /**
+   * The test node.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $node;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp($processor = NULL) {
+    parent::setUp('language_with_fallback');
+
+    // search_api_test_language_fallback.module adds a fallback from 'fr' to
+    // 'es'. When we then leave 'en' as site default language and set 'de' as
+    // original node language, we are able to spot false fallbacks to either of
+    // those.
+    foreach (['de', 'fr', 'es'] as $langcode) {
+      ConfigurableLanguage::createFromLangcode($langcode)->enable()->save();
+    }
+
+    NodeType::create([
+      'type' => 'article',
+    ])->save();
+
+    $lwf_field = new Field($this->index, 'language_with_fallback');
+    $lwf_field->setType('string');
+    $lwf_field->setPropertyPath('search_api_language_with_fallback');
+    $lwf_field->setLabel('Language (with fallback)');
+    $this->index->addField($lwf_field);
+    $this->index->setOption('index_directly', TRUE);
+    $this->index->save();
+  }
+
+  /**
+   * Tests indexing.
+   *
+   * Expected fallbacks: search_api_test_language_fallback.module has these:
+   * - no fallbacks
+   * - except 'fr' has fallback 'es'
+   *
+   * Note that language_fallback_fix.module (which is a test dependency) ensures
+   * that there can be languages without fallback, which we test here.
+   *
+   * @covers ::addFieldValues
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   */
+  public function testIndexing() {
+    $nodeValues = [
+      'title' => 'Test',
+      'type' => 'article',
+    ];
+
+    // First test with a German node.
+    $node = Node::create($nodeValues + ['langcode' => 'de']);
+    $node->save();
+    $this->node = $node;
+
+    $this->triggerPostRequestIndexing();
+    $expected[$this->getItemIdForLanguage('de')] = ['de'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Added default translation is indexed correctly.');
+
+    $node->addTranslation('es', $nodeValues);
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    $expected[$this->getItemIdForLanguage('es')] = ['es', 'fr'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Added translation with fallback is indexed correctly.');
+
+    $node->addTranslation('fr', $nodeValues);
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    $expected[$this->getItemIdForLanguage('es')] = ['es'];
+    $expected[$this->getItemIdForLanguage('fr')] = ['fr'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Added translation is indexed correctly and former fallback removed.');
+
+    $node->removeTranslation('fr');
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    unset($expected[$this->getItemIdForLanguage('fr')]);
+    $expected[$this->getItemIdForLanguage('es')] = ['es', 'fr'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Removed translation is unindexed correctly and fallback re-added.');
+
+    $node->removeTranslation('es');
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    unset($expected[$this->getItemIdForLanguage('es')]);
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Removed translation is unindexed correctly.');
+
+    $node->delete();
+    $this->triggerPostRequestIndexing();
+    $expected = [];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Removed default translation is unindexed correctly.');
+
+    // Then test with a Spanish node.
+    $node = Node::create($nodeValues + ['langcode' => 'es']);
+    $node->save();
+    $this->node = $node;
+
+    $this->triggerPostRequestIndexing();
+    $expected[$this->getItemIdForLanguage('es')] = ['es', 'fr'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Added default translation with fallback is indexed correctly.');
+
+    $node->addTranslation('de', $nodeValues);
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    $expected[$this->getItemIdForLanguage('de')] = ['de'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Added translation is indexed correctly.');
+
+    $node->addTranslation('fr', $nodeValues);
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    $expected[$this->getItemIdForLanguage('es')] = ['es'];
+    $expected[$this->getItemIdForLanguage('fr')] = ['fr'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Added translation is indexed correctly and former fallback removed.');
+
+    $node->removeTranslation('de');
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    unset($expected[$this->getItemIdForLanguage('de')]);
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Removed translation is unindexed correctly.');
+
+    $node->removeTranslation('fr');
+    $node->save();
+    $this->triggerPostRequestIndexing();
+    unset($expected[$this->getItemIdForLanguage('fr')]);
+    $expected[$this->getItemIdForLanguage('es')] = ['es', 'fr'];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Removed translation is unindexed correctly and fallback re-added.');
+
+    $node->delete();
+    $this->triggerPostRequestIndexing();
+    $expected = [];
+    $this->assertEquals($expected, $this->getLanguageWithFallbackValues(), 'Removed default translation is unindexed correctly.');
+  }
+
+  /**
+   * Retrieves the indexed values.
+   *
+   * @return array
+   *   The indexed "language_with_fallback" field values for all indexed items,
+   *   keyed by item ID.
+   */
+  protected function getLanguageWithFallbackValues() {
+    $query = $this->index->query();
+    // We don't need a query condition as we have only one node anyway.
+    $results = $query->execute();
+    $values = [];
+    /** @var \Drupal\search_api\Item\ItemInterface $result */
+    foreach ($results as $result) {
+      $fieldValues = $result->getField('language_with_fallback')->getValues();
+      sort($fieldValues);
+      $values[$result->getId()] = $fieldValues;
+    }
+    return $values;
+  }
+
+  /**
+   * Retrieves the test node's item ID for the given language.
+   *
+   * @param string $langcode
+   *   The language's code.
+   *
+   * @return string
+   *   The Search API item ID for the test node in the given language.
+   */
+  protected function getItemIdForLanguage($langcode) {
+    $nid = $this->node->id();
+    return Utility::createCombinedId('entity:node', "$nid:$langcode");
+  }
+
+}
