diff --git a/entity_language_fallback.module b/entity_language_fallback.module
index 4230004..a5409fd 100644
--- a/entity_language_fallback.module
+++ b/entity_language_fallback.module
@@ -5,69 +5,28 @@
  * Add fallback languages to entities.
  */
 
-use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\entity_language_fallback\Hook\EntityLanguageFallbackHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\entity_language_fallback\AccessHelper;
-use Drupal\entity_language_fallback\Plugin\search_api\datasource\ContentEntityFallback;
 use Drupal\language\ConfigurableLanguageInterface;
 use Drupal\search_api\IndexInterface;
 
 /**
  * Implements hook_language_fallback_candidates_alter().
  */
+#[LegacyHook]
 function entity_language_fallback_language_fallback_candidates_alter(array &$candidates, array $context) {
-  $operation = $context['operation'];
-
-  if ($operation == 'entity_upcast' || $operation == 'entity_view') {
-    /** @var \Drupal\entity_language_fallback\FallbackController $fallback_controller */
-    $fallback_controller = \Drupal::service('language_fallback.controller');
-    if ($new_candidates = $fallback_controller->getEntityFallbackCandidates($context['data'], $context['langcode'])) {
-      $candidates = $new_candidates;
-    }
-  }
+  \Drupal::service(EntityLanguageFallbackHooks::class)->languageFallbackCandidatesAlter($candidates, $context);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function entity_language_fallback_form_language_admin_edit_form_alter(&$form, FormStateInterface $form_state) {
-  /** @var Drupal\language\Entity\ConfigurableLanguage $this_language */
-  $this_language = $form_state->getFormObject()->getEntity();
-
-  $languages = Drupal::languageManager()->getLanguages();
-  $options = [];
-  foreach ($languages as $language) {
-    // Only include this language if its not itself.
-    if ($language->getId() != $this_language->getId()) {
-      $options[$language->getId()] = $language->getName();
-    }
-  }
-
-  $form['entity_language_fallback'] = [
-    '#title' => t('Entity fallback language'),
-    '#description' => t('Choose one or more fallback languages in prioritized order. The languages are used as fallback in entity view.'),
-    '#type' => 'details',
-    '#open' => TRUE,
-    '#tree' => TRUE,
-  ];
-
-  // Creating one priority field per available language.
-  $default_values = $this_language->getThirdPartySetting('entity_language_fallback', 'fallback_langcodes', []);
-  for ($i = 0; $i < count($options); $i++) {
-    $form['entity_language_fallback'][$i] = [
-      '#type' => 'select',
-      '#title' => t('Priority @priority', ['@priority' => $i + 1]),
-      '#description' => t('Choose the language used as priority @priority fallback language.', ['@priority' => $i + 1]),
-      '#options' => $options,
-      '#default_value' => !empty($default_values[$i]) ? $default_values[$i] : '',
-      '#empty_option' => t('-None-'),
-      '#tree' => TRUE,
-    ];
-  }
-
-  $form['#entity_builders'][] = 'entity_language_fallback_form_language_admin_edit_form_builder';
+  \Drupal::service(EntityLanguageFallbackHooks::class)->formLanguageAdminEditFormAlter($form, $form_state);
 }
 
 /**
@@ -88,8 +47,9 @@ function entity_language_fallback_form_language_admin_edit_form_builder($entity_
 /**
  * Implements hook_entity_access().
  */
+#[LegacyHook]
 function entity_language_fallback_entity_access($entity, $operation, AccountInterface $account) {
-  return AccessHelper::checkAccess($entity, $operation, $account);
+  return \Drupal::service(EntityLanguageFallbackHooks::class)->entityAccess($entity, $operation, $account);
 }
 
 /**
@@ -99,33 +59,9 @@ function entity_language_fallback_entity_access($entity, $operation, AccountInte
  *
  * @see search_api_entity_insert()
  */
+#[LegacyHook]
 function entity_language_fallback_entity_insert(EntityInterface $entity) {
-  if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
-    return;
-  }
-  // Check if the entity is a content entity.
-  if (!($entity instanceof ContentEntityInterface) || $entity->search_api_skip_tracking) {
-    return;
-  }
-  $indexes = ContentEntityFallback::getIndexesForEntity($entity);
-  if (!$indexes) {
-    return;
-  }
-
-  // Compute the item IDs for all languages set up on the fallback chain.
-  $item_ids = [];
-  $entity_id = $entity->id();
-  $fallback_controller = \Drupal::service('language_fallback.controller');
-  $fallback_languages = array_keys($fallback_controller->getTranslations($entity));
-
-  foreach ($fallback_languages as $langcode) {
-    $item_ids[] = $entity_id . ':' . $langcode;
-  }
-  $datasource_id = 'entity_language_fallback:' . $entity->getEntityTypeId();
-  foreach ($indexes as $index) {
-    $filtered_item_ids = ContentEntityFallback::filterValidItemIds($index, $datasource_id, $item_ids);
-    $index->trackItemsInserted($datasource_id, $filtered_item_ids);
-  }
+  \Drupal::service(EntityLanguageFallbackHooks::class)->entityInsert($entity);
 }
 
 /**
@@ -136,63 +72,9 @@ function entity_language_fallback_entity_insert(EntityInterface $entity) {
  *
  * @see search_api_entity_update()
  */
+#[LegacyHook]
 function entity_language_fallback_entity_update(EntityInterface $entity) {
-  if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
-    return;
-  }
-  // Check if the entity is a content entity.
-  if (!($entity instanceof ContentEntityInterface) || $entity->search_api_skip_tracking) {
-    return;
-  }
-  $indexes = ContentEntityFallback::getIndexesForEntity($entity);
-  if (!$indexes) {
-    return;
-  }
-
-  /** @var \Drupal\entity_language_fallback\FallbackControllerInterface $fallback_controller */
-  static $fallback_controller;
-  if (!isset($fallback_controller)) {
-    $fallback_controller = \Drupal::service('language_fallback.controller');
-  }
-
-  // Compare old and new languages for the entity to identify inserted,
-  // updated and deleted translations (and, therefore, search items).
-  $entity_id = $entity->id();
-  $inserted_item_ids = [];
-  $updated_item_ids = $fallback_controller->getTranslations($entity);
-  $deleted_item_ids = [];
-  $old_translations = $fallback_controller->getTranslations($entity->original);
-  foreach ($old_translations as $langcode => $language) {
-    if (!isset($updated_item_ids[$langcode])) {
-      $deleted_item_ids[] = $langcode;
-    }
-  }
-  foreach ($updated_item_ids as $langcode => $language) {
-    if (!isset($old_translations[$langcode])) {
-      unset($updated_item_ids[$langcode]);
-      $inserted_item_ids[] = $langcode;
-    }
-  }
-
-  $datasource_id = 'entity_language_fallback:' . $entity->getEntityTypeId();
-  $combine_id = function ($langcode) use ($entity_id) {
-    return $entity_id . ':' . $langcode;
-  };
-  $inserted_item_ids = array_map($combine_id, $inserted_item_ids);
-  $updated_item_ids = array_map($combine_id, array_keys($updated_item_ids));
-  $deleted_item_ids = array_map($combine_id, $deleted_item_ids);
-  foreach ($indexes as $index) {
-    if ($inserted_item_ids) {
-      $filtered_item_ids = ContentEntityFallback::filterValidItemIds($index, $datasource_id, $inserted_item_ids);
-      $index->trackItemsInserted($datasource_id, $filtered_item_ids);
-    }
-    if ($updated_item_ids) {
-      $index->trackItemsUpdated($datasource_id, $updated_item_ids);
-    }
-    if ($deleted_item_ids) {
-      $index->trackItemsDeleted($datasource_id, $deleted_item_ids);
-    }
-  }
+  return \Drupal::service(EntityLanguageFallbackHooks::class)->entityUpdate($entity);
 }
 
 /**
@@ -214,41 +96,15 @@ function entity_language_fallback_entity_update(EntityInterface $entity) {
  *
  * @see \Drupal\search_api\Plugin\search_api\datasource\ContentEntity
  */
+#[LegacyHook]
 function entity_language_fallback_entity_delete(EntityInterface $entity) {
-  if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
-    return;
-  }
-  // Check if the entity is a content entity.
-  if (!($entity instanceof ContentEntityInterface) || $entity->search_api_skip_tracking) {
-    return;
-  }
-  $indexes = ContentEntityFallback::getIndexesForEntity($entity);
-  if (!$indexes) {
-    return;
-  }
-
-  // Remove the search items for all the entity's translations.
-  $item_ids = [];
-  $entity_id = $entity->id();
-  foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
-    $item_ids[] = $entity_id . ':' . $langcode;
-  }
-  $datasource_id = 'entity:' . $entity->getEntityTypeId();
-  foreach ($indexes as $index) {
-    $index->trackItemsDeleted($datasource_id, $item_ids);
-  }
+  \Drupal::service(EntityLanguageFallbackHooks::class)->entityDelete($entity);
 }
 
 /**
  * Implements hook_search_api_index_items_alter().
  */
+#[LegacyHook]
 function entity_language_fallback_search_api_index_items_alter(IndexInterface $index, array &$items) {
-  /** @var Drupal\search_api\Item\Item $item */
-  foreach ($items as &$item) {
-    $object = $item->getOriginalObject(TRUE);
-    $lang = $object->language ?? $object->getValue()->langcode->value;
-    if ($lang) {
-      $item->setLanguage($lang);
-    }
-  }
+  \Drupal::service(EntityLanguageFallbackHooks::class)->searchApiIndexItemsAlter($index, $items);
 }
diff --git a/entity_language_fallback.services.yml b/entity_language_fallback.services.yml
index 15cd874..bb4a66e 100644
--- a/entity_language_fallback.services.yml
+++ b/entity_language_fallback.services.yml
@@ -4,3 +4,7 @@ services:
     arguments:
       - '@language_manager'
       - '@entity_type.manager'
+
+  Drupal\entity_language_fallback\Hook\EntityLanguageFallbackHooks:
+    class: Drupal\entity_language_fallback\Hook\EntityLanguageFallbackHooks
+    autowire: true
diff --git a/src/Hook/EntityLanguageFallbackHooks.php b/src/Hook/EntityLanguageFallbackHooks.php
new file mode 100644
index 0000000..46a066f
--- /dev/null
+++ b/src/Hook/EntityLanguageFallbackHooks.php
@@ -0,0 +1,246 @@
+<?php
+
+namespace Drupal\entity_language_fallback\Hook;
+
+use Drupal\search_api\IndexInterface;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\entity_language_fallback\Plugin\search_api\datasource\ContentEntityFallback;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\entity_language_fallback\AccessHelper;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for entity_language_fallback.
+ */
+class EntityLanguageFallbackHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_language_fallback_candidates_alter().
+   */
+  #[Hook('language_fallback_candidates_alter')]
+  public static function languageFallbackCandidatesAlter(array &$candidates, array $context) {
+    $operation = $context['operation'];
+    if ($operation == 'entity_upcast' || $operation == 'entity_view') {
+      /** @var \Drupal\entity_language_fallback\FallbackController $fallback_controller */
+      $fallback_controller = \Drupal::service('language_fallback.controller');
+      if ($new_candidates = $fallback_controller->getEntityFallbackCandidates($context['data'], $context['langcode'])) {
+        $candidates = $new_candidates;
+      }
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_language_admin_edit_form_alter')]
+  public function formLanguageAdminEditFormAlter(&$form, FormStateInterface $form_state) {
+    /** @var Drupal\language\Entity\ConfigurableLanguage $this_language */
+    $this_language = $form_state->getFormObject()->getEntity();
+    $languages = \Drupal::languageManager()->getLanguages();
+    $options = [];
+    foreach ($languages as $language) {
+      // Only include this language if its not itself.
+      if ($language->getId() != $this_language->getId()) {
+        $options[$language->getId()] = $language->getName();
+      }
+    }
+    $form['entity_language_fallback'] = [
+      '#title' => $this->t('Entity fallback language'),
+      '#description' => $this->t('Choose one or more fallback languages in prioritized order. The languages are used as fallback in entity view.'),
+      '#type' => 'details',
+      '#open' => TRUE,
+      '#tree' => TRUE,
+    ];
+    // Creating one priority field per available language.
+    $default_values = $this_language->getThirdPartySetting('entity_language_fallback', 'fallback_langcodes', []);
+    for ($i = 0; $i < count($options); $i++) {
+      $form['entity_language_fallback'][$i] = [
+        '#type' => 'select',
+        '#title' => $this->t('Priority @priority', [
+          '@priority' => $i + 1,
+        ]),
+        '#description' => $this->t('Choose the language used as priority @priority fallback language.', [
+          '@priority' => $i + 1,
+        ]),
+        '#options' => $options,
+        '#default_value' => !empty($default_values[$i]) ? $default_values[$i] : '',
+        '#empty_option' => $this->t('-None-'),
+        '#tree' => TRUE,
+      ];
+    }
+    $form['#entity_builders'][] = 'entity_language_fallback_form_language_admin_edit_form_builder';
+  }
+
+  /**
+   * Implements hook_entity_access().
+   */
+  #[Hook('entity_access')]
+  public static function entityAccess($entity, $operation, AccountInterface $account) {
+    return AccessHelper::checkAccess($entity, $operation, $account);
+  }
+
+  /**
+   * Implements hook_entity_insert().
+   *
+   * Modified version of search_api_entity_insert().
+   *
+   * @see search_api_entity_insert()
+   */
+  #[Hook('entity_insert')]
+  public static function entityInsert(EntityInterface $entity) {
+    if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
+      return;
+    }
+    // Check if the entity is a content entity.
+    if (!$entity instanceof ContentEntityInterface || $entity->search_api_skip_tracking) {
+      return;
+    }
+    $indexes = ContentEntityFallback::getIndexesForEntity($entity);
+    if (!$indexes) {
+      return;
+    }
+    // Compute the item IDs for all languages set up on the fallback chain.
+    $item_ids = [];
+    $entity_id = $entity->id();
+    $fallback_controller = \Drupal::service('language_fallback.controller');
+    $fallback_languages = array_keys($fallback_controller->getTranslations($entity));
+    foreach ($fallback_languages as $langcode) {
+      $item_ids[] = $entity_id . ':' . $langcode;
+    }
+    $datasource_id = 'entity_language_fallback:' . $entity->getEntityTypeId();
+    foreach ($indexes as $index) {
+      $filtered_item_ids = ContentEntityFallback::filterValidItemIds($index, $datasource_id, $item_ids);
+      $index->trackItemsInserted($datasource_id, $filtered_item_ids);
+    }
+  }
+
+  /**
+   * Implements hook_entity_update().
+   *
+   * Search_api_entity_update() can only update items that are in ContentEntity
+   * datasources.
+   *
+   * @see search_api_entity_update()
+   */
+  #[Hook('entity_update')]
+  public static function entityUpdate(EntityInterface $entity) {
+    if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
+      return;
+    }
+    // Check if the entity is a content entity.
+    if (!$entity instanceof ContentEntityInterface || $entity->search_api_skip_tracking) {
+      return;
+    }
+    $indexes = ContentEntityFallback::getIndexesForEntity($entity);
+    if (!$indexes) {
+      return;
+    }
+    /** @var \Drupal\entity_language_fallback\FallbackControllerInterface $fallback_controller */
+    static $fallback_controller;
+    if (!isset($fallback_controller)) {
+      $fallback_controller = \Drupal::service('language_fallback.controller');
+    }
+    // Compare old and new languages for the entity to identify inserted,
+    // updated and deleted translations (and, therefore, search items).
+    $entity_id = $entity->id();
+    $inserted_item_ids = [];
+    $updated_item_ids = $fallback_controller->getTranslations($entity);
+    $deleted_item_ids = [];
+    $old_translations = $fallback_controller->getTranslations(DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original));
+    foreach ($old_translations as $langcode => $language) {
+      if (!isset($updated_item_ids[$langcode])) {
+        $deleted_item_ids[] = $langcode;
+      }
+    }
+    foreach ($updated_item_ids as $langcode => $language) {
+      if (!isset($old_translations[$langcode])) {
+        unset($updated_item_ids[$langcode]);
+        $inserted_item_ids[] = $langcode;
+      }
+    }
+    $datasource_id = 'entity_language_fallback:' . $entity->getEntityTypeId();
+    $combine_id = function ($langcode) use ($entity_id) {
+      return $entity_id . ':' . $langcode;
+    };
+    $inserted_item_ids = array_map($combine_id, $inserted_item_ids);
+    $updated_item_ids = array_map($combine_id, array_keys($updated_item_ids));
+    $deleted_item_ids = array_map($combine_id, $deleted_item_ids);
+    foreach ($indexes as $index) {
+      if ($inserted_item_ids) {
+        $filtered_item_ids = ContentEntityFallback::filterValidItemIds($index, $datasource_id, $inserted_item_ids);
+        $index->trackItemsInserted($datasource_id, $filtered_item_ids);
+      }
+      if ($updated_item_ids) {
+        $index->trackItemsUpdated($datasource_id, $updated_item_ids);
+      }
+      if ($deleted_item_ids) {
+        $index->trackItemsDeleted($datasource_id, $deleted_item_ids);
+      }
+    }
+  }
+
+  /**
+   * Implements hook_entity_delete().
+   *
+   * Deletes all entries for this entity from the tracking table for each index
+   * that tracks this entity type.
+   *
+   * By setting the $entity->search_api_skip_tracking property to a true-like
+   * value before this hook is invoked, you can prevent this behavior and make the
+   * Search API ignore this deletion. (Note that this might lead to stale data in
+   * the tracking table or on the server, since the item will not removed from
+   * there (if it has been added before).)
+   *
+   * Note that this function implements tracking only on behalf of the "Content
+   * Entity" datasource defined in this module, not for entity-based datasources
+   * in general. Datasources defined by other modules still have to implement
+   * their own mechanism for tracking new/updated/deleted entities.
+   *
+   * @see \Drupal\search_api\Plugin\search_api\datasource\ContentEntity
+   */
+  #[Hook('entity_delete')]
+  public static function entityDelete(EntityInterface $entity) {
+    if (!\Drupal::moduleHandler()->moduleExists('search_api')) {
+      return;
+    }
+    // Check if the entity is a content entity.
+    if (!$entity instanceof ContentEntityInterface || $entity->search_api_skip_tracking) {
+      return;
+    }
+    $indexes = ContentEntityFallback::getIndexesForEntity($entity);
+    if (!$indexes) {
+      return;
+    }
+    // Remove the search items for all the entity's translations.
+    $item_ids = [];
+    $entity_id = $entity->id();
+    foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
+      $item_ids[] = $entity_id . ':' . $langcode;
+    }
+    $datasource_id = 'entity:' . $entity->getEntityTypeId();
+    foreach ($indexes as $index) {
+      $index->trackItemsDeleted($datasource_id, $item_ids);
+    }
+  }
+
+  /**
+   * Implements hook_search_api_index_items_alter().
+   */
+  #[Hook('search_api_index_items_alter')]
+  public static function searchApiIndexItemsAlter(IndexInterface $index, array &$items) {
+    /** @var Drupal\search_api\Item\Item $item */
+    foreach ($items as &$item) {
+      $object = $item->getOriginalObject(TRUE);
+      $lang = $object->language ?? $object->getValue()->langcode->value;
+      if ($lang) {
+        $item->setLanguage($lang);
+      }
+    }
+  }
+
+}
