diff --git a/language_hierarchy.module b/language_hierarchy.module
index 96da127..851dabc 100644
--- a/language_hierarchy.module
+++ b/language_hierarchy.module
@@ -1,6 +1,22 @@
 <?php
 
 /**
+ * Implements hook_module_implements_alter().
+ */
+function language_hierarchy_module_implements_alter(&$implementations, $hook) {
+  switch ($hook) {
+    case 'field_language_alter':
+      // language_hierarchy_field_language_alter() must run after
+      // entity_translation_field_language_alter() and
+      // locale_field_language_alter().
+      $group = $implementations['language_hierarchy'];
+      unset($implementations['language_hierarchy']);
+      $implementations['language_hierarchy'] = $group;
+      break;
+  }
+}
+
+/**
  * Implements hook_language_init().
  */
 function language_hierarchy_language_init() {
@@ -24,6 +40,11 @@ function language_hierarchy_permission() {
       'title' => t('Always fallback to default language'),
       'description' => t('Always include default language in fallback chain.'),
     ),
+    'use entity labels outside fallback chain' => array(
+      'title' => t('Use entity labels outside fallback chain if necessary'),
+      'description' => t('Allow fallbacks outside the fallback chain for entity labels such as node titles if there is no valid fallback within the current chain.'),
+      'restrict access' => TRUE,
+    ),
   );
 }
 
@@ -426,6 +447,112 @@ function language_hierarchy_language_fallback_candidates_alter(array &$fallback_
 }
 
 /**
+ * Implements hook_field_language_alter().
+ *
+ * Language hierarchy enforces that languages only fallback to their parent
+ * languages, whereas Drupal core would allow all languages to be fallbacks.
+ * Title replacement fields are a special case, since we may want to show a
+ * title (e.g. to admins) even if there are no translations in a parent language
+ * so all languages are allowed as fallbacks for those. We give primacy to the
+ * entity's own language first, then parent languages ahead of cousin languages.
+ * Since this may mean content is exposed that would not normally be accessible
+ * to users, a permission is used to toggle this functionality.
+ */
+function language_hierarchy_field_language_alter(&$display_language, $context) {
+  $entity_type = $context['entity_type'];
+  $entity_info = entity_get_info($entity_type);
+  if (isset($entity_info['field replacement']) && user_access('use entity labels outside fallback chain')) {
+    // If LANGUAGE_NONE is being used as the fallback, when we intended to show
+    // something else, then none of the existing fallback rules have managed to
+    // provide a better alternative. This is our time to act, if translation &
+    // fallback are enabled, and this field is a label replacement field.
+    if ($context['language'] !== LANGUAGE_NONE && variable_get('locale_field_language_fallback', TRUE) && (field_has_translation_handler($entity_type, 'locale') || (function_exists('entity_translation_enabled') && entity_translation_enabled($entity_type)))) {
+      // Figure out what this entity's label field is (if it has one).
+      if (isset($entity_info['entity keys']['label'])) {
+        $label_property = $entity_info['entity keys']['label'];
+        if (isset($entity_info['field replacement'][$label_property]['field']['field_name'])) {
+          $replacement_field = $entity_info['field replacement'][$label_property]['field']['field_name'];
+          // If there is no value for the language being displayed, but there are
+          // values in any language, and no better fallback has already been found,
+          // proceed.
+          $entity = $context['entity'];
+          if (isset($display_language[$replacement_field]) && $display_language[$replacement_field] === LANGUAGE_NONE && !empty($entity->{$replacement_field}) && empty($entity->{$replacement_field}[LANGUAGE_NONE]) && !isset($entity->{$replacement_field}[$context['language']])) {
+            // Run a breadth-first search for all children and then ancestors, so
+            // that the closest 'relatives' are given priority as fallback
+            // candidates. Language hierarchy will have picked up on any matching
+            // ancestors, but this checks children, grandchildren, siblings,
+            // nephews, uncles, cousins, great-uncles, etc (in that order).
+            $already_checked = array();
+            $ancestors = array_keys(language_hierarchy_get_ancestors($context['language']));
+            array_unshift($ancestors, $context['language']);
+            foreach ($ancestors as $ancestor_code) {
+              $tree = language_hierarchy_get_descendants($ancestor_code);
+              if ($tree && !empty($tree->children)) {
+                if ($found = language_hierarchy_breadth_first_search_for_fallback_language($entity->{$replacement_field}, $tree->children, $already_checked)) {
+                  $display_language[$replacement_field] = $found;
+                  return;
+                }
+              }
+            }
+
+            // Finally use the entity's own language as a fallback. Based on
+            // title_entity_language().
+            if (function_exists('entity_translation_enabled') && entity_translation_enabled($entity_type)) {
+              $handler = entity_translation_get_handler($entity_type, $entity, TRUE);
+              $entity_langcode = $handler->getLanguage();
+            }
+            else {
+              $entity_langcode = entity_language($entity_type, $entity);
+            }
+            if ($entity_langcode && isset($entity->{$replacement_field}[$entity_langcode])) {
+              $display_language[$replacement_field] = $entity_langcode;
+              return;
+            }
+
+            // Finally, in the unlikely case that an entity didn't even have its
+            // own language, fall back using the complete original language list.
+            foreach (language_list('weight') as $languages) {
+              foreach ($languages as $language) {
+                if (isset($entity->{$replacement_field}[$language->language])) {
+                  $display_language[$replacement_field] = $language->language;
+                  return;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Helper function to find a value in a descendant language.
+ *
+ * @param array $haystack
+ *   Usually a set of field values taken from an entity, keyed by language code.
+ * @param array $children
+ *   Array of child languages, see language_hierarchy_get_descendants().
+ * @param array $skip
+ *   Array of languages to skip, keyed by language codes.
+ * @return null|string
+ *   A matched language code to use, or NULL.
+ */
+function language_hierarchy_breadth_first_search_for_fallback_language($haystack, $children, &$skip = array()) {
+  $grandchildren = array();
+  foreach (array_diff_key($children, $skip) as $child_code => $child) {
+    $skip[$child_code] = $child_code;
+    if (isset($haystack[$child_code])) {
+      return $child_code;
+    }
+    elseif (!empty($child->children)) {
+      $grandchildren = array_merge($grandchildren, $child->children);
+    }
+  }
+  return $grandchildren ? language_hierarchy_breadth_first_search_for_fallback_language($haystack, $grandchildren, $skip) : NULL;
+}
+
+/**
  * Recursion helper function that populates language_list in hierarchical order
  *
  * @param $langcode
