/*
 *  Copyright (C) 2005-2020 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "LangCodeExpander.h"

#include "utils/StringUtils.h"
#include "utils/XBMCTinyXML.h"

#include <algorithm>
#include <array>

#define MAKECODE(a, b, c, d) \
  ((((long)(a)) << 24) | (((long)(b)) << 16) | (((long)(c)) << 8) | (long)(d))
#define MAKETWOCHARCODE(a, b) ((((long)(a)) << 8) | (long)(b))

typedef struct LCENTRY
{
  long code;
  const char* name;
} LCENTRY;

extern const std::array<struct LCENTRY, 186> g_iso639_1;
extern const std::array<struct LCENTRY, 540> g_iso639_2;

struct ISO639
{
  const char* iso639_1;
  const char* iso639_2b;
  const char* iso639_2t;
  const char* win_id;
};

struct ISO3166_1
{
  const char* alpha2;
  const char* alpha3;
};

// declared as extern to allow forward declaration
extern const std::array<ISO639, 190> LanguageCodes;
extern const std::array<ISO3166_1, 245> RegionCodes;

CLangCodeExpander::CLangCodeExpander() = default;

CLangCodeExpander::~CLangCodeExpander() = default;

void CLangCodeExpander::Clear()
{
  m_mapUser.clear();
}

void CLangCodeExpander::LoadUserCodes(const TiXmlElement* pRootElement)
{
  if (pRootElement != NULL)
  {
    m_mapUser.clear();

    std::string sShort, sLong;

    const TiXmlNode* pLangCode = pRootElement->FirstChild("code");
    while (pLangCode != NULL)
    {
      const TiXmlNode* pShort = pLangCode->FirstChildElement("short");
      const TiXmlNode* pLong = pLangCode->FirstChildElement("long");
      if (pShort != NULL && pLong != NULL)
      {
        sShort = pShort->FirstChild()->Value();
        sLong = pLong->FirstChild()->Value();
        StringUtils::ToLower(sShort);

        m_mapUser[sShort] = sLong;
      }

      pLangCode = pLangCode->NextSibling();
    }
  }
}

bool CLangCodeExpander::Lookup(const std::string& code, std::string& desc)
{
  int iSplit = code.find('-');
  if (iSplit > 0)
  {
    std::string strLeft, strRight;
    const bool bLeft = Lookup(code.substr(0, iSplit), strLeft);
    const bool bRight = Lookup(code.substr(iSplit + 1), strRight);
    if (bLeft || bRight)
    {
      desc = "";
      if (strLeft.length() > 0)
        desc = strLeft;
      else
        desc = code.substr(0, iSplit);

      if (strRight.length() > 0)
      {
        desc += " - ";
        desc += strRight;
      }
      else
      {
        desc += " - ";
        desc += code.substr(iSplit + 1);
      }

      return true;
    }

    return false;
  }

  if (LookupInUserMap(code, desc))
    return true;

  if (LookupInISO639Tables(code, desc))
    return true;

  return false;
}

bool CLangCodeExpander::Lookup(const int code, std::string& desc)
{
  char lang[3];
  lang[2] = 0;
  lang[1] = (code & 0xFF);
  lang[0] = (code >> 8) & 0xFF;

  return Lookup(lang, desc);
}

bool CLangCodeExpander::ConvertISO6391ToISO6392B(const std::string& strISO6391,
                                                 std::string& strISO6392B,
                                                 bool checkWin32Locales /*= false*/)
{
  // not a 2 char code
  if (strISO6391.length() != 2)
    return false;

  std::string strISO6391Lower(strISO6391);
  StringUtils::ToLower(strISO6391Lower);
  StringUtils::Trim(strISO6391Lower);

  for (const auto& codes : LanguageCodes)
  {
    if (strISO6391Lower == codes.iso639_1)
    {
      if (checkWin32Locales && codes.win_id)
      {
        strISO6392B = codes.win_id;
        return true;
      }

      strISO6392B = codes.iso639_2b;
      return true;
    }
  }

  return false;
}

bool CLangCodeExpander::ConvertToISO6392B(const std::string& strCharCode,
                                          std::string& strISO6392B,
                                          bool checkWin32Locales /* = false */)
{

  //first search in the user defined map
  if (LookupUserCode(strCharCode, strISO6392B))
    return true;

  if (strCharCode.size() == 2)
    return g_LangCodeExpander.ConvertISO6391ToISO6392B(strCharCode, strISO6392B, checkWin32Locales);

  if (strCharCode.size() == 3)
  {
    std::string charCode(strCharCode);
    StringUtils::ToLower(charCode);
    for (const auto& codes : LanguageCodes)
    {
      if (charCode == codes.iso639_2b ||
          (checkWin32Locales && codes.win_id != NULL && charCode == codes.win_id))
      {
        strISO6392B = charCode;
        return true;
      }
    }

    for (const auto& codes : RegionCodes)
    {
      if (charCode == codes.alpha3)
      {
        strISO6392B = charCode;
        return true;
      }
    }
  }
  else if (strCharCode.size() > 3)
  {
    for (const auto& codes : g_iso639_2)
    {
      if (StringUtils::EqualsNoCase(strCharCode, codes.name))
      {
        CodeToString(codes.code, strISO6392B);
        return true;
      }
    }
  }
  return false;
}

bool CLangCodeExpander::ConvertToISO6392T(const std::string& strCharCode,
                                          std::string& strISO6392T,
                                          bool checkWin32Locales /* = false */)
{
  if (!ConvertToISO6392B(strCharCode, strISO6392T, checkWin32Locales))
    return false;

  for (const auto& codes : LanguageCodes)
  {
    if (strISO6392T == codes.iso639_2b ||
        (checkWin32Locales && codes.win_id != NULL && strISO6392T == codes.win_id))
    {
      if (codes.iso639_2t != nullptr)
        strISO6392T = codes.iso639_2t;
      return true;
    }
  }
  return false;
}


bool CLangCodeExpander::LookupUserCode(const std::string& desc, std::string& userCode)
{
  for (STRINGLOOKUPTABLE::const_iterator it = m_mapUser.begin(); it != m_mapUser.end(); ++it)
  {
    if (StringUtils::EqualsNoCase(desc, it->first) || StringUtils::EqualsNoCase(desc, it->second))
    {
      userCode = it->first;
      return true;
    }
  }
  return false;
}

#ifdef TARGET_WINDOWS
bool CLangCodeExpander::ConvertISO31661Alpha2ToISO31661Alpha3(const std::string& strISO31661Alpha2,
                                                              std::string& strISO31661Alpha3)
{
  if (strISO31661Alpha2.length() != 2)
    return false;

  std::string strLower(strISO31661Alpha2);
  StringUtils::ToLower(strLower);
  StringUtils::Trim(strLower);
  for (const auto& codes : RegionCodes)
  {
    if (strLower == codes.alpha2)
    {
      strISO31661Alpha3 = codes.alpha3;
      return true;
    }
  }

  return true;
}

bool CLangCodeExpander::ConvertWindowsLanguageCodeToISO6392B(
    const std::string& strWindowsLanguageCode, std::string& strISO6392B)
{
  if (strWindowsLanguageCode.length() != 3)
    return false;

  std::string strLower(strWindowsLanguageCode);
  StringUtils::ToLower(strLower);
  for (const auto& codes : LanguageCodes)
  {
    if ((codes.win_id && strLower == codes.win_id) || strLower == codes.iso639_2b)
    {
      strISO6392B = codes.iso639_2b;
      return true;
    }
  }

  return false;
}
#endif

bool CLangCodeExpander::ConvertToISO6391(const std::string& lang, std::string& code)
{
  if (lang.empty())
    return false;

  //first search in the user defined map
  if (LookupUserCode(lang, code))
    return true;

  if (lang.length() == 2)
  {
    std::string tmp;
    if (Lookup(lang, tmp))
    {
      code = lang;
      return true;
    }
  }
  else if (lang.length() == 3)
  {
    std::string lower(lang);
    StringUtils::ToLower(lower);
    for (const auto& codes : LanguageCodes)
    {
      if (lower == codes.iso639_2b || (codes.win_id && lower == codes.win_id))
      {
        code = codes.iso639_1;
        return true;
      }
    }

    for (const auto& codes : RegionCodes)
    {
      if (lower == codes.alpha3)
      {
        code = codes.alpha2;
        return true;
      }
    }
  }

  // check if lang is full language name
  std::string tmp;
  if (ReverseLookup(lang, tmp))
  {
    if (tmp.length() == 2)
    {
      code = tmp;
      return true;
    }

    if (tmp.length() == 3)
    {
      // there's only an iso639-2 code that is identical to the language name, e.g. Yao
      if (StringUtils::EqualsNoCase(tmp, lang))
        return false;

      return ConvertToISO6391(tmp, code);
    }
  }

  return false;
}

bool CLangCodeExpander::ReverseLookup(const std::string& desc, std::string& code)
{
  if (desc.empty())
    return false;

  std::string descTmp(desc);
  StringUtils::Trim(descTmp);
  for (STRINGLOOKUPTABLE::const_iterator it = m_mapUser.begin(); it != m_mapUser.end(); ++it)
  {
    if (StringUtils::EqualsNoCase(descTmp, it->second))
    {
      code = it->first;
      return true;
    }
  }

  for (const auto& codes : g_iso639_1)
  {
    if (StringUtils::EqualsNoCase(descTmp, codes.name))
    {
      CodeToString(codes.code, code);
      return true;
    }
  }

  for (const auto& codes : g_iso639_2)
  {
    if (StringUtils::EqualsNoCase(descTmp, codes.name))
    {
      CodeToString(codes.code, code);
      return true;
    }
  }

  return false;
}

bool CLangCodeExpander::LookupInUserMap(const std::string& code, std::string& desc)
{
  if (code.empty())
    return false;

  // make sure we convert to lowercase before trying to find it
  std::string sCode(code);
  StringUtils::ToLower(sCode);
  StringUtils::Trim(sCode);

  STRINGLOOKUPTABLE::iterator it = m_mapUser.find(sCode);
  if (it != m_mapUser.end())
  {
    desc = it->second;
    return true;
  }

  return false;
}

bool CLangCodeExpander::LookupInISO639Tables(const std::string& code, std::string& desc)
{
  if (code.empty())
    return false;

  long longcode;
  std::string sCode(code);
  StringUtils::ToLower(sCode);
  StringUtils::Trim(sCode);

  if (sCode.length() == 2)
  {
    longcode = MAKECODE('\0', '\0', sCode[0], sCode[1]);
    for (const auto& codes : g_iso639_1)
    {
      if (codes.code == longcode)
      {
        desc = codes.name;
        return true;
      }
    }
  }
  else if (sCode.length() == 3)
  {
    longcode = MAKECODE('\0', sCode[0], sCode[1], sCode[2]);
    for (const auto& codes : g_iso639_2)
    {
      if (codes.code == longcode)
      {
        desc = codes.name;
        return true;
      }
    }
  }
  return false;
}

void CLangCodeExpander::CodeToString(long code, std::string& ret)
{
  ret.clear();
  for (unsigned int j = 0; j < 4; j++)
  {
    char c = (char)code & 0xFF;
    if (c == '\0')
      return;

    ret.insert(0, 1, c);
    code >>= 8;
  }
}

bool CLangCodeExpander::CompareFullLanguageNames(const std::string& lang1, const std::string& lang2)
{
  if (StringUtils::EqualsNoCase(lang1, lang2))
    return true;

  std::string expandedLang1, expandedLang2, code1, code2;

  if (!ReverseLookup(lang1, code1))
    return false;

  code1 = lang1;
  if (!ReverseLookup(lang2, code2))
    return false;

  code2 = lang2;
  Lookup(expandedLang1, code1);
  Lookup(expandedLang2, code2);

  return StringUtils::EqualsNoCase(expandedLang1, expandedLang2);
}

std::vector<std::string> CLangCodeExpander::GetLanguageNames(
    LANGFORMATS format /* = CLangCodeExpander::ISO_639_1 */, bool customNames /* = false */)
{
  std::vector<std::string> languages;

  if (format == CLangCodeExpander::ISO_639_2)
    std::transform(g_iso639_2.begin(), g_iso639_2.end(), std::back_inserter(languages),
                   [](const LCENTRY& e) { return e.name; });
  else
    std::transform(g_iso639_1.begin(), g_iso639_1.end(), std::back_inserter(languages),
                   [](const LCENTRY& e) { return e.name; });

  if (customNames)
    std::transform(m_mapUser.begin(), m_mapUser.end(), std::back_inserter(languages),
                   [](const STRINGLOOKUPTABLE::value_type& e) { return e.second; });

  return languages;
}

bool CLangCodeExpander::CompareISO639Codes(const std::string& code1, const std::string& code2)
{
  if (StringUtils::EqualsNoCase(code1, code2))
    return true;

  std::string expandedLang1;
  if (!Lookup(code1, expandedLang1))
    return false;

  std::string expandedLang2;
  if (!Lookup(code2, expandedLang2))
    return false;

  return StringUtils::EqualsNoCase(expandedLang1, expandedLang2);
}

std::string CLangCodeExpander::ConvertToISO6392B(const std::string& lang)
{
  if (lang.empty())
    return lang;

  std::string two, three;
  if (ConvertToISO6391(lang, two))
  {
    if (ConvertToISO6392B(two, three))
      return three;
  }

  return lang;
}

std::string CLangCodeExpander::ConvertToISO6392T(const std::string& lang)
{
  if (lang.empty())
    return lang;

  std::string two, three;
  if (ConvertToISO6391(lang, two))
  {
    if (ConvertToISO6392T(two, three))
      return three;
  }

  return lang;
}

// clang-format off
const std::array<struct LCENTRY, 186> g_iso639_1 = {{
    {MAKECODE('\0', '\0', 'a', 'a'), "Afar"},
    {MAKECODE('\0', '\0', 'a', 'b'), "Abkhazian"},
    {MAKECODE('\0', '\0', 'a', 'e'), "Avestan"},
    {MAKECODE('\0', '\0', 'a', 'f'), "Afrikaans"},
    {MAKECODE('\0', '\0', 'a', 'k'), "Akan"},
    {MAKECODE('\0', '\0', 'a', 'm'), "Amharic"},
    {MAKECODE('\0', '\0', 'a', 'n'), "Aragonese"},
    {MAKECODE('\0', '\0', 'a', 'r'), "Arabic"},
    {MAKECODE('\0', '\0', 'a', 's'), "Assamese"},
    {MAKECODE('\0', '\0', 'a', 'v'), "Avaric"},
    {MAKECODE('\0', '\0', 'a', 'y'), "Aymara"},
    {MAKECODE('\0', '\0', 'a', 'z'), "Azerbaijani"},
    {MAKECODE('\0', '\0', 'b', 'a'), "Bashkir"},
    {MAKECODE('\0', '\0', 'b', 'e'), "Belarusian"},
    {MAKECODE('\0', '\0', 'b', 'g'), "Bulgarian"},
    {MAKECODE('\0', '\0', 'b', 'h'), "Bihari"},
    {MAKECODE('\0', '\0', 'b', 'i'), "Bislama"},
    {MAKECODE('\0', '\0', 'b', 'm'), "Bambara"},
    {MAKECODE('\0', '\0', 'b', 'n'), "Bengali; Bangla"},
    {MAKECODE('\0', '\0', 'b', 'o'), "Tibetan"},
    {MAKECODE('\0', '\0', 'b', 'r'), "Breton"},
    {MAKECODE('\0', '\0', 'b', 's'), "Bosnian"},
    {MAKECODE('\0', '\0', 'c', 'a'), "Catalan"},
    {MAKECODE('\0', '\0', 'c', 'e'), "Chechen"},
    {MAKECODE('\0', '\0', 'c', 'h'), "Chamorro"},
    {MAKECODE('\0', '\0', 'c', 'o'), "Corsican"},
    {MAKECODE('\0', '\0', 'c', 'r'), "Cree"},
    {MAKECODE('\0', '\0', 'c', 's'), "Czech"},
    {MAKECODE('\0', '\0', 'c', 'u'), "Church Slavic"},
    {MAKECODE('\0', '\0', 'c', 'v'), "Chuvash"},
    {MAKECODE('\0', '\0', 'c', 'y'), "Welsh"},
    {MAKECODE('\0', '\0', 'd', 'a'), "Danish"},
    {MAKECODE('\0', '\0', 'd', 'e'), "German"},
    {MAKECODE('\0', '\0', 'd', 'v'), "Dhivehi"},
    {MAKECODE('\0', '\0', 'd', 'z'), "Dzongkha"},
    {MAKECODE('\0', '\0', 'e', 'e'), "Ewe"},
    {MAKECODE('\0', '\0', 'e', 'l'), "Greek"},
    {MAKECODE('\0', '\0', 'e', 'n'), "English"},
    {MAKECODE('\0', '\0', 'e', 'o'), "Esperanto"},
    {MAKECODE('\0', '\0', 'e', 's'), "Spanish"},
    {MAKECODE('\0', '\0', 'e', 't'), "Estonian"},
    {MAKECODE('\0', '\0', 'e', 'u'), "Basque"},
    {MAKECODE('\0', '\0', 'f', 'a'), "Persian"},
    {MAKECODE('\0', '\0', 'f', 'f'), "Fulah"},
    {MAKECODE('\0', '\0', 'f', 'i'), "Finnish"},
    {MAKECODE('\0', '\0', 'f', 'j'), "Fijian"},
    {MAKECODE('\0', '\0', 'f', 'o'), "Faroese"},
    {MAKECODE('\0', '\0', 'f', 'r'), "French"},
    {MAKECODE('\0', '\0', 'f', 'y'), "Western Frisian"},
    {MAKECODE('\0', '\0', 'g', 'a'), "Irish"},
    {MAKECODE('\0', '\0', 'g', 'd'), "Scottish Gaelic"},
    {MAKECODE('\0', '\0', 'g', 'l'), "Galician"},
    {MAKECODE('\0', '\0', 'g', 'n'), "Guarani"},
    {MAKECODE('\0', '\0', 'g', 'u'), "Gujarati"},
    {MAKECODE('\0', '\0', 'g', 'v'), "Manx"},
    {MAKECODE('\0', '\0', 'h', 'a'), "Hausa"},
    {MAKECODE('\0', '\0', 'h', 'e'), "Hebrew"},
    {MAKECODE('\0', '\0', 'h', 'i'), "Hindi"},
    {MAKECODE('\0', '\0', 'h', 'o'), "Hiri Motu"},
    {MAKECODE('\0', '\0', 'h', 'r'), "Croatian"},
    {MAKECODE('\0', '\0', 'h', 't'), "Haitian"},
    {MAKECODE('\0', '\0', 'h', 'u'), "Hungarian"},
    {MAKECODE('\0', '\0', 'h', 'y'), "Armenian"},
    {MAKECODE('\0', '\0', 'h', 'z'), "Herero"},
    {MAKECODE('\0', '\0', 'i', 'a'), "Interlingua"},
    {MAKECODE('\0', '\0', 'i', 'd'), "Indonesian"},
    {MAKECODE('\0', '\0', 'i', 'e'), "Interlingue"},
    {MAKECODE('\0', '\0', 'i', 'g'), "Igbo"},
    {MAKECODE('\0', '\0', 'i', 'i'), "Sichuan Yi"},
    {MAKECODE('\0', '\0', 'i', 'k'), "Inupiat"},
    {MAKECODE('\0', '\0', 'i', 'o'), "Ido"},
    {MAKECODE('\0', '\0', 'i', 's'), "Icelandic"},
    {MAKECODE('\0', '\0', 'i', 't'), "Italian"},
    {MAKECODE('\0', '\0', 'i', 'u'), "Inuktitut"},
    {MAKECODE('\0', '\0', 'j', 'a'), "Japanese"},
    {MAKECODE('\0', '\0', 'j', 'v'), "Javanese"},
    {MAKECODE('\0', '\0', 'k', 'a'), "Georgian"},
    {MAKECODE('\0', '\0', 'k', 'g'), "Kongo"},
    {MAKECODE('\0', '\0', 'k', 'i'), "Kikuyu"},
    {MAKECODE('\0', '\0', 'k', 'j'), "Kuanyama"},
    {MAKECODE('\0', '\0', 'k', 'k'), "Kazakh"},
    {MAKECODE('\0', '\0', 'k', 'l'), "Kalaallisut"},
    {MAKECODE('\0', '\0', 'k', 'm'), "Khmer"},
    {MAKECODE('\0', '\0', 'k', 'n'), "Kannada"},
    {MAKECODE('\0', '\0', 'k', 'o'), "Korean"},
    {MAKECODE('\0', '\0', 'k', 'r'), "Kanuri"},
    {MAKECODE('\0', '\0', 'k', 's'), "Kashmiri"},
    {MAKECODE('\0', '\0', 'k', 'u'), "Kurdish"},
    {MAKECODE('\0', '\0', 'k', 'v'), "Komi"},
    {MAKECODE('\0', '\0', 'k', 'w'), "Cornish"},
    {MAKECODE('\0', '\0', 'k', 'y'), "Kirghiz"},
    {MAKECODE('\0', '\0', 'l', 'a'), "Latin"},
    {MAKECODE('\0', '\0', 'l', 'b'), "Luxembourgish"},
    {MAKECODE('\0', '\0', 'l', 'g'), "Ganda"},
    {MAKECODE('\0', '\0', 'l', 'i'), "Limburgan"},
    {MAKECODE('\0', '\0', 'l', 'n'), "Lingala"},
    {MAKECODE('\0', '\0', 'l', 'o'), "Lao"},
    {MAKECODE('\0', '\0', 'l', 't'), "Lithuanian"},
    {MAKECODE('\0', '\0', 'l', 'u'), "Luba-Katanga"},
    {MAKECODE('\0', '\0', 'l', 'v'), "Latvian, Lettish"},
    {MAKECODE('\0', '\0', 'm', 'g'), "Malagasy"},
    {MAKECODE('\0', '\0', 'm', 'h'), "Marshallese"},
    {MAKECODE('\0', '\0', 'm', 'i'), "Maori"},
    {MAKECODE('\0', '\0', 'm', 'k'), "Macedonian"},
    {MAKECODE('\0', '\0', 'm', 'l'), "Malayalam"},
    {MAKECODE('\0', '\0', 'm', 'n'), "Mongolian"},
    {MAKECODE('\0', '\0', 'm', 'r'), "Marathi"},
    {MAKECODE('\0', '\0', 'm', 's'), "Malay"},
    {MAKECODE('\0', '\0', 'm', 't'), "Maltese"},
    {MAKECODE('\0', '\0', 'm', 'y'), "Burmese"},
    {MAKECODE('\0', '\0', 'n', 'a'), "Nauru"},
    {MAKECODE('\0', '\0', 'n', 'b'), "Norwegian Bokm\xC3\xA5l"},
    {MAKECODE('\0', '\0', 'n', 'd'), "Ndebele, North"},
    {MAKECODE('\0', '\0', 'n', 'e'), "Nepali"},
    {MAKECODE('\0', '\0', 'n', 'g'), "Ndonga"},
    {MAKECODE('\0', '\0', 'n', 'l'), "Dutch"},
    {MAKECODE('\0', '\0', 'n', 'n'), "Norwegian Nynorsk"},
    {MAKECODE('\0', '\0', 'n', 'o'), "Norwegian"},
    {MAKECODE('\0', '\0', 'n', 'r'), "Ndebele, South"},
    {MAKECODE('\0', '\0', 'n', 'v'), "Navajo"},
    {MAKECODE('\0', '\0', 'n', 'y'), "Chichewa"},
    {MAKECODE('\0', '\0', 'o', 'c'), "Occitan"},
    {MAKECODE('\0', '\0', 'o', 'j'), "Ojibwa"},
    {MAKECODE('\0', '\0', 'o', 'm'), "Oromo"},
    {MAKECODE('\0', '\0', 'o', 'r'), "Oriya"},
    {MAKECODE('\0', '\0', 'o', 's'), "Ossetic"},
    {MAKECODE('\0', '\0', 'p', 'a'), "Punjabi"},
    {MAKECODE('\0', '\0', 'p', 'i'), "Pali"},
    {MAKECODE('\0', '\0', 'p', 'l'), "Polish"},
    {MAKECODE('\0', '\0', 'p', 's'), "Pashto, Pushto"},
    {MAKECODE('\0', '\0', 'p', 't'), "Portuguese"},
    // pb = unofficial language code for Brazilian Portuguese
    {MAKECODE('\0', '\0', 'p', 'b'), "Portuguese (Brazil)"},
    {MAKECODE('\0', '\0', 'q', 'u'), "Quechua"},
    {MAKECODE('\0', '\0', 'r', 'm'), "Romansh"},
    {MAKECODE('\0', '\0', 'r', 'n'), "Kirundi"},
    {MAKECODE('\0', '\0', 'r', 'o'), "Romanian"},
    {MAKECODE('\0', '\0', 'r', 'u'), "Russian"},
    {MAKECODE('\0', '\0', 'r', 'w'), "Kinyarwanda"},
    {MAKECODE('\0', '\0', 's', 'a'), "Sanskrit"},
    {MAKECODE('\0', '\0', 's', 'c'), "Sardinian"},
    {MAKECODE('\0', '\0', 's', 'd'), "Sindhi"},
    {MAKECODE('\0', '\0', 's', 'e'), "Northern Sami"},
    {MAKECODE('\0', '\0', 's', 'g'), "Sangho"},
    {MAKECODE('\0', '\0', 's', 'h'), "Serbo-Croatian"},
    {MAKECODE('\0', '\0', 's', 'i'), "Sinhalese"},
    {MAKECODE('\0', '\0', 's', 'k'), "Slovak"},
    {MAKECODE('\0', '\0', 's', 'l'), "Slovenian"},
    {MAKECODE('\0', '\0', 's', 'm'), "Samoan"},
    {MAKECODE('\0', '\0', 's', 'n'), "Shona"},
    {MAKECODE('\0', '\0', 's', 'o'), "Somali"},
    {MAKECODE('\0', '\0', 's', 'q'), "Albanian"},
    {MAKECODE('\0', '\0', 's', 'r'), "Serbian"},
    {MAKECODE('\0', '\0', 's', 's'), "Swati"},
    {MAKECODE('\0', '\0', 's', 't'), "Sesotho"},
    {MAKECODE('\0', '\0', 's', 'u'), "Sundanese"},
    {MAKECODE('\0', '\0', 's', 'v'), "Swedish"},
    {MAKECODE('\0', '\0', 's', 'w'), "Swahili"},
    {MAKECODE('\0', '\0', 't', 'a'), "Tamil"},
    {MAKECODE('\0', '\0', 't', 'e'), "Telugu"},
    {MAKECODE('\0', '\0', 't', 'g'), "Tajik"},
    {MAKECODE('\0', '\0', 't', 'h'), "Thai"},
    {MAKECODE('\0', '\0', 't', 'i'), "Tigrinya"},
    {MAKECODE('\0', '\0', 't', 'k'), "Turkmen"},
    {MAKECODE('\0', '\0', 't', 'l'), "Tagalog"},
    {MAKECODE('\0', '\0', 't', 'n'), "Tswana"},
    {MAKECODE('\0', '\0', 't', 'o'), "Tonga"},
    {MAKECODE('\0', '\0', 't', 'r'), "Turkish"},
    {MAKECODE('\0', '\0', 't', 's'), "Tsonga"},
    {MAKECODE('\0', '\0', 't', 't'), "Tatar"},
    {MAKECODE('\0', '\0', 't', 'w'), "Twi"},
    {MAKECODE('\0', '\0', 't', 'y'), "Tahitian"},
    {MAKECODE('\0', '\0', 'u', 'g'), "Uighur"},
    {MAKECODE('\0', '\0', 'u', 'k'), "Ukrainian"},
    {MAKECODE('\0', '\0', 'u', 'r'), "Urdu"},
    {MAKECODE('\0', '\0', 'u', 'z'), "Uzbek"},
    {MAKECODE('\0', '\0', 'v', 'e'), "Venda"},
    {MAKECODE('\0', '\0', 'v', 'i'), "Vietnamese"},
    {MAKECODE('\0', '\0', 'v', 'o'), "Volapuk"},
    {MAKECODE('\0', '\0', 'w', 'a'), "Walloon"},
    {MAKECODE('\0', '\0', 'w', 'o'), "Wolof"},
    {MAKECODE('\0', '\0', 'x', 'h'), "Xhosa"},
    {MAKECODE('\0', '\0', 'y', 'i'), "Yiddish"},
    {MAKECODE('\0', '\0', 'y', 'o'), "Yoruba"},
    {MAKECODE('\0', '\0', 'z', 'a'), "Zhuang"},
    {MAKECODE('\0', '\0', 'z', 'h'), "Chinese"},
    {MAKECODE('\0', '\0', 'z', 'u'), "Zulu"},
}};
// clang-format on

// clang-format off
const std::array<struct LCENTRY, 540> g_iso639_2 = {{
    {MAKECODE('\0', 'a', 'b', 'k'), "Abkhaz"},
    {MAKECODE('\0', 'a', 'b', 'k'), "Abkhazian"},
    {MAKECODE('\0', 'a', 'c', 'e'), "Achinese"},
    {MAKECODE('\0', 'a', 'c', 'h'), "Acoli"},
    {MAKECODE('\0', 'a', 'd', 'a'), "Adangme"},
    {MAKECODE('\0', 'a', 'd', 'y'), "Adygei"},
    {MAKECODE('\0', 'a', 'd', 'y'), "Adyghe"},
    {MAKECODE('\0', 'a', 'a', 'r'), "Afar"},
    {MAKECODE('\0', 'a', 'f', 'h'), "Afrihili"},
    {MAKECODE('\0', 'a', 'f', 'r'), "Afrikaans"},
    {MAKECODE('\0', 'a', 'f', 'a'), "Afro-Asiatic (Other)"},
    {MAKECODE('\0', 'a', 'k', 'a'), "Akan"},
    {MAKECODE('\0', 'a', 'k', 'k'), "Akkadian"},
    {MAKECODE('\0', 'a', 'l', 'b'), "Albanian"},
    {MAKECODE('\0', 's', 'q', 'i'), "Albanian"},
    {MAKECODE('\0', 'a', 'l', 'e'), "Aleut"},
    {MAKECODE('\0', 'a', 'l', 'g'), "Algonquian languages"},
    {MAKECODE('\0', 't', 'u', 't'), "Altaic (Other)"},
    {MAKECODE('\0', 'a', 'm', 'h'), "Amharic"},
    {MAKECODE('\0', 'a', 'p', 'a'), "Apache languages"},
    {MAKECODE('\0', 'a', 'r', 'a'), "Arabic"},
    {MAKECODE('\0', 'a', 'r', 'g'), "Aragonese"},
    {MAKECODE('\0', 'a', 'r', 'c'), "Aramaic"},
    {MAKECODE('\0', 'a', 'r', 'p'), "Arapaho"},
    {MAKECODE('\0', 'a', 'r', 'n'), "Araucanian"},
    {MAKECODE('\0', 'a', 'r', 'w'), "Arawak"},
    {MAKECODE('\0', 'a', 'r', 'm'), "Armenian"},
    {MAKECODE('\0', 'h', 'y', 'e'), "Armenian"},
    {MAKECODE('\0', 'a', 'r', 't'), "Artificial (Other)"},
    {MAKECODE('\0', 'a', 's', 'm'), "Assamese"},
    {MAKECODE('\0', 'a', 's', 't'), "Asturian"},
    {MAKECODE('\0', 'a', 't', 'h'), "Athapascan languages"},
    {MAKECODE('\0', 'a', 'u', 's'), "Australian languages"},
    {MAKECODE('\0', 'm', 'a', 'p'), "Austronesian (Other)"},
    {MAKECODE('\0', 'a', 'v', 'a'), "Avaric"},
    {MAKECODE('\0', 'a', 'v', 'e'), "Avestan"},
    {MAKECODE('\0', 'a', 'w', 'a'), "Awadhi"},
    {MAKECODE('\0', 'a', 'y', 'm'), "Aymara"},
    {MAKECODE('\0', 'a', 'z', 'e'), "Azerbaijani"},
    {MAKECODE('\0', 'a', 's', 't'), "Bable"},
    {MAKECODE('\0', 'b', 'a', 'n'), "Balinese"},
    {MAKECODE('\0', 'b', 'a', 't'), "Baltic (Other)"},
    {MAKECODE('\0', 'b', 'a', 'l'), "Baluchi"},
    {MAKECODE('\0', 'b', 'a', 'm'), "Bambara"},
    {MAKECODE('\0', 'b', 'a', 'i'), "Bamileke languages"},
    {MAKECODE('\0', 'b', 'a', 'd'), "Banda"},
    {MAKECODE('\0', 'b', 'n', 't'), "Bantu (Other)"},
    {MAKECODE('\0', 'b', 'a', 's'), "Basa"},
    {MAKECODE('\0', 'b', 'a', 'k'), "Bashkir"},
    {MAKECODE('\0', 'b', 'a', 'q'), "Basque"},
    {MAKECODE('\0', 'e', 'u', 's'), "Basque"},
    {MAKECODE('\0', 'b', 't', 'k'), "Batak (Indonesia)"},
    {MAKECODE('\0', 'b', 'e', 'j'), "Beja"},
    {MAKECODE('\0', 'b', 'e', 'l'), "Belarusian"},
    {MAKECODE('\0', 'b', 'e', 'm'), "Bemba"},
    {MAKECODE('\0', 'b', 'e', 'n'), "Bengali"},
    {MAKECODE('\0', 'b', 'e', 'r'), "Berber (Other)"},
    {MAKECODE('\0', 'b', 'h', 'o'), "Bhojpuri"},
    {MAKECODE('\0', 'b', 'i', 'h'), "Bihari"},
    {MAKECODE('\0', 'b', 'i', 'k'), "Bikol"},
    {MAKECODE('\0', 'b', 'y', 'n'), "Bilin"},
    {MAKECODE('\0', 'b', 'i', 'n'), "Bini"},
    {MAKECODE('\0', 'b', 'i', 's'), "Bislama"},
    {MAKECODE('\0', 'b', 'y', 'n'), "Blin"},
    {MAKECODE('\0', 'n', 'o', 'b'), "Bokm\xC3\xA5l, Norwegian"},
    {MAKECODE('\0', 'b', 'o', 's'), "Bosnian"},
    {MAKECODE('\0', 'b', 'r', 'a'), "Braj"},
    {MAKECODE('\0', 'b', 'r', 'e'), "Breton"},
    {MAKECODE('\0', 'b', 'u', 'g'), "Buginese"},
    {MAKECODE('\0', 'b', 'u', 'l'), "Bulgarian"},
    {MAKECODE('\0', 'b', 'u', 'a'), "Buriat"},
    {MAKECODE('\0', 'b', 'u', 'r'), "Burmese"},
    {MAKECODE('\0', 'm', 'y', 'a'), "Burmese"},
    {MAKECODE('\0', 'c', 'a', 'd'), "Caddo"},
    {MAKECODE('\0', 'c', 'a', 'r'), "Carib"},
    {MAKECODE('\0', 's', 'p', 'a'), "Spanish"},
    {MAKECODE('\0', 'c', 'a', 't'), "Catalan"},
    {MAKECODE('\0', 'c', 'a', 'u'), "Caucasian (Other)"},
    {MAKECODE('\0', 'c', 'e', 'b'), "Cebuano"},
    {MAKECODE('\0', 'c', 'e', 'l'), "Celtic (Other)"},
    {MAKECODE('\0', 'c', 'h', 'g'), "Chagatai"},
    {MAKECODE('\0', 'c', 'm', 'c'), "Chamic languages"},
    {MAKECODE('\0', 'c', 'h', 'a'), "Chamorro"},
    {MAKECODE('\0', 'c', 'h', 'e'), "Chechen"},
    {MAKECODE('\0', 'c', 'h', 'r'), "Cherokee"},
    {MAKECODE('\0', 'n', 'y', 'a'), "Chewa"},
    {MAKECODE('\0', 'c', 'h', 'y'), "Cheyenne"},
    {MAKECODE('\0', 'c', 'h', 'b'), "Chibcha"},
    {MAKECODE('\0', 'n', 'y', 'a'), "Chichewa"},
    {MAKECODE('\0', 'c', 'h', 'i'), "Chinese"},
    {MAKECODE('\0', 'z', 'h', 'o'), "Chinese"},
    {MAKECODE('\0', 'c', 'h', 'n'), "Chinook jargon"},
    {MAKECODE('\0', 'c', 'h', 'p'), "Chipewyan"},
    {MAKECODE('\0', 'c', 'h', 'o'), "Choctaw"},
    {MAKECODE('\0', 'z', 'h', 'a'), "Chuang"},
    {MAKECODE('\0', 'c', 'h', 'u'), "Church Slavonic"},
    {MAKECODE('\0', 'c', 'h', 'k'), "Chuukese"},
    {MAKECODE('\0', 'c', 'h', 'v'), "Chuvash"},
    {MAKECODE('\0', 'n', 'w', 'c'), "Classical Nepal Bhasa"},
    {MAKECODE('\0', 'n', 'w', 'c'), "Classical Newari"},
    {MAKECODE('\0', 'c', 'o', 'p'), "Coptic"},
    {MAKECODE('\0', 'c', 'o', 'r'), "Cornish"},
    {MAKECODE('\0', 'c', 'o', 's'), "Corsican"},
    {MAKECODE('\0', 'c', 'r', 'e'), "Cree"},
    {MAKECODE('\0', 'm', 'u', 's'), "Creek"},
    {MAKECODE('\0', 'c', 'r', 'p'), "Creoles and pidgins (Other)"},
    {MAKECODE('\0', 'c', 'p', 'e'), "English-based (Other)"},
    {MAKECODE('\0', 'c', 'p', 'f'), "French-based (Other)"},
    {MAKECODE('\0', 'c', 'p', 'p'), "Portuguese-based (Other)"},
    {MAKECODE('\0', 'c', 'r', 'h'), "Crimean Tatar"},
    {MAKECODE('\0', 'c', 'r', 'h'), "Crimean Turkish"},
    {MAKECODE('\0', 'h', 'r', 'v'), "Croatian"},
    {MAKECODE('\0', 's', 'c', 'r'), "Croatian"},
    {MAKECODE('\0', 'c', 'u', 's'), "Cushitic (Other)"},
    {MAKECODE('\0', 'c', 'z', 'e'), "Czech"},
    {MAKECODE('\0', 'c', 'e', 's'), "Czech"},
    {MAKECODE('\0', 'd', 'a', 'k'), "Dakota"},
    {MAKECODE('\0', 'd', 'a', 'n'), "Danish"},
    {MAKECODE('\0', 'd', 'a', 'r'), "Dargwa"},
    {MAKECODE('\0', 'd', 'a', 'y'), "Dayak"},
    {MAKECODE('\0', 'd', 'e', 'l'), "Delaware"},
    {MAKECODE('\0', 'd', 'i', 'n'), "Dinka"},
    {MAKECODE('\0', 'd', 'i', 'v'), "Divehi"},
    {MAKECODE('\0', 'd', 'o', 'i'), "Dogri"},
    {MAKECODE('\0', 'd', 'g', 'r'), "Dogrib"},
    {MAKECODE('\0', 'd', 'r', 'a'), "Dravidian (Other)"},
    {MAKECODE('\0', 'd', 'u', 'a'), "Duala"},
    {MAKECODE('\0', 'd', 'u', 't'), "Dutch"},
    {MAKECODE('\0', 'n', 'l', 'd'), "Dutch"},
    {MAKECODE('\0', 'd', 'u', 'm'), "Dutch, Middle (ca. 1050-1350)"},
    {MAKECODE('\0', 'd', 'y', 'u'), "Dyula"},
    {MAKECODE('\0', 'd', 'z', 'o'), "Dzongkha"},
    {MAKECODE('\0', 'e', 'f', 'i'), "Efik"},
    {MAKECODE('\0', 'e', 'g', 'y'), "Egyptian (Ancient)"},
    {MAKECODE('\0', 'e', 'k', 'a'), "Ekajuk"},
    {MAKECODE('\0', 'e', 'l', 'x'), "Elamite"},
    {MAKECODE('\0', 'e', 'n', 'g'), "English"},
    {MAKECODE('\0', 'e', 'n', 'm'), "English, Middle (1100-1500)"},
    {MAKECODE('\0', 'a', 'n', 'g'), "English, Old (ca.450-1100)"},
    {MAKECODE('\0', 'm', 'y', 'v'), "Erzya"},
    {MAKECODE('\0', 'e', 'p', 'o'), "Esperanto"},
    {MAKECODE('\0', 'e', 's', 't'), "Estonian"},
    {MAKECODE('\0', 'e', 'w', 'e'), "Ewe"},
    {MAKECODE('\0', 'e', 'w', 'o'), "Ewondo"},
    {MAKECODE('\0', 'f', 'a', 'n'), "Fang"},
    {MAKECODE('\0', 'f', 'a', 't'), "Fanti"},
    {MAKECODE('\0', 'f', 'a', 'o'), "Faroese"},
    {MAKECODE('\0', 'f', 'i', 'j'), "Fijian"},
    {MAKECODE('\0', 'f', 'i', 'l'), "Filipino"},
    {MAKECODE('\0', 'f', 'i', 'n'), "Finnish"},
    {MAKECODE('\0', 'f', 'i', 'u'), "Finno-Ugrian (Other)"},
    {MAKECODE('\0', 'd', 'u', 't'), "Flemish"},
    {MAKECODE('\0', 'n', 'l', 'd'), "Flemish"},
    {MAKECODE('\0', 'f', 'o', 'n'), "Fon"},
    {MAKECODE('\0', 'f', 'r', 'e'), "French"},
    {MAKECODE('\0', 'f', 'r', 'a'), "French"},
    {MAKECODE('\0', 'f', 'r', 'm'), "French, Middle (ca.1400-1600)"},
    {MAKECODE('\0', 'f', 'r', 'o'), "French, Old (842-ca.1400)"},
    {MAKECODE('\0', 'f', 'r', 'y'), "Frisian"},
    {MAKECODE('\0', 'f', 'u', 'r'), "Friulian"},
    {MAKECODE('\0', 'f', 'u', 'l'), "Fulah"},
    {MAKECODE('\0', 'g', 'a', 'a'), "Ga"},
    {MAKECODE('\0', 'g', 'l', 'a'), "Gaelic"},
    {MAKECODE('\0', 'g', 'l', 'g'), "Gallegan"},
    {MAKECODE('\0', 'l', 'u', 'g'), "Ganda"},
    {MAKECODE('\0', 'g', 'a', 'y'), "Gayo"},
    {MAKECODE('\0', 'g', 'b', 'a'), "Gbaya"},
    {MAKECODE('\0', 'g', 'e', 'z'), "Geez"},
    {MAKECODE('\0', 'g', 'e', 'o'), "Georgian"},
    {MAKECODE('\0', 'k', 'a', 't'), "Georgian"},
    {MAKECODE('\0', 'g', 'e', 'r'), "German"},
    {MAKECODE('\0', 'd', 'e', 'u'), "German"},
    {MAKECODE('\0', 'n', 'd', 's'), "German, Low"},
    {MAKECODE('\0', 'g', 'm', 'h'), "German, Middle High (ca.1050-1500)"},
    {MAKECODE('\0', 'g', 'o', 'h'), "German, Old High (ca.750-1050)"},
    {MAKECODE('\0', 'g', 's', 'w'), "German, Swiss German"},
    {MAKECODE('\0', 'g', 'e', 'm'), "Germanic (Other)"},
    {MAKECODE('\0', 'k', 'i', 'k'), "Gikuyu"},
    {MAKECODE('\0', 'g', 'i', 'l'), "Gilbertese"},
    {MAKECODE('\0', 'g', 'o', 'n'), "Gondi"},
    {MAKECODE('\0', 'g', 'o', 'r'), "Gorontalo"},
    {MAKECODE('\0', 'g', 'o', 't'), "Gothic"},
    {MAKECODE('\0', 'g', 'r', 'b'), "Grebo"},
    {MAKECODE('\0', 'g', 'r', 'c'), "Greek, Ancient (to 1453)"},
    {MAKECODE('\0', 'g', 'r', 'e'), "Greek, Modern (1453-)"},
    {MAKECODE('\0', 'e', 'l', 'l'), "Greek, Modern (1453-)"},
    {MAKECODE('\0', 'k', 'a', 'l'), "Greenlandic"},
    {MAKECODE('\0', 'g', 'r', 'n'), "Guarani"},
    {MAKECODE('\0', 'g', 'u', 'j'), "Gujarati"},
    {MAKECODE('\0', 'g', 'w', 'i'), "Gwich\xC2\xB4in"},
    {MAKECODE('\0', 'h', 'a', 'i'), "Haida"},
    {MAKECODE('\0', 'h', 'a', 't'), "Haitian"},
    {MAKECODE('\0', 'h', 'a', 't'), "Haitian Creole"},
    {MAKECODE('\0', 'h', 'a', 'u'), "Hausa"},
    {MAKECODE('\0', 'h', 'a', 'w'), "Hawaiian"},
    {MAKECODE('\0', 'h', 'e', 'b'), "Hebrew"},
    {MAKECODE('\0', 'h', 'e', 'r'), "Herero"},
    {MAKECODE('\0', 'h', 'i', 'l'), "Hiligaynon"},
    {MAKECODE('\0', 'h', 'i', 'm'), "Himachali"},
    {MAKECODE('\0', 'h', 'i', 'n'), "Hindi"},
    {MAKECODE('\0', 'h', 'm', 'o'), "Hiri Motu"},
    {MAKECODE('\0', 'h', 'i', 't'), "Hittite"},
    {MAKECODE('\0', 'h', 'm', 'n'), "Hmong"},
    {MAKECODE('\0', 'h', 'u', 'n'), "Hungarian"},
    {MAKECODE('\0', 'h', 'u', 'p'), "Hupa"},
    {MAKECODE('\0', 'i', 'b', 'a'), "Iban"},
    {MAKECODE('\0', 'i', 'c', 'e'), "Icelandic"},
    {MAKECODE('\0', 'i', 's', 'l'), "Icelandic"},
    {MAKECODE('\0', 'i', 'd', 'o'), "Ido"},
    {MAKECODE('\0', 'i', 'b', 'o'), "Igbo"},
    {MAKECODE('\0', 'i', 'j', 'o'), "Ijo"},
    {MAKECODE('\0', 'i', 'l', 'o'), "Iloko"},
    {MAKECODE('\0', 's', 'm', 'n'), "Inari Sami"},
    {MAKECODE('\0', 'i', 'n', 'c'), "Indic (Other)"},
    {MAKECODE('\0', 'i', 'n', 'e'), "Indo-European (Other)"},
    {MAKECODE('\0', 'i', 'n', 'd'), "Indonesian"},
    {MAKECODE('\0', 'i', 'n', 'h'), "Ingush"},
    {MAKECODE('\0', 'i', 'n', 'a'), "Auxiliary Language Association)"},
    {MAKECODE('\0', 'i', 'l', 'e'), "Interlingue"},
    {MAKECODE('\0', 'i', 'k', 'u'), "Inuktitut"},
    {MAKECODE('\0', 'i', 'p', 'k'), "Inupiaq"},
    {MAKECODE('\0', 'i', 'r', 'a'), "Iranian (Other)"},
    {MAKECODE('\0', 'g', 'l', 'e'), "Irish"},
    {MAKECODE('\0', 'm', 'g', 'a'), "Irish, Middle (900-1200)"},
    {MAKECODE('\0', 's', 'g', 'a'), "Irish, Old (to 900)"},
    {MAKECODE('\0', 'i', 'r', 'o'), "Iroquoian languages"},
    {MAKECODE('\0', 'i', 't', 'a'), "Italian"},
    {MAKECODE('\0', 'j', 'p', 'n'), "Japanese"},
    {MAKECODE('\0', 'j', 'a', 'v'), "Javanese"},
    {MAKECODE('\0', 'j', 'r', 'b'), "Judeo-Arabic"},
    {MAKECODE('\0', 'j', 'p', 'r'), "Judeo-Persian"},
    {MAKECODE('\0', 'k', 'b', 'd'), "Kabardian"},
    {MAKECODE('\0', 'k', 'a', 'b'), "Kabyle"},
    {MAKECODE('\0', 'k', 'a', 'c'), "Kachin"},
    {MAKECODE('\0', 'k', 'a', 'l'), "Kalaallisut"},
    {MAKECODE('\0', 'x', 'a', 'l'), "Kalmyk"},
    {MAKECODE('\0', 'k', 'a', 'm'), "Kamba"},
    {MAKECODE('\0', 'k', 'a', 'n'), "Kannada"},
    {MAKECODE('\0', 'k', 'a', 'u'), "Kanuri"},
    {MAKECODE('\0', 'k', 'r', 'c'), "Karachay-Balkar"},
    {MAKECODE('\0', 'k', 'a', 'a'), "Kara-Kalpak"},
    {MAKECODE('\0', 'k', 'a', 'r'), "Karen"},
    {MAKECODE('\0', 'k', 'a', 's'), "Kashmiri"},
    {MAKECODE('\0', 'c', 's', 'b'), "Kashubian"},
    {MAKECODE('\0', 'k', 'a', 'w'), "Kawi"},
    {MAKECODE('\0', 'k', 'a', 'z'), "Kazakh"},
    {MAKECODE('\0', 'k', 'h', 'a'), "Khasi"},
    {MAKECODE('\0', 'k', 'h', 'm'), "Khmer"},
    {MAKECODE('\0', 'k', 'h', 'i'), "Khoisan (Other)"},
    {MAKECODE('\0', 'k', 'h', 'o'), "Khotanese"},
    {MAKECODE('\0', 'k', 'i', 'k'), "Kikuyu"},
    {MAKECODE('\0', 'k', 'm', 'b'), "Kimbundu"},
    {MAKECODE('\0', 'k', 'i', 'n'), "Kinyarwanda"},
    {MAKECODE('\0', 'k', 'i', 'r'), "Kirghiz"},
    {MAKECODE('\0', 't', 'l', 'h'), "Klingon"},
    {MAKECODE('\0', 'k', 'o', 'm'), "Komi"},
    {MAKECODE('\0', 'k', 'o', 'n'), "Kongo"},
    {MAKECODE('\0', 'k', 'o', 'k'), "Konkani"},
    {MAKECODE('\0', 'k', 'o', 'r'), "Korean"},
    {MAKECODE('\0', 'k', 'o', 's'), "Kosraean"},
    {MAKECODE('\0', 'k', 'p', 'e'), "Kpelle"},
    {MAKECODE('\0', 'k', 'r', 'o'), "Kru"},
    {MAKECODE('\0', 'k', 'u', 'a'), "Kuanyama"},
    {MAKECODE('\0', 'k', 'u', 'm'), "Kumyk"},
    {MAKECODE('\0', 'k', 'u', 'r'), "Kurdish"},
    {MAKECODE('\0', 'k', 'r', 'u'), "Kurukh"},
    {MAKECODE('\0', 'k', 'u', 't'), "Kutenai"},
    {MAKECODE('\0', 'k', 'u', 'a'), "Kwanyama, Kuanyama"},
    {MAKECODE('\0', 'l', 'a', 'd'), "Ladino"},
    {MAKECODE('\0', 'l', 'a', 'h'), "Lahnda"},
    {MAKECODE('\0', 'l', 'a', 'm'), "Lamba"},
    {MAKECODE('\0', 'l', 'a', 'o'), "Lao"},
    {MAKECODE('\0', 'l', 'a', 't'), "Latin"},
    {MAKECODE('\0', 'l', 'a', 'v'), "Latvian"},
    {MAKECODE('\0', 'l', 't', 'z'), "Letzeburgesch"},
    {MAKECODE('\0', 'l', 'e', 'z'), "Lezghian"},
    {MAKECODE('\0', 'l', 'i', 'm'), "Limburgan"},
    {MAKECODE('\0', 'l', 'i', 'm'), "Limburger"},
    {MAKECODE('\0', 'l', 'i', 'm'), "Limburgish"},
    {MAKECODE('\0', 'l', 'i', 'n'), "Lingala"},
    {MAKECODE('\0', 'l', 'i', 't'), "Lithuanian"},
    {MAKECODE('\0', 'j', 'b', 'o'), "Lojban"},
    {MAKECODE('\0', 'n', 'd', 's'), "Low German"},
    {MAKECODE('\0', 'n', 'd', 's'), "Low Saxon"},
    {MAKECODE('\0', 'd', 's', 'b'), "Lower Sorbian"},
    {MAKECODE('\0', 'l', 'o', 'z'), "Lozi"},
    {MAKECODE('\0', 'l', 'u', 'b'), "Luba-Katanga"},
    {MAKECODE('\0', 'l', 'u', 'a'), "Luba-Lulua"},
    {MAKECODE('\0', 'l', 'u', 'i'), "Luiseno"},
    {MAKECODE('\0', 's', 'm', 'j'), "Lule Sami"},
    {MAKECODE('\0', 'l', 'u', 'n'), "Lunda"},
    {MAKECODE('\0', 'l', 'u', 'o'), "Luo (Kenya and Tanzania)"},
    {MAKECODE('\0', 'l', 'u', 's'), "Lushai"},
    {MAKECODE('\0', 'l', 't', 'z'), "Luxembourgish"},
    {MAKECODE('\0', 'm', 'a', 'c'), "Macedonian"},
    {MAKECODE('\0', 'm', 'k', 'd'), "Macedonian"},
    {MAKECODE('\0', 'm', 'a', 'd'), "Madurese"},
    {MAKECODE('\0', 'm', 'a', 'g'), "Magahi"},
    {MAKECODE('\0', 'm', 'a', 'i'), "Maithili"},
    {MAKECODE('\0', 'm', 'a', 'k'), "Makasar"},
    {MAKECODE('\0', 'm', 'l', 'g'), "Malagasy"},
    {MAKECODE('\0', 'm', 'a', 'y'), "Malay"},
    {MAKECODE('\0', 'm', 's', 'a'), "Malay"},
    {MAKECODE('\0', 'm', 'a', 'l'), "Malayalam"},
    {MAKECODE('\0', 'm', 'l', 't'), "Maltese"},
    {MAKECODE('\0', 'm', 'n', 'c'), "Manchu"},
    {MAKECODE('\0', 'm', 'd', 'r'), "Mandar"},
    {MAKECODE('\0', 'm', 'a', 'n'), "Mandingo"},
    {MAKECODE('\0', 'm', 'n', 'i'), "Manipuri"},
    {MAKECODE('\0', 'm', 'n', 'o'), "Manobo languages"},
    {MAKECODE('\0', 'g', 'l', 'v'), "Manx"},
    {MAKECODE('\0', 'm', 'a', 'o'), "Maori"},
    {MAKECODE('\0', 'm', 'r', 'i'), "Maori"},
    {MAKECODE('\0', 'm', 'a', 'r'), "Marathi"},
    {MAKECODE('\0', 'c', 'h', 'm'), "Mari"},
    {MAKECODE('\0', 'm', 'a', 'h'), "Marshallese"},
    {MAKECODE('\0', 'm', 'w', 'r'), "Marwari"},
    {MAKECODE('\0', 'm', 'a', 's'), "Masai"},
    {MAKECODE('\0', 'm', 'y', 'n'), "Mayan languages"},
    {MAKECODE('\0', 'm', 'e', 'n'), "Mende"},
    {MAKECODE('\0', 'm', 'i', 'c'), "Micmac"},
    {MAKECODE('\0', 'm', 'i', 'c'), "Mi'kmaq"},
    {MAKECODE('\0', 'm', 'i', 'n'), "Minangkabau"},
    {MAKECODE('\0', 'm', 'w', 'l'), "Mirandese"},
    {MAKECODE('\0', 'm', 'i', 's'), "Miscellaneous languages"},
    {MAKECODE('\0', 'm', 'o', 'h'), "Mohawk"},
    {MAKECODE('\0', 'm', 'd', 'f'), "Moksha"},
    {MAKECODE('\0', 'm', 'o', 'l'), "Moldavian"},
    {MAKECODE('\0', 'm', 'k', 'h'), "Mon-Khmer (Other)"},
    {MAKECODE('\0', 'l', 'o', 'l'), "Mongo"},
    {MAKECODE('\0', 'm', 'o', 'n'), "Mongolian"},
    {MAKECODE('\0', 'm', 'o', 's'), "Mossi"},
    {MAKECODE('\0', 'm', 'u', 'l'), "Multiple languages"},
    {MAKECODE('\0', 'm', 'u', 'n'), "Munda languages"},
    {MAKECODE('\0', 'n', 'a', 'h'), "Nahuatl"},
    {MAKECODE('\0', 'n', 'a', 'u'), "Nauru"},
    {MAKECODE('\0', 'n', 'a', 'v'), "Navaho, Navajo"},
    {MAKECODE('\0', 'n', 'a', 'v'), "Navajo"},
    {MAKECODE('\0', 'n', 'd', 'e'), "Ndebele, North"},
    {MAKECODE('\0', 'n', 'b', 'l'), "Ndebele, South"},
    {MAKECODE('\0', 'n', 'd', 'o'), "Ndonga"},
    {MAKECODE('\0', 'n', 'a', 'p'), "Neapolitan"},
    {MAKECODE('\0', 'n', 'e', 'w'), "Nepal Bhasa"},
    {MAKECODE('\0', 'n', 'e', 'p'), "Nepali"},
    {MAKECODE('\0', 'n', 'e', 'w'), "Newari"},
    {MAKECODE('\0', 'n', 'i', 'a'), "Nias"},
    {MAKECODE('\0', 'n', 'i', 'c'), "Niger-Kordofanian (Other)"},
    {MAKECODE('\0', 's', 's', 'a'), "Nilo-Saharan (Other)"},
    {MAKECODE('\0', 'n', 'i', 'u'), "Niuean"},
    {MAKECODE('\0', 'z', 'x', 'x'), "No linguistic content"},
    {MAKECODE('\0', 'n', 'o', 'g'), "Nogai"},
    {MAKECODE('\0', 'n', 'o', 'n'), "Norse, Old"},
    {MAKECODE('\0', 'n', 'a', 'i'), "North American Indian (Other)"},
    {MAKECODE('\0', 's', 'm', 'e'), "Northern Sami"},
    {MAKECODE('\0', 'n', 's', 'o'), "Northern Sotho"},
    {MAKECODE('\0', 'n', 'd', 'e'), "North Ndebele"},
    {MAKECODE('\0', 'n', 'o', 'r'), "Norwegian"},
    {MAKECODE('\0', 'n', 'o', 'b'), "Norwegian Bokm\xC3\xA5l"},
    {MAKECODE('\0', 'n', 'n', 'o'), "Norwegian Nynorsk"},
    {MAKECODE('\0', 'n', 'u', 'b'), "Nubian languages"},
    {MAKECODE('\0', 'n', 'y', 'm'), "Nyamwezi"},
    {MAKECODE('\0', 'n', 'y', 'a'), "Nyanja"},
    {MAKECODE('\0', 'n', 'y', 'n'), "Nyankole"},
    {MAKECODE('\0', 'n', 'n', 'o'), "Nynorsk, Norwegian"},
    {MAKECODE('\0', 'n', 'y', 'o'), "Nyoro"},
    {MAKECODE('\0', 'n', 'z', 'i'), "Nzima"},
    {MAKECODE('\0', 'o', 'c', 'i'), "Occitan (post 1500)"},
    {MAKECODE('\0', 'o', 'j', 'i'), "Ojibwa"},
    {MAKECODE('\0', 'c', 'h', 'u'), "Old Bulgarian"},
    {MAKECODE('\0', 'c', 'h', 'u'), "Old Church Slavonic"},
    {MAKECODE('\0', 'n', 'w', 'c'), "Old Newari"},
    {MAKECODE('\0', 'c', 'h', 'u'), "Old Slavonic"},
    {MAKECODE('\0', 'o', 'r', 'i'), "Oriya"},
    {MAKECODE('\0', 'o', 'r', 'm'), "Oromo"},
    {MAKECODE('\0', 'o', 's', 'a'), "Osage"},
    {MAKECODE('\0', 'o', 's', 's'), "Ossetian"},
    {MAKECODE('\0', 'o', 's', 's'), "Ossetic"},
    {MAKECODE('\0', 'o', 't', 'o'), "Otomian languages"},
    {MAKECODE('\0', 'p', 'a', 'l'), "Pahlavi"},
    {MAKECODE('\0', 'p', 'a', 'u'), "Palauan"},
    {MAKECODE('\0', 'p', 'l', 'i'), "Pali"},
    {MAKECODE('\0', 'p', 'a', 'm'), "Pampanga"},
    {MAKECODE('\0', 'p', 'a', 'g'), "Pangasinan"},
    {MAKECODE('\0', 'p', 'a', 'n'), "Panjabi"},
    {MAKECODE('\0', 'p', 'a', 'p'), "Papiamento"},
    {MAKECODE('\0', 'p', 'a', 'a'), "Papuan (Other)"},
    {MAKECODE('\0', 'n', 's', 'o'), "Pedi"},
    {MAKECODE('\0', 'p', 'e', 'r'), "Persian"},
    {MAKECODE('\0', 'f', 'a', 's'), "Persian"},
    {MAKECODE('\0', 'p', 'e', 'o'), "Persian, Old (ca.600-400 B.C.)"},
    {MAKECODE('\0', 'p', 'h', 'i'), "Philippine (Other)"},
    {MAKECODE('\0', 'p', 'h', 'n'), "Phoenician"},
    {MAKECODE('\0', 'f', 'i', 'l'), "Pilipino"},
    {MAKECODE('\0', 'p', 'o', 'n'), "Pohnpeian"},
    {MAKECODE('\0', 'p', 'o', 'l'), "Polish"},
    {MAKECODE('\0', 'p', 'o', 'r'), "Portuguese"},
    // pob = unofficial language code for Brazilian Portuguese
    {MAKECODE('\0', 'p', 'o', 'b'), "Portuguese (Brazil)"},
    {MAKECODE('\0', 'p', 'r', 'a'), "Prakrit languages"},
    {MAKECODE('\0', 'o', 'c', 'i'), "Proven\xC3\xA7"
                                    "al"},
    {MAKECODE('\0', 'p', 'r', 'o'), "Proven\xC3\xA7"
                                    "al, Old (to 1500)"},
    {MAKECODE('\0', 'p', 'a', 'n'), "Punjabi"},
    {MAKECODE('\0', 'p', 'u', 's'), "Pushto"},
    {MAKECODE('\0', 'q', 'u', 'e'), "Quechua"},
    {MAKECODE('\0', 'r', 'o', 'h'), "Raeto-Romance"},
    {MAKECODE('\0', 'r', 'a', 'j'), "Rajasthani"},
    {MAKECODE('\0', 'r', 'a', 'p'), "Rapanui"},
    {MAKECODE('\0', 'r', 'a', 'r'), "Rarotongan"},
    // { "qaa-qtz", "Reserved for local use" },
    {MAKECODE('\0', 'r', 'o', 'a'), "Romance (Other)"},
    {MAKECODE('\0', 'r', 'u', 'm'), "Romanian"},
    {MAKECODE('\0', 'r', 'o', 'n'), "Romanian"},
    {MAKECODE('\0', 'r', 'o', 'm'), "Romany"},
    {MAKECODE('\0', 'r', 'u', 'n'), "Rundi"},
    {MAKECODE('\0', 'r', 'u', 's'), "Russian"},
    {MAKECODE('\0', 's', 'a', 'l'), "Salishan languages"},
    {MAKECODE('\0', 's', 'a', 'm'), "Samaritan Aramaic"},
    {MAKECODE('\0', 's', 'm', 'i'), "Sami languages (Other)"},
    {MAKECODE('\0', 's', 'm', 'o'), "Samoan"},
    {MAKECODE('\0', 's', 'a', 'd'), "Sandawe"},
    {MAKECODE('\0', 's', 'a', 'g'), "Sango"},
    {MAKECODE('\0', 's', 'a', 'n'), "Sanskrit"},
    {MAKECODE('\0', 's', 'a', 't'), "Santali"},
    {MAKECODE('\0', 's', 'r', 'd'), "Sardinian"},
    {MAKECODE('\0', 's', 'a', 's'), "Sasak"},
    {MAKECODE('\0', 'n', 'd', 's'), "Saxon, Low"},
    {MAKECODE('\0', 's', 'c', 'o'), "Scots"},
    {MAKECODE('\0', 'g', 'l', 'a'), "Scottish Gaelic"},
    {MAKECODE('\0', 's', 'e', 'l'), "Selkup"},
    {MAKECODE('\0', 's', 'e', 'm'), "Semitic (Other)"},
    {MAKECODE('\0', 'n', 's', 'o'), "Sepedi"},
    {MAKECODE('\0', 's', 'c', 'c'), "Serbian"},
    {MAKECODE('\0', 's', 'r', 'p'), "Serbian"},
    {MAKECODE('\0', 's', 'r', 'r'), "Serer"},
    {MAKECODE('\0', 's', 'h', 'n'), "Shan"},
    {MAKECODE('\0', 's', 'n', 'a'), "Shona"},
    {MAKECODE('\0', 'i', 'i', 'i'), "Sichuan Yi"},
    {MAKECODE('\0', 's', 'c', 'n'), "Sicilian"},
    {MAKECODE('\0', 's', 'i', 'd'), "Sidamo"},
    {MAKECODE('\0', 's', 'g', 'n'), "Sign languages"},
    {MAKECODE('\0', 'b', 'l', 'a'), "Siksika"},
    {MAKECODE('\0', 's', 'n', 'd'), "Sindhi"},
    {MAKECODE('\0', 's', 'i', 'n'), "Sinhala"},
    {MAKECODE('\0', 's', 'i', 'n'), "Sinhalese"},
    {MAKECODE('\0', 's', 'i', 't'), "Sino-Tibetan (Other)"},
    {MAKECODE('\0', 's', 'i', 'o'), "Siouan languages"},
    {MAKECODE('\0', 's', 'm', 's'), "Skolt Sami"},
    {MAKECODE('\0', 'd', 'e', 'n'), "Slave (Athapascan)"},
    {MAKECODE('\0', 's', 'l', 'a'), "Slavic (Other)"},
    {MAKECODE('\0', 's', 'l', 'o'), "Slovak"},
    {MAKECODE('\0', 's', 'l', 'k'), "Slovak"},
    {MAKECODE('\0', 's', 'l', 'v'), "Slovenian"},
    {MAKECODE('\0', 's', 'o', 'g'), "Sogdian"},
    {MAKECODE('\0', 's', 'o', 'm'), "Somali"},
    {MAKECODE('\0', 's', 'o', 'n'), "Songhai"},
    {MAKECODE('\0', 's', 'n', 'k'), "Soninke"},
    {MAKECODE('\0', 'w', 'e', 'n'), "Sorbian languages"},
    {MAKECODE('\0', 'n', 's', 'o'), "Sotho, Northern"},
    {MAKECODE('\0', 's', 'o', 't'), "Sotho, Southern"},
    {MAKECODE('\0', 's', 'a', 'i'), "South American Indian (Other)"},
    {MAKECODE('\0', 's', 'm', 'a'), "Southern Sami"},
    {MAKECODE('\0', 'n', 'b', 'l'), "South Ndebele"},
    {MAKECODE('\0', 's', 'p', 'a'), "Castilian"},
    {MAKECODE('\0', 's', 'u', 'k'), "Sukuma"},
    {MAKECODE('\0', 's', 'u', 'x'), "Sumerian"},
    {MAKECODE('\0', 's', 'u', 'n'), "Sundanese"},
    {MAKECODE('\0', 's', 'u', 's'), "Susu"},
    {MAKECODE('\0', 's', 'w', 'a'), "Swahili"},
    {MAKECODE('\0', 's', 's', 'w'), "Swati"},
    {MAKECODE('\0', 's', 'w', 'e'), "Swedish"},
    {MAKECODE('\0', 's', 'y', 'r'), "Syriac"},
    {MAKECODE('\0', 't', 'g', 'l'), "Tagalog"},
    {MAKECODE('\0', 't', 'a', 'h'), "Tahitian"},
    {MAKECODE('\0', 't', 'a', 'i'), "Tai (Other)"},
    {MAKECODE('\0', 't', 'g', 'k'), "Tajik"},
    {MAKECODE('\0', 't', 'm', 'h'), "Tamashek"},
    {MAKECODE('\0', 't', 'a', 'm'), "Tamil"},
    {MAKECODE('\0', 't', 'a', 't'), "Tatar"},
    {MAKECODE('\0', 't', 'e', 'l'), "Telugu"},
    {MAKECODE('\0', 't', 'e', 'r'), "Tereno"},
    {MAKECODE('\0', 't', 'e', 't'), "Tetum"},
    {MAKECODE('\0', 't', 'h', 'a'), "Thai"},
    {MAKECODE('\0', 't', 'i', 'b'), "Tibetan"},
    {MAKECODE('\0', 'b', 'o', 'd'), "Tibetan"},
    {MAKECODE('\0', 't', 'i', 'g'), "Tigre"},
    {MAKECODE('\0', 't', 'i', 'r'), "Tigrinya"},
    {MAKECODE('\0', 't', 'e', 'm'), "Timne"},
    {MAKECODE('\0', 't', 'i', 'v'), "Tiv"},
    {MAKECODE('\0', 't', 'l', 'h'), "tlhIngan-Hol"},
    {MAKECODE('\0', 't', 'l', 'i'), "Tlingit"},
    {MAKECODE('\0', 't', 'p', 'i'), "Tok Pisin"},
    {MAKECODE('\0', 't', 'k', 'l'), "Tokelau"},
    {MAKECODE('\0', 't', 'o', 'g'), "Tonga (Nyasa)"},
    {MAKECODE('\0', 't', 'o', 'n'), "Tonga (Tonga Islands)"},
    {MAKECODE('\0', 't', 's', 'i'), "Tsimshian"},
    {MAKECODE('\0', 't', 's', 'o'), "Tsonga"},
    {MAKECODE('\0', 't', 's', 'n'), "Tswana"},
    {MAKECODE('\0', 't', 'u', 'm'), "Tumbuka"},
    {MAKECODE('\0', 't', 'u', 'p'), "Tupi languages"},
    {MAKECODE('\0', 't', 'u', 'r'), "Turkish"},
    {MAKECODE('\0', 'o', 't', 'a'), "Turkish, Ottoman (1500-1928)"},
    {MAKECODE('\0', 't', 'u', 'k'), "Turkmen"},
    {MAKECODE('\0', 't', 'v', 'l'), "Tuvalu"},
    {MAKECODE('\0', 't', 'y', 'v'), "Tuvinian"},
    {MAKECODE('\0', 't', 'w', 'i'), "Twi"},
    {MAKECODE('\0', 'u', 'd', 'm'), "Udmurt"},
    {MAKECODE('\0', 'u', 'g', 'a'), "Ugaritic"},
    {MAKECODE('\0', 'u', 'i', 'g'), "Uighur"},
    {MAKECODE('\0', 'u', 'k', 'r'), "Ukrainian"},
    {MAKECODE('\0', 'u', 'm', 'b'), "Umbundu"},
    {MAKECODE('\0', 'u', 'n', 'd'), "Undetermined"},
    {MAKECODE('\0', 'h', 's', 'b'), "Upper Sorbian"},
    {MAKECODE('\0', 'u', 'r', 'd'), "Urdu"},
    {MAKECODE('\0', 'u', 'i', 'g'), "Uyghur"},
    {MAKECODE('\0', 'u', 'z', 'b'), "Uzbek"},
    {MAKECODE('\0', 'v', 'a', 'i'), "Vai"},
    {MAKECODE('\0', 'c', 'a', 't'), "Valencian"},
    {MAKECODE('\0', 'v', 'e', 'n'), "Venda"},
    {MAKECODE('\0', 'v', 'i', 'e'), "Vietnamese"},
    {MAKECODE('\0', 'v', 'o', 'l'), "Volap\xC3\xBCk"},
    {MAKECODE('\0', 'v', 'o', 't'), "Votic"},
    {MAKECODE('\0', 'w', 'a', 'k'), "Wakashan languages"},
    {MAKECODE('\0', 'w', 'a', 'l'), "Walamo"},
    {MAKECODE('\0', 'w', 'l', 'n'), "Walloon"},
    {MAKECODE('\0', 'w', 'a', 'r'), "Waray"},
    {MAKECODE('\0', 'w', 'a', 's'), "Washo"},
    {MAKECODE('\0', 'w', 'e', 'l'), "Welsh"},
    {MAKECODE('\0', 'c', 'y', 'm'), "Welsh"},
    {MAKECODE('\0', 'w', 'o', 'l'), "Wolof"},
    {MAKECODE('\0', 'x', 'h', 'o'), "Xhosa"},
    {MAKECODE('\0', 's', 'a', 'h'), "Yakut"},
    {MAKECODE('\0', 'y', 'a', 'o'), "Yao"},
    {MAKECODE('\0', 'y', 'a', 'p'), "Yapese"},
    {MAKECODE('\0', 'y', 'i', 'd'), "Yiddish"},
    {MAKECODE('\0', 'y', 'o', 'r'), "Yoruba"},
    {MAKECODE('\0', 'y', 'p', 'k'), "Yupik languages"},
    {MAKECODE('\0', 'z', 'n', 'd'), "Zande"},
    {MAKECODE('\0', 'z', 'a', 'p'), "Zapotec"},
    {MAKECODE('\0', 'z', 'e', 'n'), "Zenaga"},
    {MAKECODE('\0', 'z', 'h', 'a'), "Zhuang"},
    {MAKECODE('\0', 'z', 'u', 'l'), "Zulu"},
    {MAKECODE('\0', 'z', 'u', 'n'), "Zuni"},
}};
// clang-format on

// clang-format off
const std::array<ISO639, 190> LanguageCodes = {{
    {"aa", "aar", NULL, NULL},
    {"ab", "abk", NULL, NULL},
    {"af", "afr", NULL, NULL},
    {"ak", "aka", NULL, NULL},
    {"am", "amh", NULL, NULL},
    {"ar", "ara", NULL, NULL},
    {"an", "arg", NULL, NULL},
    {"as", "asm", NULL, NULL},
    {"av", "ava", NULL, NULL},
    {"ae", "ave", NULL, NULL},
    {"ay", "aym", NULL, NULL},
    {"az", "aze", NULL, NULL},
    {"ba", "bak", NULL, NULL},
    {"bm", "bam", NULL, NULL},
    {"be", "bel", NULL, NULL},
    {"bn", "ben", NULL, NULL},
    {"bh", "bih", NULL, NULL},
    {"bi", "bis", NULL, NULL},
    {"bo", "tib", NULL, "bod"},
    {"bs", "bos", NULL, NULL},
    {"br", "bre", NULL, NULL},
    {"bg", "bul", NULL, NULL},
    {"ca", "cat", NULL, NULL},
    {"cs", "cze", "ces", "ces"},
    {"ch", "cha", NULL, NULL},
    {"ce", "che", NULL, NULL},
    {"cu", "chu", NULL, NULL},
    {"cv", "chv", NULL, NULL},
    {"kw", "cor", NULL, NULL},
    {"co", "cos", NULL, NULL},
    {"cr", "cre", NULL, NULL},
    {"cy", "wel", NULL, "cym"},
    {"da", "dan", NULL, NULL},
    {"de", "ger", "deu", "deu"},
    {"dv", "div", NULL, NULL},
    {"dz", "dzo", NULL, NULL},
    {"el", "gre", "ell", "ell"},
    {"en", "eng", NULL, NULL},
    {"eo", "epo", NULL, NULL},
    {"et", "est", NULL, NULL},
    {"eu", "baq", NULL, "eus"},
    {"ee", "ewe", NULL, NULL},
    {"fo", "fao", NULL, NULL},
    {"fa", "per", NULL, "fas"},
    {"fj", "fij", NULL, NULL},
    {"fi", "fin", NULL, NULL},
    {"fr", "fre", "fra", "fra"},
    {"fy", "fry", NULL, NULL},
    {"ff", "ful", NULL, NULL},
    {"gd", "gla", NULL, NULL},
    {"ga", "gle", NULL, NULL},
    {"gl", "glg", NULL, NULL},
    {"gv", "glv", NULL, NULL},
    {"gn", "grn", NULL, NULL},
    {"gu", "guj", NULL, NULL},
    {"ht", "hat", NULL, NULL},
    {"ha", "hau", NULL, NULL},
    {"he", "heb", NULL, NULL},
    {"hz", "her", NULL, NULL},
    {"hi", "hin", NULL, NULL},
    {"ho", "hmo", NULL, NULL},
    {"hr", "hrv", NULL, NULL},
    {"hu", "hun", NULL, NULL},
    {"hy", "arm", NULL, "hye"},
    {"ig", "ibo", NULL, NULL},
    {"io", "ido", NULL, NULL},
    {"ii", "iii", NULL, NULL},
    {"iu", "iku", NULL, NULL},
    {"ie", "ile", NULL, NULL},
    {"ia", "ina", NULL, NULL},
    {"id", "ind", NULL, NULL},
    {"ik", "ipk", NULL, NULL},
    {"is", "ice", "isl", "isl"},
    {"it", "ita", NULL, NULL},
    {"jv", "jav", NULL, NULL},
    {"ja", "jpn", NULL, NULL},
    {"kl", "kal", NULL, NULL},
    {"kn", "kan", NULL, NULL},
    {"ks", "kas", NULL, NULL},
    {"ka", "geo", NULL, "kat"},
    {"kr", "kau", NULL, NULL},
    {"kk", "kaz", NULL, NULL},
    {"km", "khm", NULL, NULL},
    {"ki", "kik", NULL, NULL},
    {"rw", "kin", NULL, NULL},
    {"ky", "kir", NULL, NULL},
    {"kv", "kom", NULL, NULL},
    {"kg", "kon", NULL, NULL},
    {"ko", "kor", NULL, NULL},
    {"kj", "kua", NULL, NULL},
    {"ku", "kur", NULL, NULL},
    {"lo", "lao", NULL, NULL},
    {"la", "lat", NULL, NULL},
    {"lv", "lav", NULL, NULL},
    {"li", "lim", NULL, NULL},
    {"ln", "lin", NULL, NULL},
    {"lt", "lit", NULL, NULL},
    {"lb", "ltz", NULL, NULL},
    {"lu", "lub", NULL, NULL},
    {"lg", "lug", NULL, NULL},
    {"mk", "mac", NULL, "mdk"},
    {"mh", "mah", NULL, NULL},
    {"ml", "mal", NULL, NULL},
    {"mi", "mao", NULL, "mri"},
    {"mr", "mar", NULL, NULL},
    {"ms", "may", NULL, "msa"},
    {"mg", "mlg", NULL, NULL},
    {"mt", "mlt", NULL, NULL},
    {"mn", "mon", NULL, NULL},
    {"my", "bur", NULL, "mya"},
    {"na", "nau", NULL, NULL},
    {"nv", "nav", NULL, NULL},
    {"nr", "nbl", NULL, NULL},
    {"nd", "nde", NULL, NULL},
    {"ng", "ndo", NULL, NULL},
    {"ne", "nep", NULL, NULL},
    {"nl", "dut", "nld", "nld"},
    {"nn", "nno", NULL, NULL},
    {"nb", "nob", NULL, NULL},
    {"no", "nor", NULL, NULL},
    {"ny", "nya", NULL, NULL},
    {"oc", "oci", NULL, NULL},
    {"oj", "oji", NULL, NULL},
    {"or", "ori", NULL, NULL},
    {"om", "orm", NULL, NULL},
    {"os", "oss", NULL, NULL},
    {"pa", "pan", NULL, NULL},
    // pb / pob = unofficial language code for Brazilian Portuguese
    {"pb", "pob", NULL, NULL},
    {"pi", "pli", NULL, NULL},
    {"pl", "pol", "plk", NULL},
    {"pt", "por", "ptg", NULL},
    {"ps", "pus", NULL, NULL},
    {"qu", "que", NULL, NULL},
    {"rm", "roh", NULL, NULL},
    {"ro", "rum", "ron", "ron"},
    {"rn", "run", NULL, NULL},
    {"ru", "rus", NULL, NULL},
    {"sh", "scr", NULL, NULL},
    {"sg", "sag", NULL, NULL},
    {"sa", "san", NULL, NULL},
    {"si", "sin", NULL, NULL},
    {"sk", "slo", "sky", "slk"},
    {"sl", "slv", NULL, NULL},
    {"se", "sme", NULL, NULL},
    {"sm", "smo", NULL, NULL},
    {"sn", "sna", NULL, NULL},
    {"sd", "snd", NULL, NULL},
    {"so", "som", NULL, NULL},
    {"st", "sot", NULL, NULL},
    {"es", "spa", "esp", NULL},
    {"sq", "alb", NULL, "sqi"},
    {"sc", "srd", NULL, NULL},
    {"sr", "srp", NULL, NULL},
    {"ss", "ssw", NULL, NULL},
    {"su", "sun", NULL, NULL},
    {"sw", "swa", NULL, NULL},
    {"sv", "swe", "sve", NULL},
    {"ty", "tah", NULL, NULL},
    {"ta", "tam", NULL, NULL},
    {"tt", "tat", NULL, NULL},
    {"te", "tel", NULL, NULL},
    {"tg", "tgk", NULL, NULL},
    {"tl", "tgl", NULL, NULL},
    {"th", "tha", NULL, NULL},
    {"ti", "tir", NULL, NULL},
    {"to", "ton", NULL, NULL},
    {"tn", "tsn", NULL, NULL},
    {"ts", "tso", NULL, NULL},
    {"tk", "tuk", NULL, NULL},
    {"tr", "tur", "trk", NULL},
    {"tw", "twi", NULL, NULL},
    {"ug", "uig", NULL, NULL},
    {"uk", "ukr", NULL, NULL},
    {"ur", "urd", NULL, NULL},
    {"uz", "uzb", NULL, NULL},
    {"ve", "ven", NULL, NULL},
    {"vi", "vie", NULL, NULL},
    {"vo", "vol", NULL, NULL},
    {"wa", "wln", NULL, NULL},
    {"wo", "wol", NULL, NULL},
    {"xh", "xho", NULL, NULL},
    {"yi", "yid", NULL, NULL},
    {"yo", "yor", NULL, NULL},
    {"za", "zha", NULL, NULL},
    {"zh", "chi", "zho", "zho"},
    {"zu", "zul", NULL, NULL},
    {"zv", "und", NULL, NULL}, // Kodi intern mapping for missing "Undetermined" iso639-1 code
    {"zx", "zxx", NULL,
     NULL}, // Kodi intern mapping for missing "No linguistic content" iso639-1 code
    {"zy", "mis", NULL,
     NULL}, // Kodi intern mapping for missing "Miscellaneous languages" iso639-1 code
    {"zz", "mul", NULL, NULL} // Kodi intern mapping for missing "Multiple languages" iso639-1 code
}};
// clang-format on

// Based on ISO 3166
// clang-format off
const std::array<ISO3166_1, 245> RegionCodes = {{
    {"af", "afg"},
    {"ax", "ala"},
    {"al", "alb"},
    {"dz", "dza"},
    {"as", "asm"},
    {"ad", "and"},
    {"ao", "ago"},
    {"ai", "aia"},
    {"aq", "ata"},
    {"ag", "atg"},
    {"ar", "arg"},
    {"am", "arm"},
    {"aw", "abw"},
    {"au", "aus"},
    {"at", "aut"},
    {"az", "aze"},
    {"bs", "bhs"},
    {"bh", "bhr"},
    {"bd", "bgd"},
    {"bb", "brb"},
    {"by", "blr"},
    {"be", "bel"},
    {"bz", "blz"},
    {"bj", "ben"},
    {"bm", "bmu"},
    {"bt", "btn"},
    {"bo", "bol"},
    {"ba", "bih"},
    {"bw", "bwa"},
    {"bv", "bvt"},
    {"br", "bra"},
    {"io", "iot"},
    {"bn", "brn"},
    {"bg", "bgr"},
    {"bf", "bfa"},
    {"bi", "bdi"},
    {"kh", "khm"},
    {"cm", "cmr"},
    {"ca", "can"},
    {"cv", "cpv"},
    {"ky", "cym"},
    {"cf", "caf"},
    {"td", "tcd"},
    {"cl", "chl"},
    {"cn", "chn"},
    {"cx", "cxr"},
    {"co", "col"},
    {"km", "com"},
    {"cg", "cog"},
    {"cd", "cod"},
    {"ck", "cok"},
    {"cr", "cri"},
    {"ci", "civ"},
    {"hr", "hrv"},
    {"cu", "cub"},
    {"cy", "cyp"},
    {"cz", "cze"},
    {"dk", "dnk"},
    {"dj", "dji"},
    {"dm", "dma"},
    {"do", "dom"},
    {"ec", "ecu"},
    {"eg", "egy"},
    {"sv", "slv"},
    {"gq", "gnq"},
    {"er", "eri"},
    {"ee", "est"},
    {"et", "eth"},
    {"fk", "flk"},
    {"fo", "fro"},
    {"fj", "fji"},
    {"fi", "fin"},
    {"fr", "fra"},
    {"gf", "guf"},
    {"pf", "pyf"},
    {"tf", "atf"},
    {"ga", "gab"},
    {"gm", "gmb"},
    {"ge", "geo"},
    {"de", "deu"},
    {"gh", "gha"},
    {"gi", "gib"},
    {"gr", "grc"},
    {"gl", "grl"},
    {"gd", "grd"},
    {"gp", "glp"},
    {"gu", "gum"},
    {"gt", "gtm"},
    {"gg", "ggy"},
    {"gn", "gin"},
    {"gw", "gnb"},
    {"gy", "guy"},
    {"ht", "hti"},
    {"hm", "hmd"},
    {"va", "vat"},
    {"hn", "hnd"},
    {"hk", "hkg"},
    {"hu", "hun"},
    {"is", "isl"},
    {"in", "ind"},
    {"id", "idn"},
    {"ir", "irn"},
    {"iq", "irq"},
    {"ie", "irl"},
    {"im", "imn"},
    {"il", "isr"},
    {"it", "ita"},
    {"jm", "jam"},
    {"jp", "jpn"},
    {"je", "jey"},
    {"jo", "jor"},
    {"kz", "kaz"},
    {"ke", "ken"},
    {"ki", "kir"},
    {"kp", "prk"},
    {"kr", "kor"},
    {"kw", "kwt"},
    {"kg", "kgz"},
    {"la", "lao"},
    {"lv", "lva"},
    {"lb", "lbn"},
    {"ls", "lso"},
    {"lr", "lbr"},
    {"ly", "lby"},
    {"li", "lie"},
    {"lt", "ltu"},
    {"lu", "lux"},
    {"mo", "mac"},
    {"mk", "mkd"},
    {"mg", "mdg"},
    {"mw", "mwi"},
    {"my", "mys"},
    {"mv", "mdv"},
    {"ml", "mli"},
    {"mt", "mlt"},
    {"mh", "mhl"},
    {"mq", "mtq"},
    {"mr", "mrt"},
    {"mu", "mus"},
    {"yt", "myt"},
    {"mx", "mex"},
    {"fm", "fsm"},
    {"md", "mda"},
    {"mc", "mco"},
    {"mn", "mng"},
    {"me", "mne"},
    {"ms", "msr"},
    {"ma", "mar"},
    {"mz", "moz"},
    {"mm", "mmr"},
    {"na", "nam"},
    {"nr", "nru"},
    {"np", "npl"},
    {"nl", "nld"},
    {"an", "ant"},
    {"nc", "ncl"},
    {"nz", "nzl"},
    {"ni", "nic"},
    {"ne", "ner"},
    {"ng", "nga"},
    {"nu", "niu"},
    {"nf", "nfk"},
    {"mp", "mnp"},
    {"no", "nor"},
    {"om", "omn"},
    {"pk", "pak"},
    {"pw", "plw"},
    {"ps", "pse"},
    {"pa", "pan"},
    {"pg", "png"},
    {"py", "pry"},
    {"pe", "per"},
    {"ph", "phl"},
    {"pn", "pcn"},
    {"pl", "pol"},
    {"pt", "prt"},
    {"pr", "pri"},
    {"qa", "qat"},
    {"re", "reu"},
    {"ro", "rou"},
    {"ru", "rus"},
    {"rw", "rwa"},
    {"bl", "blm"},
    {"sh", "shn"},
    {"kn", "kna"},
    {"lc", "lca"},
    {"mf", "maf"},
    {"pm", "spm"},
    {"vc", "vct"},
    {"ws", "wsm"},
    {"sm", "smr"},
    {"st", "stp"},
    {"sa", "sau"},
    {"sn", "sen"},
    {"rs", "srb"},
    {"sc", "syc"},
    {"sl", "sle"},
    {"sg", "sgp"},
    {"sk", "svk"},
    {"si", "svn"},
    {"sb", "slb"},
    {"so", "som"},
    {"za", "zaf"},
    {"gs", "sgs"},
    {"es", "esp"},
    {"lk", "lka"},
    {"sd", "sdn"},
    {"sr", "sur"},
    {"sj", "sjm"},
    {"sz", "swz"},
    {"se", "swe"},
    {"ch", "che"},
    {"sy", "syr"},
    {"tw", "twn"},
    {"tj", "tjk"},
    {"tz", "tza"},
    {"th", "tha"},
    {"tl", "tls"},
    {"tg", "tgo"},
    {"tk", "tkl"},
    {"to", "ton"},
    {"tt", "tto"},
    {"tn", "tun"},
    {"tr", "tur"},
    {"tm", "tkm"},
    {"tc", "tca"},
    {"tv", "tuv"},
    {"ug", "uga"},
    {"ua", "ukr"},
    {"ae", "are"},
    {"gb", "gbr"},
    {"us", "usa"},
    {"um", "umi"},
    {"uy", "ury"},
    {"uz", "uzb"},
    {"vu", "vut"},
    {"ve", "ven"},
    {"vn", "vnm"},
    {"vg", "vgb"},
    {"vi", "vir"},
    {"wf", "wlf"},
    {"eh", "esh"},
    {"ye", "yem"},
    {"zm", "zmb"},
    {"zw", "zwe"}
}};
// clang-format on
