text: from collections import defaultdict, namedtuple
  from gettext import gettext as _
  import json
  import logging
  import pkgutil
  import pycountry
  import re
  
  logger = logging.getLogger('le_utils')
  logger.setLevel(logging.INFO)
  
  
  LTR_LANGUAGE = 'ltr'
  RTL_LANGUAGE = 'rtl'
  LANGUAGE_DIRECTIONS = (
      (LTR_LANGUAGE, _('Left to Right')),
      (RTL_LANGUAGE, _('Right to Left')),
  )
  
  RTL_LANG_CODES = [
      "ar",  # Arabic
      "arq", # Algerian
      "dv",  # Divehi; Dhivehi; Maldivian
      "he",  # Hebrew (modern)
      "fa",  # Persian
      "ps",  # Pashto; Pushto
      "ur",  # Urdu
      "yi",  # Yiddish
  ]
  
  
  class Language(
          namedtuple("Language", [
              "native_name", "primary_code", "subcode", "name", "ka_name"
          ])):
      @property
      def code(self):
          if self.subcode:
              return "{primary_code}-{subcode}".format(
                  primary_code=self.primary_code, subcode=self.subcode)
          else:
              return self.primary_code
  
      @property
      def id(self):
          return self.code
      
      @property
      def first_native_name(self):
          """
          Return the first native name in the comma-seprated list of `native_name`.
          """
          return self.native_name.split(',')[0]
  
  
  def _parse_out_iso_639_code(code):
      code_regex = r'(?P<primary_code>\w{2,3})(-(?P<subcode>\w{2,4}))?'
  
      match = re.match(code_regex, code)
      if match:
          return defaultdict(lambda: None, **match.groupdict())
      else:
          return None
  
  def generate_list(constantlist):
      for code, lang in constantlist.items():
          values = _parse_out_iso_639_code(code)
          values.update(lang)
  
          # add a default value to ka_name
          if 'ka_name' not in values:
              values['ka_name'] = None
  
          yield Language(**values)
  
  def _initialize_language_list():
      langlist = json.loads(
          pkgutil.get_data('le_utils', 'resources/languagelookup.json').decode(
              'utf-8'))
  
      return generate_list(langlist)
  
  def _iget(key, lookup_dict):
      """
      Case-insensitive search for `key` within keys of `lookup_dict`.
      """
      for k, v in lookup_dict.items():
          if k.lower() == key.lower():
              return v
      return None
  
  LANGUAGELIST = list(_initialize_language_list())
  
  _LANGLOOKUP = {l.code: l for l in LANGUAGELIST}
  
  def getlang(code, default=None):
      """
      Try to lookup a Language object for `code` in internal representation defined
      in resources/languagelookup.json.
      Returns None if lookup by internal representation fails.
      """
      return _LANGLOOKUP.get(code) or None
      # should this be   return _LANGLOOKUP.get(code) or _LANGLOOKUP[default]  ???
  
  
  # NOTES ON INTERNAL REPRESENTATION FOR LANGUAGE CODES
  ################################################################################
  # The language code lookup table in `resources/languagelookup.json` is complex:
  # 1.   "<code>":{
  # 2.     "name":"EnglishName, variant; AlternativeName",
  # 3.     "native_name":"NativeName, AlternativeNativeName",
  #       }
  #
  # 1. <code> can be two letter code, three letter code, or <code>-<locale> string
  # 2. `name` attr. contains ";"-separated list of alternative names for the language,
  #     some names have a ","-separated variant, e.g.,  "Spanish, Spain"
  # 3. `native_name` contains ","-separated list of strings for language name in that language
  #
  # The following lookup tables and lookup functions perform the necessary logic
  # to deal with the ";" and "," separators used in `resources/languagelookup.json`
  ################################################################################
  
  _LANGUAGE_NAME_LOOKUP = {l.name: l for l in LANGUAGELIST}
  
  # Enrich _LANGUAGE_NAME_LOOKUP with aliases for list-names, and simplified names
  new_items = {}
  for lang_name, lang_obj in _LANGUAGE_NAME_LOOKUP.items():
      # Add language names that are separated by semicolons, e.g. "Catalan; Valencian"
      if ';' in lang_name:
          new_names = [n.strip() for n in lang_name.split(';')]
          for new_name in new_names:
              if new_name in _LANGUAGE_NAME_LOOKUP.keys() or new_name in new_items:
                  logger.debug('Skip ' + new_name + ' because it already exisits')
              else:
                  new_items[new_name] = lang_obj
      # Add base names without modifies in brackets or country/region after comma
      elif '(' in lang_name or ',' in lang_name:
          simple_name = lang_name.split(',')[0]            # take part before comma
          simple_name = simple_name.split('(')[0].strip()  # and before any bracket
          if simple_name in _LANGUAGE_NAME_LOOKUP.keys() or simple_name in new_items:
              logger.debug('Skip ' + simple_name + ' because it already exisits')
          else:
              new_items[simple_name] = lang_obj
  _LANGUAGE_NAME_LOOKUP.update(new_items)
  
  def getlang_by_name(name):
      """
      Try to lookup a Language object by name, e.g. 'English', in internal language list.
      Returns None if lookup by language name fails in resources/languagelookup.json.
      """
      direct_match = _iget(name, _LANGUAGE_NAME_LOOKUP)
      if direct_match:
          return direct_match
      else:
          simple_name = name.split(',')[0]                 # take part before comma
          simple_name = simple_name.split('(')[0].strip()  # and before any bracket
          return _LANGUAGE_NAME_LOOKUP.get(simple_name, None)
  
  
  
  
  _LANGUAGE_NATIVE_NAME_LOOKUP = {l.native_name: l for l in LANGUAGELIST}
  
  # Enrich _LANGUAGE_NATIVE_NAME_LOOKUP with aliases for list-like names
  new_items = {}
  for lang_native_name, lang_obj in _LANGUAGE_NATIVE_NAME_LOOKUP.items():
      # Add base native names without modifies in brackets or after comma
      if ',' in lang_native_name:
          new_native_names = [n.strip() for n in lang_native_name.split(',')]
          for new_native_name in new_native_names:
              simple_native_name = new_native_name.split('(')[0].strip()  # text before any bracket
              if simple_native_name in _LANGUAGE_NATIVE_NAME_LOOKUP.keys() or new_native_name in new_items:
                  logger.debug('Skip ' + simple_native_name + ' because it already exisits')
              else:
                  new_items[simple_native_name] = lang_obj
      elif '(' in lang_native_name:
          simple_native_name = lang_native_name.split('(')[0].strip()  # text before any bracket
          if simple_native_name in _LANGUAGE_NATIVE_NAME_LOOKUP.keys() or simple_native_name in new_items:
              logger.debug('Skip ' + simple_native_name + ' because it already exisits')
          else:
              new_items[simple_native_name] = lang_obj
  _LANGUAGE_NATIVE_NAME_LOOKUP.update(new_items)
  
  
  def getlang_by_native_name(native_name):
      """
      Try to lookup a Language object by native_name, e.g. 'English', in internal language list.
      Returns None if lookup by language name fails in resources/languagelookup.json.
      """
      direct_match = _iget(native_name, _LANGUAGE_NATIVE_NAME_LOOKUP)
      if direct_match:
          return direct_match
      else:
          simple_native_name = native_name.split(',')[0]                 # take part before comma
          simple_native_name = simple_native_name.split('(')[0].strip()  # and before any bracket
          return _LANGUAGE_NATIVE_NAME_LOOKUP.get(simple_native_name, None)
  
  
  
  def getlang_by_alpha2(code):
      """
      Lookup a Language object for language code `code` based on these strategies:
        - Special case rules for Hebrew and Chinese Hans/Hant scripts
        - Using `alpha_2` lookup in `pycountry.languages` followed by lookup for a
          language with the same `name` in the internal representaion
      Returns `None` if no matching language is found.
      """
      # First try exact match to a language code in the internal representaion
      exact_match = getlang(code)
      if exact_match:
          return exact_match
  
      # Handle special cases for language codes returned by YouTube API
      if code == 'iw':   # handle old Hebrew code 'iw' and return modern code 'he'
          return getlang('he')
      elif 'zh-Hans' in code:        # use code `zh-CN` for all simplified Chinese
          return getlang('zh-CN')
      elif re.match('zh(.*)?-TW', code):        # matches all Taiwan chinese codes
          return getlang('zh-TW')
      elif re.match('zh(.*)?-HK', code):        # use code `zh-Hant` for Hong Kong
          return getlang('zh-Hant')
  
      # extract prefix only if specified with subcode: e.g. zh-Hans --> zh
      first_part = code.split('-')[0]
  
      # See if pycountry can find this language
      try:
          pyc_lang = pycountry.languages.get(alpha_2=first_part)
          if pyc_lang:
              if hasattr(pyc_lang, 'inverted_name'):
                  lang_name = pyc_lang.inverted_name
              else:
                  lang_name = pyc_lang.name
              return getlang_by_name(lang_name)
          else:
              return None
      except KeyError:
          return None
  
  def getlang_direction(code):
      if code in RTL_LANG_CODES:
          return RTL_LANGUAGE
      else:
          return LTR_LANGUAGE
  