diff --git a/language_fallback.fallback.inc b/language_fallback.fallback.inc
index 754d352..cc45067 100644
--- a/language_fallback.fallback.inc
+++ b/language_fallback.fallback.inc
@@ -8,13 +8,13 @@ class localeWithFallback implements ArrayAccess {
 
   private $langcode;
   private $cache = array();
-  private $fallback;
+  private $fallbacks;
+  private $initialized = false;
 
-  public function __construct($langcode, $fallback = '') {
+  public function __construct($langcode) {
     $languages = language_list();
     $this->langcode = isset($languages[$langcode]) ? $langcode : language_default('language');
     $this->cache = array();
-    $this->fallback = isset($languages[$fallback]) ? $fallback : language_default('langauge');
   }
 
   public function offsetExists($offset) {
@@ -22,8 +22,35 @@ class localeWithFallback implements ArrayAccess {
   }
 
   public function &offsetGet($offset) {
-    if (!isset($this->cache[$offset])) {
-      $this->cache[$offset] = new localeContextWithFallback($this->langcode, $this->fallback, $offset);
+    if (!isset($this->cache[$offset]) || (!$this->initialized && function_exists('language_fallback_get_chain'))) {
+      /**
+       * Fallback chains are red and cached with the first call to offsetGet() function.
+       * This is on purpouse, to prevent chains to be stored in database when
+       * variable_set() function is called on language sttings form.
+       */
+      if (!isset($this->fallbacks) || (!$this->initialized && function_exists('language_fallback_get_chain'))) {
+        /**
+         * WARNING!
+         * On some rare occasions when drupal is not fully bootstrapped
+         * the function language_fallback_get_chain might not be available.
+         * This may happen on some administration pages so it should be
+         * safe to just skip all defined fallbacks.
+         */
+        if (function_exists('language_fallback_get_chain')) {
+          $this->fallbacks = language_fallback_get_chain($this->langcode);
+          $this->initialized = true;
+
+          // Reinitialize this offset with apropriate data.
+          if (isset($this->cache[$offset]))
+            unset($this->cache[$offset]);
+        }
+        else {
+          $this->fallbacks = array();
+        }
+      }
+
+      $this->cache[$offset] = new localeContextWithFallback($this->langcode, $this->fallbacks, $offset);
+
     }
     return $this->cache[$offset];
   }
@@ -53,19 +80,19 @@ class localeContextWithFallback implements ArrayAccess {
   private $context = '';
   private $fallback;
 
-  public function __construct($langcode, $fallback, $context) {
+  public function __construct($langcode, $fallbacks, $context) {
     $languages = language_list();
     $this->first_language = isset($languages[$langcode]) ? $langcode : language_default('language');
-    $this->second_language = isset($languages[$fallback]) ? $fallback : language_default('language');
+    $this->second_language = $fallbacks ? array_shift($fallbacks) : language_default('language');
     $this->context = $context;
-    $second_lang_obj = $languages[$this->second_language];
+
     // If the fallback is the default language, we don't need a
     // localeContextWithFallback since the default language does not have a
     // fallback. Otherwise we use a localeContextWithFallback so we gradually
     // fall back to the default language (cascading fallbacks, see
     // http://drupal.org/node/1877880 for more info.
     if ($this->second_language != language_default('language')) {
-      $this->fallback = new localeContextWithFallback($this->second_language, isset($languages[$second_lang_obj->fallback]) ? $second_lang_obj->fallback : language_default('language'), $context);
+      $this->fallback = new localeContextWithFallback($this->second_language, $fallbacks, $context);
     }
   }
 
diff --git a/language_fallback.info b/language_fallback.info
index fcb9481..cc9273f 100644
--- a/language_fallback.info
+++ b/language_fallback.info
@@ -6,3 +6,6 @@ core = 7.x
 dependencies[] = locale
 
 files[] = language_fallback.fallback.inc
+files[] = views/views_handler_filter_locale_language_with_fallback.inc
+
+configure = admin/config/regional/language_fallback
\ No newline at end of file
diff --git a/language_fallback.install b/language_fallback.install
index 94b474b..e741c33 100644
--- a/language_fallback.install
+++ b/language_fallback.install
@@ -6,44 +6,116 @@
  */
 
 /**
- * Implements hook_schema_alter().
+ * Implements hook_schema().
  */
-function language_fallback_schema_alter(&$schema) {
-  if (isset($schema['languages'])) {
-    $schema['languages']['fields']['fallback'] = array(
-      'type' => 'varchar',
-      'length' => 12,
-      'not null' => TRUE,
-      'default' => '',
-      'description' => "Language code, e.g. 'de' or 'en-US'.",
-    );
-  }
+function language_fallback_schema() {
+  $schema['language_fallback'] = array(
+    'description' => 'Contains fallback chains for languages',
+    'fields' => array(
+      'language' => array(
+        'description' => 'Lang code',
+        'type' => 'varchar',
+        'length' => 8,
+        'not null' => true,
+      ),
+      'country' => array(
+        'description' => 'Visitor country code or "all" for all countries',
+        'type' => 'varchar',
+        'length' => 3,
+        'not null' => true,
+        'default' => 'all'
+      ),
+      'chain' => array(
+        'description' => 'Fallback chain ("|" delimiter)',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => true,
+      ),
+    ),
+    'primary key' => array('language', 'country'),
+  );
+  return $schema;
 }
 
 /**
- * Implements hook_install().
+ * Implements hook_disable()
+ *
+ * WARNING!
+ * It is mandatory to remove all "locale_custom_strings_ID" variables!
+ * After this module is disabled the class localeWithFallback is no longer
+ * available but these variables hold reference to it.
  */
-function language_fallback_install() {
-  // Add the field to the languages table so that the 'fallback' property is set
-  // on every object in the array returned by language_list().
-  db_add_field(
-    'languages',
-    'fallback',
-    array(
-      'type' => 'varchar',
-      'length' => 12,
-      'not null' => TRUE,
-      'default' => '',
-      'description' => "Language code, e.g. 'de' or 'en-US'.",
-    )
-  );
-  drupal_static_reset('language_list');
+function language_fallback_disable(){
+  /**
+   * We do not know all our variable names because they are created dynamically.
+   * So to remove unwanted variables we first need to know their names.
+   *
+   * Our variables are "locale_custom_strings_ID"
+   * and their values contain "localeWithFallback" string.
+   *
+   * So the trick is to read all variable names from "variable" table
+   * that met the criteria above.
+   */
+
+  $result = db_update("variable")
+    ->expression('name', "CONCAT('_', name)")
+    ->condition('name', 'locale_custom_string_%', 'LIKE')
+    ->condition('value', '%localeWithFallback%', 'LIKE')
+    ->execute();
+}
+
+/**
+ * Implements hook_enable()
+ *
+ * Revert back "locale_custom_strings_ID" variable names.
+ * See language_fallback_disable() for details.
+ */
+function language_fallback_enable(){
+  $result = db_update("variable")
+    ->expression('name', "REPLACE(name, '_locale_custom_strings_', 'locale_custom_strings_')")
+    ->condition('name', '_locale_custom_string_%', 'LIKE')
+    ->condition('value', '%localeWithFallback%', 'LIKE')
+    ->execute();
 }
 
 /**
  * Implements hook_uninstall().
+ *
+ * WARNING!
+ * It is mandatory to remove all "locale_custom_strings_ID" variables!
+ * After this module is disabled the class localeWithFallback is no
+ * longer available but these variables hold reference to it.
  */
-function language_fallback_uninstall() {
-  db_drop_field('languages', 'fallback');
-  drupal_static_reset('language_list');
+function language_fallback_uninstall(){
+
+  /**
+   * We do not know all our variable names because they are created dynamically.
+   * So to remove unwanted variables we first need to know their names.
+   *
+   * Our variables are "locale_custom_strings_ID" but they were renamed on
+   * hook_disable to "_locale_custom_strings_ID".
+   * and their values contain "localeWithFallback" stirng.
+   *
+   * So the trick is to read all variable names from "variable" table
+   * that met the criteria above.
+   */
+  $result = db_select("variable", "v")
+    ->fields('v', array('name'))
+    ->condition('name', '_locale_custom_string_%', 'LIKE')
+    ->condition('value', '%localeWithFallback%', 'LIKE')
+    ->execute();
+
+  while ($name = $result->fetchField()) {
+    variable_del($name);
+  }
+
+  if (isset($_SESSION['language_fallback'])) {
+    unset($_SESSION['language_fallback']);
+  }
+
+  /**
+   * WARNING!
+   * This is also very important as variables are cached!
+   */
+  cache_clear_all('variables', 'cache_bootstrap');
 }
diff --git a/language_fallback.module b/language_fallback.module
index 19b1691..ed5e6bc 100644
--- a/language_fallback.module
+++ b/language_fallback.module
@@ -1,64 +1,220 @@
 <?php
 
+define('LANGUAGE_FALLBACK_ALL_COUNTRIES', 'all');
+
 /**
- * @file
- * Hook implementations for the Language Fallback module.
+ * Administration forms are moved the separate file for better readability.
+ * The same goes for blocks.
  */
+module_load_include('inc', 'language_fallback', 'language_fallback.admin');
+module_load_include('inc', 'language_fallback', 'language_fallback.block');
 
 /**
- * Implements hook_form_alter().
+ * Implements hook_menu().
  */
-function language_fallback_form_alter(&$form, &$form_state, $form_id) {
-  if ($form_id == 'locale_languages_predefined_form' || $form_id == 'locale_languages_custom_form' || $form_id == 'locale_languages_edit_form') {
-    // Store language for edit form or FALSE on create forms.
-    $editmode = isset($form['langcode_view']) ? $form['langcode']['#value'] : FALSE;
-    $default_lang = language_default();
-    $languages = language_list();
-    $options = locale_language_list();
-
-    // Alter label of default language.
-    if (isset($options[$default_lang->language])) {
-      $options[$default_lang->language] = t('Default language (@lang)', array('@lang' => $default_lang->name));
-    }
+function language_fallback_menu() {
+  $items = array();
 
-    // Add native name.
-    foreach ($options as $key => $option) {
-      if (!empty($languages[$key]->native)) {
-        $options[$key] .= " ({$languages[$key]->native})";
-      }
-    }
+  $items['admin/config/regional/language_fallback'] = array(
+      'title' => 'Language fallback',
+      'description' => 'Configuration for Language fallback module',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('language_fallback_settings_form'),
+      'access arguments' => array('access administration pages'),
+      'type' => MENU_NORMAL_ITEM,
+      'file' => 'language_fallback.settings.inc'
+  );
+
+  return $items;
+}
+
+/**
+ * Implements Drupal hook_language_fallback_candidates_alter().
+ *
+ * This function provides fallback features for entities.
+ *
+ * @param $fallback_candidates: An array of language codes whose order will determine the language fallback order.
+ */
+function language_fallback_language_fallback_candidates_alter(array &$fallback_candidates) {
 
-    // Add select to create/edit form.
-    $key = $form_id == 'locale_languages_predefined_form' ? 'language list' : 'custom language';
-    $form[$key]['fallback'] = array(
-      '#type' => 'select',
-      '#title' => t('Language fallback'),
-      '#default_value' => '',
-      '#options' => $options,
-      '#description' => t('This language will be used as a fallback for text that has no translation in the selected language.'),
-    );
-    $form['#submit'][] = 'language_fallback_add_language_submit';
-
-    // Remove current language in edit mode, set default.
-    if ($editmode) {
-      unset($form[$key]['fallback']['#options'][$editmode]);
-      $form[$key]['fallback']['#default_value'] = $languages[$editmode]->fallback;
+  // Check if entity fallback is enabled
+  if (!variable_get('language_fallback_entity', false))
+    return;
+
+  global $language;
+
+  if (LANGUAGE_NONE == $language->language)
+    return;  // There is no fallback for "Undeterminated" language.
+
+  // Get fallback chain for current language
+  $fallbacks = language_fallback_get_chain($language->language);
+
+  if (count($fallbacks) > 0) {
+    $fallback_candidates = $fallbacks;
+  } else {
+    /* By default Drupal uses list of all enabled languages as a fallback chain.
+     * This is a bit weird and makes no sense in case of sites that actually
+     * make use of fallback mechanisms.
+     *
+     * That is why we rewrite the fallback chain with one default value only.
+     */
+    $fallback_candidates = array(LANGUAGE_NONE);
+  }
+}
+
+/**
+ * Get fallback chain for specified language.
+ * If you want to hook up to language fallback module - this is the place to start.
+ *
+ * @param string $lang: Language code for which fallback chain is needed.
+ * @param string $country: Country code for which fallback chain is needed.
+ * @param bool $strict: Used only internally.
+ * @return array of languages. An empty array is returned if no fallback is defined.
+ */
+function language_fallback_get_chain($lang, $country = false, $strict = false){
+
+  if (!$strict) {
+    /* User defined fallabck always takes precedence before any other fallback
+     * But we can't use it on administration pages!
+     */
+    $user_chain = language_fallback_get_user_chain();
+
+    if (count($user_chain) > 0)
+      return  $user_chain;
+  }
+
+  $chains = &drupal_static(__FUNCTION__);
+
+  if (!isset($chains))
+    $chains = array();
+
+  if ($country === false)
+    $country = language_fallback_get_country();
+
+  // Check cache - maybe the chain was already loaded
+  if (!isset($chains[$lang][$country])) {
+
+    $chain = db_select('language_fallback', 'f')
+              ->fields('f', array('chain'))
+              ->condition('language', $lang, '=')
+              ->condition('country', $country, '=')
+              ->execute()
+              ->fetchField();
+
+    if ($chain) {
+      // We have a fallback chain for this country
+      $chains[$lang][$country] = explode("|", $chain); // for better readability fallback chains are stored in a string separated with "|" character.
+    }
+    else if ($country == LANGUAGE_FALLBACK_ALL_COUNTRIES) {
+      // We don't have a fallback chain for all countries
+      $chains[$lang][$country] = array();
+    }
+    else if ($strict) {
+      // Do not use default fallback if no fallback was found.
+      return array();
+    }
+    else {
+      // We dno't have fallback so we try default fallback for all countries
+      // WARNING! Recurency!
+      $chains[$lang][$country] = language_fallback_get_chain($lang, LANGUAGE_FALLBACK_ALL_COUNTRIES);
     }
   }
+
+  return $chains[$lang][$country];
 }
 
 /**
- * Form submit callback.
+ * Get user defined fallback chain if it exists.
+ * If user defined chain does not exists an empty array is returned.
+ *
+ * @return array: user defined fallback chain.
  */
-function language_fallback_add_language_submit($form, &$form_state) {
-  db_update('languages')
-    ->fields(array('fallback' => $form_state['values']['fallback']))
-    ->condition('language', $form_state['values']['langcode'])
-    ->execute();
-  if (!empty($form_state['values']['fallback'])) {
-    variable_set('locale_custom_strings_' . $form_state['values']['langcode'], new localeWithFallback($form_state['values']['langcode'], $form_state['values']['fallback']));
+function language_fallback_get_user_chain() {
+  if (isset($_SESSION['language_fallback']['user_chain'])
+    && is_array($_SESSION['language_fallback']['user_chain'])
+    && count($_SESSION['language_fallback']['user_chain']) > 0)
+      return $_SESSION['language_fallback']['user_chain'];
+
+  return array();
+}
+
+/**
+ * Set user defined fallback chain for CURRENT user.
+ * User fallback chain is stored in a session variable.
+ * Empty chain will unset user session variable.
+ */
+function language_fallback_set_user_chain($chain = null) {
+  if (empty($chain))
+    unset ($_SESSION['language_fallback']['user_chain']);
+  else
+    $_SESSION['language_fallback']['user_chain'] = $chain;
+}
+
+/**
+ * Get country code for fallback check.
+ *
+ * @return string Either country code for fallback chain lookup or empty string if no country is specified for current visitor.
+ */
+function language_fallback_get_country(){
+  // Check if country is stored in a session variable
+  if (isset($_SESSION['language_fallback']['country'])) {
+    return $_SESSION['language_fallback']['country'];
+  } else if (function_exists('smart_ip_get_location')) {
+
+    // Use smart IP to get user location
+    $country = smart_ip_get_location(ip_address());
+    if ($country !== false)
+      $_SESSION['language_fallback']['country'] = strtoupper($country['country_code']);
+    else
+      $_SESSION['language_fallback']['country'] = LANGUAGE_FALLBACK_ALL_COUNTRIES;
+
+    // We store country in a asession variable only if it is really needed.
+  }
+
+  if (isset($_SESSION['language_fallback']['country'])) {
+    return $_SESSION['language_fallback']['country'];
+  }
+
+  return LANGUAGE_FALLBACK_ALL_COUNTRIES;
+}
+
+/**
+ * Set country code for current user.
+ * @param string $country
+ */
+function language_fallback_set_country($country = false){
+  if (!$country)
+    $country = LANGUAGE_FALLBACK_ALL_COUNTRIES;
+
+  $_SESSION['language_fallback']['country'] = $country;
+}
+
+/**
+ * Get list of countries for country specific fallback.
+ * @return array with country names keyed by country code.
+ */
+function language_fallback_get_countries(){
+  $ret = array(
+      LANGUAGE_FALLBACK_ALL_COUNTRIES => t('All countries')
+  );
+
+  if (variable_get('language_fallback_country', false)){
+
+    $ret += country_get_list();
   }
-  else {
-    variable_del('locale_custom_strings_' . $form_state['values']['langcode']);
+
+  return $ret;
+}
+
+/**
+ * Implementation of hook_field_views_data_alter().
+ *
+ * This function overwrites default 'views_handler_filter_locale_language'
+ * filter handler with our own implementation that is fallback-aware.
+ */
+function language_fallback_field_views_data_alter(&$result, &$field, &$module) {
+  foreach($result as &$field) {
+    if (isset($field['language']) && isset($field['language']['filter']))
+      $field['language']['filter']['handler'] = 'views_handler_filter_locale_language_with_fallback';
   }
 }
