# vim:et sts=4 sw=4 # # ibus-typing-booster - A completion input method for IBus # # Copyright (c) 2013-2026 Mike FABIAN # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see ''' Core utility functions used in ibus-typing-booster. This module must not import Gtk or Gdk or require a display. ''' from typing import Any from typing import Tuple from typing import List from typing import Dict from typing import DefaultDict from typing import Set from typing import Optional from typing import Union from typing import Iterable from typing import Pattern # pylint: disable=wrong-import-position import sys if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal from enum import Flag import os import functools import collections import unicodedata import locale import logging import shutil import subprocess import glob import gettext import platform import ctypes import ctypes.util import xml.etree.ElementTree from dataclasses import dataclass from collections import defaultdict from gi import require_version require_version('IBus', '1.0') from gi.repository import IBus require_version('GLib', '2.0') from gi.repository import GLib # type: ignore # pylint: enable=wrong-import-position import itb_version try: # Enable new improved regex engine instead of backwards compatible # v0. regex.match('ß', 'SS', regex.IGNORECASE) matches only with # the improved version! See also: https://pypi.org/project/regex/ import regex # type: ignore regex.DEFAULT_VERSION = regex.VERSION1 re = regex USING_REGEX = True except ImportError: # Use standard “re” module as a fallback: import re USING_REGEX = False try: import distro IMPORT_DISTRO_SUCCESSFUL = True except (ImportError,): IMPORT_DISTRO_SUCCESSFUL = False # Importing xdg.BaseDirectory is not needed at the moment, see # the implemention of xdg_save_data_path() below does not use it # at the moment. # #IMPORT_XDG_BASEDIRECTORY_SUCCESSFUL = False #try: # import xdg.BaseDirectory # type: ignore # IMPORT_XDG_BASEDIRECTORY_SUCCESSFUL = True #except (ImportError,): # IMPORT_XDG_BASEDIRECTORY_SUCCESSFUL = False try: import pyaudio # type: ignore IMPORT_PYAUDIO_SUCCESSFUL = True except (ImportError,): IMPORT_PYAUDIO_SUCCESSFUL = False try: import queue IMPORT_QUEUE_SUCCESSFUL = True except (ImportError,): IMPORT_QUEUE_SUCCESSFUL = False try: import langtable # type: ignore IMPORT_LANGTABLE_SUCCESSFUL = True except (ImportError,): IMPORT_LANGTABLE_SUCCESSFUL = False try: import pycountry # type: ignore IMPORT_PYCOUNTRY_SUCCESSFUL = True except (ImportError,): IMPORT_PYCOUNTRY_SUCCESSFUL = False LOGGER = logging.getLogger('ibus-typing-booster') DOMAINNAME = 'ibus-typing-booster' def _(text: str) -> str: '''Gettext translation function.''' return gettext.dgettext(DOMAINNAME, text) def N_(text: str) -> str: # pylint: disable=invalid-name '''Mark string for translation without actually translating. Used by gettext tools to extract strings that need translation. ''' return text M17N_ENGINE_NAME_PATTERN = re.compile( r'^tb:(?P[a-z]{1,3}):(?P[^\s:]+)$') AVAILABLE_IBUS_KEYMAPS = ('in', 'jp', 'kr', 'us') ANTHY_HENKAN_WIDE = [ 'Left', 'Left', 'Left', 'Left', 'Left', 'S-Right', 'S-Right', 'S-Right', 'S-Right', 'S-Right', ] # When matching keybindings, only the bits in the following mask are # considered for key.state: KEYBINDING_STATE_MASK = ( IBus.ModifierType.MODIFIER_MASK # pylint: disable=no-member & ~IBus.ModifierType.LOCK_MASK # Caps Lock & ~IBus.ModifierType.MOD2_MASK # Num Lock & ~IBus.ModifierType.MOD3_MASK # Scroll Lock ) # The number of current dictionaries needs to be limited to some fixed # maximum number because of the property menu to select the highest # priority dictionary. Unfortunately the number of sub-properties for # such a menu cannot be changed, as a workaround a fixed number can be # used and unused entries can be hidden. MAXIMUM_NUMBER_OF_DICTIONARIES = 10 # The number of current imes needs to be limited to some fixed # maximum number because of the property menu to select the preëdit # ime. Unfortunately the number of sub-properties for such a menu # cannot be changed, as a workaround a fixed number can be used # and unused entries can be hidden. MAXIMUM_NUMBER_OF_INPUT_METHODS = 10 NORMALIZATION_FORM_INTERNAL: Literal['NFC', 'NFD', 'NFKC', 'NFKD'] = 'NFD' # maximum possible value for the Gsettings.get_int32() INT32_MAX = 2**31-1 # maximum possible value for the Gsettings.get_uint32() UINT32_MAX = 2**32-1 # maximum possible value for the INTEGER datatype in SQLite3 # and for Gsettings.get_int64() INT64_MAX = 2**63-1 # maximum possible value for the Gsettings.get_uint64() UINT64_MAX = 2**64-1 # user frequency used for a user defined shortcut SHORTCUT_USER_FREQ = 1000000 # If a punctuation character from the following list is comitted # (possibly followed by whitespace) remove trailing white space before # the committed string. For example if commit_phrase is “!”, and the # context before is “word ”, make the result “word!”. And if the # commit_phrase is “! ” and the context before is “word ” make the # result “word! ”. FIX_WHITESPACE_CHARACTERS = { 'fr_FR': {'.,)]}': '', '?!;:%#«»€$¢£¥฿': ' ', # U+202F NARROW NO-BREAK SPACE }, 'fr_CA': {'.,;?!)]}': '', ':': ' ', # U+202F NARROW NO-BREAK SPACE }, # all other languages: '*': {'.,;:?!)]}': ''}, } FIX_WHITESPACE_CHARACTERS['fr_MC'] = FIX_WHITESPACE_CHARACTERS['fr_FR'] FIX_WHITESPACE_CHARACTERS['fr_BE'] = FIX_WHITESPACE_CHARACTERS['fr_FR'] FIX_WHITESPACE_CHARACTERS['fr_LU'] = FIX_WHITESPACE_CHARACTERS['fr_FR'] FIX_WHITESPACE_CHARACTERS['fr_CH'] = FIX_WHITESPACE_CHARACTERS['fr_FR'] CATEGORIES_TO_STRIP_FROM_TOKENS = ( 'Po', # Punctuation Other 'Pi', # Punctuation Initial quote 'Pf', # Punctuation Final quote 'Ps', # Punctuation Open 'Pe', # Punctuation Close 'Pc', # Punctuation Connector 'Pd', # Punctuation Dash ) # List of languages where accent insensitive matching makes sense: ACCENT_LANGUAGES = { 'af': '', 'ast': '', 'az': '', 'be': '', 'bg': '', 'br': '', 'bs': '', 'ca': '', 'cs': '', 'csb': '', 'cv': '', 'cy': '', 'da': 'æÆøØåÅ', 'de': '', 'dsb': '', 'el': '', 'en': '', 'es': '', 'eu': '', 'fi': 'åÅäÄöÖ', 'fo': '', 'fr': '', 'fur': '', 'fy': '', 'ga': '', 'gd': '', 'gl': '', 'grc': '', 'gv': '', 'haw': '', 'hr': '', 'hsb': '', 'ht': '', 'hu': '', 'ia': '', 'is': '', 'it': '', 'kk': '', 'ku': '', 'ky': '', 'lb': '', 'ln': '', 'lv': '', 'mg': '', 'mi': '', 'mk': '', 'mn': '', 'mos': '', 'mt': '', 'nb': 'æÆøØåÅ', 'nds': '', 'nl': '', 'nn': 'æÆøØåÅ', 'nr': '', 'nso': '', 'ny': '', 'oc': '', 'pl': '', 'plt': '', 'pt': '', 'qu': '', 'quh': '', 'ru': '', 'sc': '', 'se': '', 'sh': '', 'shs': '', 'sk': '', 'sl': '', 'smj': '', 'sq': '', 'sr': '', 'ss': '', 'st': '', 'sv': 'åÅäÄöÖ', 'tet': '', 'tk': '', 'tn': '', 'ts': '', 'uk': '', 'uz': '', 've': '', 'vi': '', 'wa': '', 'xh': '', } M17N_IME_SYMBOLS = [ # These are to be displayed in the Gnome panel when the input method # is selected. In the Panels of non-Gnome desktops, the icons are displayed # instead. # # However, even on non-Gnome desktops, if a direct input mode is # enabled these symbols are displayed instead of the icons if # switching between “normal” input mode and direct input mode. To # indicate direct input mode, a character is added to the # symbol. As symbols longer than 2 Unicode characters to not work # as indicators for input methods in the Gnome panel, make sure # all of these symbols are consist of only one Unicode character # so that when the character to indicate direct input mode is # added, it still does not exceed 2 characters (Invisible # characters count as well to this limit, do not add extra # invisible characters like combining characters or variation # selectors!) # # See also: # https://bugzilla.redhat.com/show_bug.cgi?id=1678386 # https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/1026 # (Input method indicator should not limit to 2 characters, # at least 3 characters should be allowed) # # These symbols are chosen almost like those in ibus-m17n, except where # it exceeds the one character limit. (r'tb:zh:tonepy.*', '调'), (r'tb:zh:quick', '簡'), (r'tb:zh:py.*', '拼'), (r'tb:zh:pinyin.*', 'P'), # ibus-m17n uses 'PY' (r'tb:zh:cangjie.*', '倉'), (r'tb:zh:bopomofo.*', 'ㄅ'), (r'tb:yi:.+', 'ש'), (r'tb:vi:nom.*', '喃'), (r'tb:vi:han.*', '越'), (r'tb:vi:.+', 'ắ'), (r'tb:ur:.+', 'خ'), (r'tb:th:.+', 'ท'), (r'tb:tai:.+', '꫞'), (r'tb:ru:.+', 'Я'), (r'tb:ps:.+', 'خ'), (r'tb:oj:.+', 'ᐏ'), (r'tb:nsk:.+', 'ᐃ'), (r'tb:lo:.+', 'ກ'), (r'tb:km:.+', 'ក'), (r'tb:kk:kbd', 'Қ'), # ibus-m17n uses ҚА (r'tb:kk:arabic', 'ق'), # ibus-m17n uses قا (r'tb:ja:.+', 'あ'), (r'tb:iu:.+', 'ᐃ'), (r'tb:ii:.+', 'ꆈ'), (r'tb:fa:.+', 'ف'), (r'tb:cr:.+', 'ᐃ'), (r'tb:bo:.+', 'ཀ'), (r'tb:am:.+', 'አ'), (r'tb:te:.+', 'క'), (r'tb:ta:.+', 'க'), (r'tb:si:.+', 'සි'), (r'tb:sd:.+', 'क'), (r'tb:sat:.+deva', 'क'), (r'tb:sat:.+olck', 'ᱚ'), (r'tb:sa:.+', 'क'), (r'tb:pa:.+', 'ਕ'), (r'tb:or:.+', 'କ'), (r'tb:ne:.+', 'क'), (r'tb:mr:.+', 'क'), (r'tb:mni:.+mtei', 'ꯀ'), (r'tb:mni:.+beng', 'ক'), (r'tb:ml:.+', 'ക'), (r'tb:mai:.+', 'क'), (r'tb:ks:kbd', 'خ'), (r'tb:ks:.+', 'क'), (r'tb:kok:.+', 'क'), (r'tb:kn:.+', 'ಕ'), (r'tb:hi:.+', 'क'), (r'tb:gu:.+', 'ક'), (r'tb:doi:.+', 'क'), (r'tb:brx:.+', 'क'), (r'tb:bn:.+', 'ক'), (r'tb:as:.+', 'ক'), # Fallback for all the rest is ⌨ U+2328 KEYBOARD. A character to # indicate direct input mode can be added to this: (r'.*', '\u2328'), ] LOCALE_DEFAULTS = { # Contains the default input methods and dictionaries which should # be used if ibus-typing-booster is started for the very first # time. In that case, no previous settings can be found from dconf # and a reasonable default should be used depending on the current # locale. 'af': {'inputmethods': ['NoIME'], 'dictionaries': ['af_ZA']}, 'af_NA': {'inputmethods': ['NoIME'], 'dictionaries': ['af_NA']}, 'ak': {'inputmethods': ['NoIME'], 'dictionaries': ['ak_GH']}, 'am': {'inputmethods': ['am-sera'], 'dictionaries': ['am_ET']}, 'ar': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_SA']}, 'ar_AE': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_AE']}, 'ar_BH': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_BH']}, 'ar_DJ': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_DJ']}, 'ar_DZ': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_DZ']}, 'ar_EG': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_EG']}, 'ar_ER': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_ER']}, 'ar_IL': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_IL']}, 'ar_IN': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_IN']}, 'ar_IQ': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_IQ']}, 'ar_JO': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_JO']}, 'ar_KM': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_KM']}, 'ar_KW': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_KW']}, 'ar_LB': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_LB']}, 'ar_LY': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_LY']}, 'ar_MA': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_MA']}, 'ar_MR': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_MR']}, 'ar_OM': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_OM']}, 'ar_PS': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_PS']}, 'ar_QA': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_QA']}, 'ar_SA': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_SA']}, 'ar_SD': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_SD']}, 'ar_SO': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_SO']}, 'ar_SY': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_SY']}, 'ar_TD': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_TD']}, 'ar_TN': {'inputmethods':['ar-kbd', 'NoIME'], 'dictionaries': ['ar_TN']}, 'ar_YE': {'inputmethods': ['ar-kbd', 'NoIME'], 'dictionaries': ['ar_YE']}, # libgnome-desktop/default-input-sources.h has "m17n:as:phonetic" # as the default. Parag Nemade says the as translator for Fedora a long # time back used as-phonetic. We have no later data from the # community. 'as': {'inputmethods': ['as-inscript2', 'NoIME'], 'dictionaries': ['as_IN', 'en_GB']}, 'ast': {'inputmethods': ['NoIME'], 'dictionaries': ['ast_ES']}, 'az': {'inputmethods': ['NoIME'], 'dictionaries': ['az_AZ']}, 'be': {'inputmethods': ['NoIME'], 'dictionaries': ['be_BY']}, 'ber': {'inputmethods': ['NoIME'], 'dictionaries': ['ber_MA']}, 'bg': {'inputmethods': ['NoIME'], 'dictionaries': ['bg_BG']}, 'bn': {'inputmethods': ['bn-inscript2', 'NoIME'], 'dictionaries': ['bn_IN', 'en_GB']}, 'br': {'inputmethods': ['NoIME'], 'dictionaries': ['br_FR']}, 'brx': {'inputmethods': ['brx-inscript2-deva', 'NoIME'], 'dictionaries': ['en_GB']}, 'bs': {'inputmethods': ['NoIME'], 'dictionaries': ['bs_BA']}, 'ca': {'inputmethods': ['NoIME'], 'dictionaries': ['ca_ES']}, 'ca_AD': {'inputmethods': ['NoIME'], 'dictionaries': ['ca_AD']}, 'ca_FR': {'inputmethods': ['NoIME'], 'dictionaries': ['ca_FR']}, 'ca_IT': {'inputmethods': ['NoIME'], 'dictionaries': ['ca_IT']}, 'cop': {'inputmethods': ['NoIME'], 'dictionaries': ['cop_EG']}, 'cs': {'inputmethods': ['NoIME'], 'dictionaries': ['cs_CZ']}, 'csb': {'inputmethods': ['NoIME'], 'dictionaries': ['csb_PL']}, 'cv': {'inputmethods': ['NoIME'], 'dictionaries': ['cv_RU']}, 'cy': {'inputmethods': ['NoIME'], 'dictionaries': ['cy_GB']}, 'da': {'inputmethods': ['NoIME'], 'dictionaries': ['da_DK']}, 'de': {'inputmethods':['NoIME'], 'dictionaries': ['de_DE']}, 'de_AT': {'inputmethods': ['NoIME'], 'dictionaries': ['de_AT']}, 'de_BE': {'inputmethods': ['NoIME'], 'dictionaries': ['de_BE']}, 'de_CH': {'inputmethods': ['NoIME'], 'dictionaries': ['de_CH']}, 'de_LI': {'inputmethods': ['NoIME'], 'dictionaries': ['de_LI']}, 'de_LU': {'inputmethods': ['NoIME'], 'dictionaries': ['de_LU']}, 'doi': {'inputmethods': ['doi-inscript2-deva', 'NoIME'], 'dictionaries': ['en_GB']}, 'dsb': {'inputmethods': ['NoIME'], 'dictionaries': ['dsb_DE']}, 'el': {'inputmethods': ['NoIME'], 'dictionaries': ['el_GR']}, 'en': {'inputmethods': ['NoIME'], 'dictionaries': ['en_US']}, 'en_US': {'inputmethods': ['NoIME'], 'dictionaries': ['en_US']}, 'el_CY': {'inputmethods': ['NoIME'], 'dictionaries': ['el_CY']}, 'en_AG': {'inputmethods': ['NoIME'], 'dictionaries': ['en_AG']}, 'en_AU': {'inputmethods': ['NoIME'], 'dictionaries': ['en_AU']}, 'en_BS': {'inputmethods': ['NoIME'], 'dictionaries': ['en_BS']}, 'en_BW': {'inputmethods': ['NoIME'], 'dictionaries': ['en_BW']}, 'en_BZ': {'inputmethods': ['NoIME'], 'dictionaries': ['en_BZ']}, 'en_CA': {'inputmethods': ['NoIME'], 'dictionaries': ['en_CA']}, 'en_DK': {'inputmethods': ['NoIME'], 'dictionaries': ['en_DK']}, 'en_GB': {'inputmethods': ['NoIME'], 'dictionaries': ['en_GB']}, 'en_GH': {'inputmethods': ['NoIME'], 'dictionaries': ['en_GH']}, 'en_HK': {'inputmethods': ['NoIME'], 'dictionaries': ['en_HK']}, 'en_IE': {'inputmethods': ['NoIME'], 'dictionaries': ['en_IE']}, 'en_IN': {'inputmethods': ['NoIME'], 'dictionaries': ['en_IN']}, 'en_JM': {'inputmethods': ['NoIME'], 'dictionaries': ['en_JM']}, 'en_MW': {'inputmethods': ['NoIME'], 'dictionaries': ['en_MW']}, 'en_NA': {'inputmethods': ['NoIME'], 'dictionaries': ['en_NA']}, 'en_NG': {'inputmethods': ['NoIME'], 'dictionaries': ['en_NG']}, 'en_NZ': {'inputmethods': ['NoIME'], 'dictionaries': ['en_NZ']}, 'en_PH': {'inputmethods': ['NoIME'], 'dictionaries': ['en_PH']}, 'en_SG': {'inputmethods': ['NoIME'], 'dictionaries': ['en_SG']}, 'en_TT': {'inputmethods': ['NoIME'], 'dictionaries': ['en_TT']}, 'en_ZA': {'inputmethods': ['NoIME'], 'dictionaries': ['en_ZA']}, 'en_ZM': {'inputmethods': ['NoIME'], 'dictionaries': ['en_ZM']}, 'en_ZW': {'inputmethods': ['NoIME'], 'dictionaries': ['en_ZW']}, 'eo': {'inputmethods': ['NoIME'], 'dictionaries': ['eo']}, 'es_': {'inputmethods': ['NoIME'], 'dictionaries': ['es_ES']}, 'es_AR': {'inputmethods': ['NoIME'], 'dictionaries': ['es_AR']}, 'es_BO': {'inputmethods': ['NoIME'], 'dictionaries': ['es_BO']}, 'es_CL': {'inputmethods': ['NoIME'], 'dictionaries': ['es_CL']}, 'es_CO': {'inputmethods': ['NoIME'], 'dictionaries': ['es_CO']}, 'es_CR': {'inputmethods': ['NoIME'], 'dictionaries': ['es_CR']}, 'es_CU': {'inputmethods': ['NoIME'], 'dictionaries': ['es_CU']}, 'es_DO': {'inputmethods': ['NoIME'], 'dictionaries': ['es_DO']}, 'es_EC': {'inputmethods': ['NoIME'], 'dictionaries': ['es_EC']}, 'es_ES': {'inputmethods': ['NoIME'], 'dictionaries': ['es_ES']}, 'es_GT': {'inputmethods': ['NoIME'], 'dictionaries': ['es_GT']}, 'es_HN': {'inputmethods': ['NoIME'], 'dictionaries': ['es_HN']}, 'es_MX': {'inputmethods': ['NoIME'], 'dictionaries': ['es_MX']}, 'es_NI': {'inputmethods': ['NoIME'], 'dictionaries': ['es_NI']}, 'es_PA': {'inputmethods': ['NoIME'], 'dictionaries': ['es_PA']}, 'es_PE': {'inputmethods': ['NoIME'], 'dictionaries': ['es_PE']}, 'es_PR': {'inputmethods': ['NoIME'], 'dictionaries': ['es_PR']}, 'es_PY': {'inputmethods': ['NoIME'], 'dictionaries': ['es_PY']}, 'es_SV': {'inputmethods': ['NoIME'], 'dictionaries': ['es_SV']}, 'es_US': {'inputmethods': ['NoIME'], 'dictionaries': ['es_US']}, 'es_UY': {'inputmethods': ['NoIME'], 'dictionaries': ['es_UY']}, 'es_VE': {'inputmethods': ['NoIME'], 'dictionaries': ['es_VE']}, 'et': {'inputmethods': ['NoIME'], 'dictionaries': ['et_EE']}, 'eu': {'inputmethods': ['NoIME'], 'dictionaries': ['eu_ES']}, 'fa': {'inputmethods': ['NoIME'], 'dictionaries': ['fa_IR']}, 'fil': {'inputmethods': ['NoIME'], 'dictionaries': ['fil_PH']}, 'fj': {'inputmethods': ['NoIME'], 'dictionaries': ['fj']}, 'fo': {'inputmethods': ['NoIME'], 'dictionaries': ['fo_FO']}, 'fr': {'inputmethods': ['NoIME'], 'dictionaries': ['fr_FR']}, 'fr_BE': {'inputmethods': ['NoIME'], 'dictionaries': ['fr_BE']}, 'fr_CA': {'inputmethods': ['NoIME'], 'dictionaries': ['fr_CA']}, 'fr_CH': {'inputmethods': ['NoIME'], 'dictionaries': ['fr_CH']}, 'fr_LU': {'inputmethods': ['NoIME'], 'dictionaries': ['fr_LU']}, 'fr_MC': {'inputmethods': ['NoIME'], 'dictionaries': ['fr_MC']}, 'fur': {'inputmethods': ['NoIME'], 'dictionaries': ['fur_IT']}, 'fy': {'inputmethods': ['NoIME'], 'dictionaries': ['fy_NL']}, 'fy_DE': {'inputmethods': ['NoIME'], 'dictionaries': ['fy_DE']}, 'ga': {'inputmethods': ['NoIME'], 'dictionaries': ['ga_IE']}, 'gd': {'inputmethods': ['NoIME'], 'dictionaries': ['gd_GB']}, 'gl': {'inputmethods': ['NoIME'], 'dictionaries': ['gl_ES']}, 'grc': {'inputmethods': ['NoIME'], 'dictionaries': ['grc']}, 'gu': {'inputmethods': ['gu-inscript2', 'NoIME'], 'dictionaries': ['gu_IN', 'en_GB']}, 'gv': {'inputmethods': ['NoIME'], 'dictionaries': ['gv_GB']}, 'haw': {'inputmethods': ['NoIME'], 'dictionaries': ['haw']}, 'he': {'inputmethods': ['NoIME'], 'dictionaries': ['he_IL']}, 'hi': {'inputmethods': ['hi-inscript2', 'NoIME'], 'dictionaries': ['hi_IN', 'en_GB']}, 'hil': {'inputmethods': ['NoIME'], 'dictionaries': ['hil_PH']}, 'hr': {'inputmethods': ['NoIME'], 'dictionaries': ['hr_HR']}, 'hsb': {'inputmethods': ['NoIME'], 'dictionaries': ['hsb_DE']}, 'ht': {'inputmethods': ['NoIME'], 'dictionaries': ['ht_HT']}, 'hu': {'inputmethods': ['NoIME'], 'dictionaries': ['hu_HU']}, 'hy': {'inputmethods': ['NoIME'], 'dictionaries': ['hy_AM']}, 'ia': {'inputmethods': ['NoIME'], 'dictionaries': ['ia']}, 'id': {'inputmethods': ['NoIME'], 'dictionaries': ['id_ID']}, 'is': {'inputmethods': ['NoIME'], 'dictionaries': ['is_IS']}, 'it': {'inputmethods': ['NoIME'], 'dictionaries': ['it_IT']}, 'it_CH': {'inputmethods': ['NoIME'], 'dictionaries': ['it_CH']}, 'kk': {'inputmethods': ['NoIME'], 'dictionaries': ['kk_KZ']}, 'km': {'inputmethods': ['NoIME'], 'dictionaries': ['km_KH']}, # libgnome-desktop/default-input-sources.h has "m17n:kn:kgp" as # the default for kn_IN. According to Parag Nemade this probably came # from the translation community. 'kn': {'inputmethods': ['kn-inscript2', 'NoIME'], 'dictionaries': ['kn_IN', 'en_GB']}, 'ko': {'inputmethods': ['ko-han2', 'NoIME'], 'dictionaries': ['ko_KR', 'en_GB']}, 'kok' : {'inputmethods': ['kok-inscript2-deva', 'NoIME'], 'dictionaries': ['en_GB']}, 'ks': {'inputmethods': ['ks-kbd', 'NoIME'], 'dictionaries': ['en_GB']}, 'ks_Deva': {'inputmethods': ['ks-inscript2-deva', 'NoIME'], 'dictionaries': ['en_GB']}, 'ku': {'inputmethods': ['NoIME'], 'dictionaries': ['ku_TR']}, 'ku_SY': {'inputmethods': ['NoIME'], 'dictionaries': ['ku_SY']}, 'ky': {'inputmethods': ['NoIME'], 'dictionaries': ['ky_KG']}, 'la': {'inputmethods': ['NoIME'], 'dictionaries': ['la']}, 'lb': {'inputmethods': ['NoIME'], 'dictionaries': ['lb_LU']}, 'ln': {'inputmethods': ['NoIME'], 'dictionaries': ['ln_CD']}, 'lt': {'inputmethods': ['NoIME'], 'dictionaries': ['lt_LT']}, 'lv': {'inputmethods': ['NoIME'], 'dictionaries': ['lv_LV']}, 'mai': {'inputmethods': ['mai-inscript2', 'NoIME'], 'dictionaries': ['mai_IN', 'en_GB']}, 'mg': {'inputmethods': ['NoIME'], 'dictionaries': ['mg']}, 'mi': {'inputmethods': ['NoIME'], 'dictionaries': ['mi_NZ']}, 'mk': {'inputmethods': ['NoIME'], 'dictionaries': ['mk_MK']}, 'ml': {'inputmethods': ['ml-inscript2', 'NoIME'], 'dictionaries': ['ml_IN', 'en_GB']}, 'mn': {'inputmethods': ['NoIME'], 'dictionaries': ['mn_MN']}, 'mni': {'inputmethods': ['mni-inscript2-beng', 'NoIME'], 'dictionaries': ['en_GB']}, 'mni_Mtei': {'inputmethods': ['mni-inscript2-mtei', 'NoIME'], 'dictionaries': ['en_GB']}, 'mos': {'inputmethods': ['NoIME'], 'dictionaries': ['mos_BF']}, 'mr': {'inputmethods': ['mr-inscript2', 'NoIME'], 'dictionaries': ['mr_IN', 'en_GB']}, 'ms': {'inputmethods': ['NoIME'], 'dictionaries': ['ms_MY']}, 'ms_BN': {'inputmethods': ['NoIME'], 'dictionaries': ['ms_BN']}, 'mt': {'inputmethods': ['NoIME'], 'dictionaries': ['mt_MT']}, 'nb': {'inputmethods': ['NoIME'], 'dictionaries': ['nb_NO']}, 'no': {'inputmethods': ['NoIME'], 'dictionaries': ['nb_NO']}, 'nds': {'inputmethods': ['NoIME'], 'dictionaries': ['nds_DE']}, 'nds_NL': {'inputmethods': ['NoIME'], 'dictionaries': ['nds_NL']}, 'ne': {'inputmethods': ['ne-rom', 'NoIME'], 'dictionaries': ['ne_NP', 'en_GB']}, 'ne_IN': {'inputmethods': ['ne-inscript2-deva', 'NoIME'], 'dictionaries': ['ne_IN', 'en_GB']}, 'nl': {'inputmethods': ['NoIME'], 'dictionaries': ['nl_NL']}, 'nl_AW': {'inputmethods': ['NoIME'], 'dictionaries': ['nl_AW']}, 'nl_BE': {'inputmethods': ['NoIME'], 'dictionaries': ['nl_BE']}, 'nn': {'inputmethods': ['NoIME'], 'dictionaries': ['nn_NO']}, 'nr': {'inputmethods': ['NoIME'], 'dictionaries': ['nr_ZA']}, 'nso': {'inputmethods': ['NoIME'], 'dictionaries': ['nso_ZA']}, 'ny': {'inputmethods': ['NoIME'], 'dictionaries': ['ny_MW']}, 'oc': {'inputmethods': ['NoIME'], 'dictionaries': ['oc_FR']}, 'om': {'inputmethods': ['NoIME'], 'dictionaries': ['om_ET']}, 'om_KE': {'inputmethods': ['NoIME'], 'dictionaries': ['om_KE']}, 'or': {'inputmethods': ['or-inscript2', 'NoIME'], 'dictionaries': ['or_IN', 'en_GB']}, 'pa': {'inputmethods': ['pa-inscript2-guru', 'NoIME'], 'dictionaries': ['pa_IN', 'en_GB']}, 'pa_PK': {'inputmethods': ['NoIME'], 'dictionaries': ['en_GB']}, 'pl': {'inputmethods': ['NoIME'], 'dictionaries': ['pl_PL']}, 'plt': {'inputmethods': ['NoIME'], 'dictionaries': ['plt']}, 'pt': {'inputmethods': ['NoIME'], 'dictionaries': ['pt_PT']}, 'pt_AO': {'inputmethods': ['NoIME'], 'dictionaries': ['pt_AO']}, 'pt_BR': {'inputmethods': ['NoIME'], 'dictionaries': ['pt_BR']}, 'pt_PT': {'inputmethods': ['NoIME'], 'dictionaries': ['pt_PT']}, 'qu': {'inputmethods': ['NoIME'], 'dictionaries': ['qu_EC']}, 'quh': {'inputmethods': ['NoIME'], 'dictionaries': ['quh_BO']}, 'ro': {'inputmethods': ['NoIME'], 'dictionaries': ['ro_RO']}, 'ru': {'inputmethods': ['NoIME'], 'dictionaries': ['ru_RU']}, 'ru_UA': {'inputmethods': ['NoIME'], 'dictionaries': ['ru_UA']}, 'rw': {'inputmethods': ['NoIME'], 'dictionaries': ['rw_RW']}, 'sa': {'inputmethods': ['sa-inscript2', 'NoIME'], 'dictionaries': ['en_GB']}, 'sat': {'inputmethods': ['sat-inscript2-deva', 'NoIME'], 'dictionaries': ['en_GB']}, 'sc': {'inputmethods': ['NoIME'], 'dictionaries': ['sc_IT']}, 'sd': {'inputmethods': ['NoIME'], 'dictionaries': ['en_GB']}, 'sd_Arab': {'inputmethods': ['NoIME'], 'dictionaries': ['en_GB']}, 'sd_Deva': {'inputmethods': ['sd-inscript2-deva', 'NoIME'], 'dictionaries': ['en_GB']}, 'se': {'inputmethods': ['NoIME'], 'dictionaries': ['se_SE']}, 'se_FI': {'inputmethods': ['NoIME'], 'dictionaries': ['se_FI']}, 'se_NO': {'inputmethods': ['NoIME'], 'dictionaries': ['se_NO']}, 'sh': {'inputmethods': ['NoIME'], 'dictionaries': ['sh_RS']}, 'sh_ME': {'inputmethods': ['NoIME'], 'dictionaries': ['sh_ME']}, 'sh_YU': {'inputmethods': ['NoIME'], 'dictionaries': ['sh_YU']}, 'shs': {'inputmethods': ['NoIME'], 'dictionaries': ['shs_CA']}, 'si': {'inputmethods': ['si-wijesekara', 'NoIME'], 'dictionaries': ['si_LK', 'en_GB']}, 'sk': {'inputmethods': ['NoIME'], 'dictionaries': ['sk_SK']}, 'sl': {'inputmethods': ['NoIME'], 'dictionaries': ['sl_SI']}, 'smj': {'inputmethods': ['NoIME'], 'dictionaries': ['smj_NO']}, 'smj_SE': {'inputmethods': ['NoIME'], 'dictionaries': ['smj_SE']}, 'so': {'inputmethods': ['NoIME'], 'dictionaries': ['so_SO']}, 'so_DJ': {'inputmethods': ['NoIME'], 'dictionaries': ['so_DJ']}, 'so_ET': {'inputmethods': ['NoIME'], 'dictionaries': ['so_ET']}, 'so_KE': {'inputmethods': ['NoIME'], 'dictionaries': ['so_KE']}, 'sq': {'inputmethods': ['NoIME'], 'dictionaries': ['sq_AL']}, 'sr': {'inputmethods': ['NoIME'], 'dictionaries': ['sr_RS']}, 'sr_ME': {'inputmethods': ['NoIME'], 'dictionaries': ['sr_ME']}, 'sr_YU': {'inputmethods': ['NoIME'], 'dictionaries': ['sr_YU']}, 'ss': {'inputmethods': ['NoIME'], 'dictionaries': ['ss_ZA']}, 'st': {'inputmethods': ['NoIME'], 'dictionaries': ['st_ZA']}, 'sv': {'inputmethods': ['NoIME'], 'dictionaries': ['sv_SE']}, 'sv_FI': {'inputmethods': ['NoIME'], 'dictionaries': ['sv_FI']}, 'sw': {'inputmethods': ['NoIME'], 'dictionaries': ['sw_TZ']}, 'sw_KE': {'inputmethods': ['NoIME'], 'dictionaries': ['sw_KE']}, # libgnome-desktop/default-input-sources.h has "m17n:ta:tamil99" # as the default for ta_IN. According to Parag Nemade this probably came # from the translation community. 'ta': {'inputmethods': ['ta-inscript2', 'NoIME'], 'dictionaries': ['ta_IN', 'en_GB']}, 'te': {'inputmethods': ['te-inscript2', 'NoIME'], 'dictionaries': ['te_IN', 'en_GB']}, 'tet': {'inputmethods': ['NoIME'], 'dictionaries': ['tet_ID']}, 'tet_TL': {'inputmethods': ['NoIME'], 'dictionaries': ['tet_TL']}, 'th': {'inputmethods': ['NoIME'], 'dictionaries': ['th_TH']}, 'ti': {'inputmethods': ['NoIME'], 'dictionaries': ['ti_ER']}, 'ti_ET': {'inputmethods': ['NoIME'], 'dictionaries': ['ti_ET']}, 'tk': {'inputmethods': ['NoIME'], 'dictionaries': ['tk_TM']}, 'tl': {'inputmethods': ['NoIME'], 'dictionaries': ['tl_PH']}, 'tn': {'inputmethods': ['NoIME'], 'dictionaries': ['tn_ZA']}, 'tn_BW': {'inputmethods': ['NoIME'], 'dictionaries': ['tn_BW']}, 'tpi': {'inputmethods': ['NoIME'], 'dictionaries': ['tpi_PG']}, 'ts': {'inputmethods': ['NoIME'], 'dictionaries': ['ts_ZA']}, 'uk': {'inputmethods': ['NoIME'], 'dictionaries': ['uk_UA']}, 'ur': {'inputmethods': ['NoIME'], 'dictionaries': ['ur_PK']}, 'ur_IN': {'inputmethods': ['ur-phonetic', 'NoIME'], 'dictionaries': ['ur_IN', 'en_GB']}, 'uz': {'inputmethods': ['NoIME'], 'dictionaries': ['uz_UZ']}, 've': {'inputmethods': ['NoIME'], 'dictionaries': ['ve_ZA']}, 'vi': {'inputmethods': ['NoIME', 'vi-telex'], 'dictionaries': ['vi_VN']}, 'wa': {'inputmethods': ['NoIME'], 'dictionaries': ['wa_BE']}, 'xh': {'inputmethods': ['NoIME'], 'dictionaries': ['xh_ZA']}, 'yi': {'inputmethods': ['NoIME', 'yi-yivo'], 'dictionaries': ['yi_US', 'en_US']}, 'zu': {'inputmethods': ['NoIME'], 'dictionaries': ['zu_ZA', 'en_GB']}, } def get_default_input_methods(locale_string: str) -> List[str]: ''' Gets the default input methods for a locale :param locale_string: The name of the locale to get the default input methods for Examples: >>> get_default_input_methods('te_IN') ['te-inscript2', 'NoIME'] >>> get_default_input_methods('ks_IN.UTF-8@devanagari') ['ks-inscript2-deva', 'NoIME'] >>> get_default_input_methods('ks_IN.UTF-8') ['ks-kbd', 'NoIME'] >>> get_default_input_methods('mni_IN.UTF-8') ['mni-inscript2-beng', 'NoIME'] >>> get_default_input_methods('mni_Mtei') # No locale in glibc for this! ['mni-inscript2-mtei', 'NoIME'] >>> get_default_input_methods('xx_YY') ['NoIME'] ''' language = locale_normalize(locale_string) for lang in expand_languages([language]): if lang in LOCALE_DEFAULTS: return LOCALE_DEFAULTS[lang]['inputmethods'] return ['NoIME'] def get_default_dictionaries(locale_string: str) -> List[str]: ''' Gets the default dictionaries for a locale :param locale_string: The name of the locale to get the default dictionaries for Examples: >>> get_default_dictionaries('te_IN') ['te_IN', 'en_GB'] >>> get_default_dictionaries('xx_YY') ['en_US'] ''' language = locale_normalize(locale_string) for lang in expand_languages([language]): if lang in LOCALE_DEFAULTS: return LOCALE_DEFAULTS[lang]['dictionaries'] return ['en_US'] def input_methods_str_to_list(imes_str: str) -> List[str]: '''Converts a list of input methods from a comma separated string to a list of strings. :param imes_str: Input methods as a comma separated string Examples: If the input is not empty but contains nothing which could be an input method, the default input method ['NoIME'] is returned: >>> input_methods_str_to_list(',,') ['NoIME'] If the input is empty, the default list of input methods for the current locale is returned: >>> old_lc_all = os.environ.get('LC_ALL') >>> os.environ['LC_ALL'] = 'hi_IN.UTF-8' >>> input_methods_str_to_list('') ['hi-inscript2', 'NoIME'] >>> if old_lc_all: ... os.environ['LC_ALL'] = old_lc_all ... else: ... # unneeded return value assigned to variable ... _ = os.environ.pop('LC_ALL', None) If the input has more than the maximum number of allowed input methods, the list is reduced to the maximum (currently 10): >>> input_methods_str_to_list('0, 1,2,3 ,4,5,6,7,8,9,10,11,12') ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] If the input contains duplicates, the duplicates are removed: >>> input_methods_str_to_list('a ,a,b , c ,, a ,d,e,f, ') ['a', 'b', 'c', 'd', 'e', 'f'] Non-standard spellings of the special input method ['NoIME'] are converted to the standard spelling: >>> input_methods_str_to_list('nOiMe') ['NoIME'] ''' if imes_str == '': return get_default_input_methods(get_effective_lc_ctype()) imes: List[str] = [] for ime in [re.sub(re.escape('noime'), 'NoIME', x.strip(), flags=re.IGNORECASE) for x in imes_str.split(',') if x.strip()]: if ime not in imes: imes.append(ime) if len(imes) > MAXIMUM_NUMBER_OF_INPUT_METHODS: LOGGER.error( 'More than the allowed maximum of %s input methods.\n' 'Trying to use: %s\n' 'Really using: %s\n', MAXIMUM_NUMBER_OF_INPUT_METHODS, imes, imes[:MAXIMUM_NUMBER_OF_INPUT_METHODS]) imes = imes[:MAXIMUM_NUMBER_OF_INPUT_METHODS] if imes: return imes return ['NoIME'] def dictionaries_str_to_list(dictionaries_str: str) -> List[str]: # pylint: disable=line-too-long ''' Converts a list of dictionaries from a comma separated string to a list of strings. ;param dictionaries_str: Dictionaries as a comma separated string Examples: If the input is not empty but contains nothing which could be a dictionary, the default dictionary ['en_US'] is returned: >>> dictionaries_str_to_list(',') ['en_US'] If the input is empty, the default list of dictionaries for the current locale is returned: >>> old_lc_all = os.environ.get('LC_ALL') >>> os.environ['LC_ALL'] = 'hi_IN.UTF-8' >>> dictionaries_str_to_list('') ['hi_IN', 'en_GB'] >>> if old_lc_all: ... os.environ['LC_ALL'] = old_lc_all ... else: ... # unneeded return value assigned to variable ... _ = os.environ.pop('LC_ALL', None) If the input has more than the maximum number of allowed dictionaries the list is reduced to the maximum (currently 10): >>> dictionaries_str_to_list('en_AG,en_AU,en_BW,en_CA,en_DK,en_GB,en_HK,en_IE,en_IL,en_IN,en_NG,en_NZ,en_PH,en_SC,en_SG,en_US,en_ZA,en_ZM,en_ZW') ['en_AG', 'en_AU', 'en_BW', 'en_CA', 'en_DK', 'en_GB', 'en_HK', 'en_IE', 'en_IL', 'en_IN'] If the input contains duplicates, the duplicates are removed: >>> dictionaries_str_to_list('en_US.UTF-8,en_US,en.UTF-8,en,C') ['en_US', 'en', 'en_US_POSIX'] Non-standard spellings of the special dictionary ['None'] are converted to the standard spelling: >>> dictionaries_str_to_list('nOnE') ['None'] >>> dictionaries_str_to_list('a,en_GB') ['en_GB'] >>> dictionaries_str_to_list('xxxx') ['en_US'] ''' # pylint: enable=line-too-long if dictionaries_str == '': return get_default_dictionaries(get_effective_lc_ctype()) dictionaries: List[str] = [] for dictionary in [re.sub(re.escape('none'), 'None', x.strip(), flags=re.IGNORECASE) for x in dictionaries_str.split(',') if x.strip()]: if dictionary != 'None': dictionary = locale_normalize(dictionary) if dictionary and dictionary not in dictionaries: dictionaries.append(dictionary) if len(dictionaries) > MAXIMUM_NUMBER_OF_DICTIONARIES: LOGGER.error( 'More than the allowed maximum of %s dictionaries.\n' 'Trying to use: %s\n' 'Really using: %s\n', MAXIMUM_NUMBER_OF_DICTIONARIES, dictionaries, dictionaries[:MAXIMUM_NUMBER_OF_DICTIONARIES]) dictionaries = dictionaries[:MAXIMUM_NUMBER_OF_DICTIONARIES] if dictionaries: return dictionaries return ['en_US'] HUNSPELL_DICTIONARIES = { # List of all locales/languages where hunspell dictionaries exist. # They do not necessary need to be installed on the system at the # moment. But it is known that a hunspell dictionary for that # locale/language exists, so usually one just needs to install the # right package to make it available. 'af_NA', 'af_ZA', 'ak_GH', 'am_ET', 'an_ES', 'ar_AE', 'ar_BH', 'ar_DJ', 'ar_DZ', 'ar_EG', 'ar_ER', 'ar_IL', 'ar_IN', 'ar_IQ', 'ar_JO', 'ar_KM', 'ar_KW', 'ar_LB', 'ar_LY', 'ar_MA', 'ar_MR', 'ar_OM', 'ar_PS', 'ar_QA', 'ar_SA', 'ar_SD', 'ar_SO', 'ar_SY', 'ar_TD', 'ar_TN', 'ar_YE', 'as_IN', 'ast_ES', 'az_AZ', 'be_BY', 'ber_MA', 'bg_BG', 'bn_IN', 'br_FR', 'bs_BA', 'ca_AD', 'ca_ES', 'ca_ES_VALENCIA', 'ca_FR', 'ca_IT', 'ckb', 'ckb_IQ', 'cop_EG', 'cs_CZ', 'csb_PL', 'cv_RU', 'cy_GB', 'da_DK', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_LI', 'de_LU', 'dsb_DE', 'el_CY', 'el_GR', 'en_AG', 'en_AU', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_DK', 'en_GB', 'en_GH', 'en_HK', 'en_IE', 'en_IN', 'en_JM', 'en_MW', 'en_NA', 'en_NG', 'en_NZ', 'en_PH', 'en_SG', 'en_TT', 'en_US', 'en_ZA', 'en_ZM', 'en_ZW', 'eo', 'es_AR', 'es_BO', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EC', 'es_ES', 'es_GT', 'es_HN', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'et_EE', 'eu_ES', 'fa_IR', 'fil_PH', 'fj', 'fo_FO', 'fr_BE', 'fr_CA', 'fr_CH', 'fr_FR', 'fr_LU', 'fr_MC', 'fur_IT', 'fy_DE', 'fy_NL', 'ga_IE', 'gd_GB', 'gl_ES', 'grc', 'gu_IN', 'gug', 'gv_GB', 'haw', 'he_IL', 'hi_IN', 'hil_PH', 'hr_HR', 'hsb_DE', 'ht_HT', 'hu_HU', 'hy_AM', 'ia', 'id_ID', 'is_IS', 'it_CH', 'it_IT', 'kk_KZ', 'km_KH', 'kmr', 'kmr_TR', 'kmr_SY', 'kn_IN', 'ko_KR', 'ku_SY', 'ku_TR', 'ky_KG', 'la', 'lb_LU', 'ln_CD', 'lt_LT', 'lv_LV', 'mai_IN', 'mg', 'mi_NZ', 'mk_MK', 'ml_IN', 'mn_MN', 'mos_BF', 'mr_IN', 'ms_BN', 'ms_MY', 'mt_MT', 'nb_NO', 'nds_DE', 'nds_NL', 'ne_IN', 'ne_NP', 'nl_AW', 'nl_BE', 'nl_NL', 'nn_NO', 'nr_ZA', 'nso_ZA', 'ny_MW', 'oc_FR', 'om_ET', 'om_KE', 'or_IN', 'pa_IN', 'pl_PL', 'plt', 'pt_AO', 'pt_BR', 'pt_PT', 'qu_EC', 'quh_BO', 'ro_RO', 'ru_RU', 'ru_UA', 'rw_RW', 'sc_IT', 'se_FI', 'se_NO', 'se_SE', 'sh_ME', 'sh_RS', 'sh_YU', 'shs_CA', 'si_LK', 'sk_SK', 'sl_SI', 'smj_NO', 'smj_SE', 'so_DJ', 'so_ET', 'so_KE', 'so_SO', 'sq_AL', 'sr_ME', 'sr_RS', 'sr_YU', 'ss_ZA', 'st_ZA', 'sv_FI', 'sv_SE', 'sw_KE', 'sw_TZ', 'ta_IN', 'te_IN', 'tet_ID', 'tet_TL', 'th_TH', 'ti_ER', 'ti_ET', 'tk', 'tl_PH', 'tn_BW', 'tn_ZA', 'tpi_PG', 'ts_ZA', 'uk_UA', 'ur_IN', 'ur_PK', 'uz_UZ', 've_ZA', 'vi_VN', 'wa_BE', 'xh_ZA', 'yi_US', 'zu', } CLDR_ANNOTATION_FILES = { # List of all locales/languages where CLDR annotation files # currently exist. Not all of these are necessarily available at # the moment. That depends what is currently installed on the # system. 'af', 'af_NA', 'af_ZA', 'agq', 'agq_CM', 'ak', 'ak_GH', 'am', 'am_ET', 'ar', 'ar_001', 'ar_AE', 'ar_BH', 'ar_DJ', 'ar_DZ', 'ar_EG', 'ar_EH', 'ar_ER', 'ar_IL', 'ar_IQ', 'ar_JO', 'ar_KM', 'ar_KW', 'ar_LB', 'ar_LY', 'ar_MA', 'ar_MR', 'ar_OM', 'ar_PS', 'ar_QA', 'ar_SA', 'ar_SD', 'ar_SO', 'ar_SS', 'ar_SY', 'ar_TD', 'ar_TN', 'ar_YE', 'as', 'as_IN', 'asa', 'asa_TZ', 'ast', 'ast_ES', 'az', 'az_Cyrl', 'az_Cyrl_AZ', 'az_Latn', 'az_Latn_AZ', 'bas', 'bas_CM', 'be', 'be_BY', 'bem', 'bem_ZM', 'bew', 'bez', 'bez_TZ', 'bg', 'bg_BG', 'bgn', 'blo', 'bm', 'bm_ML', 'bn', 'bn_BD', 'bn_IN', 'bo', 'bo_CN', 'bo_IN', 'br', 'br_FR', 'brx', 'brx_IN', 'bs', 'bs_Cyrl', 'bs_Cyrl_BA', 'bs_Latn', 'bs_Latn_BA', 'ca', 'ca_AD', 'ca_ES', 'ca_ES_VALENCIA', 'ca_FR', 'ca_IT', 'ccp', 'ccp_BD', 'ccp_IN', 'ce', 'ce_RU', 'ceb', 'cgg', 'cgg_UG', 'chr', 'chr_US', 'ckb', 'ckb_IQ', 'ckb_IR', 'cs', 'cs_CZ', 'cu', 'cu_RU', 'cv', 'cy', 'cy_GB', 'da', 'da_DK', 'da_GL', 'dav', 'dav_KE', 'de', 'de_AT', 'de_BE', 'de_CH', 'de_DE', 'de_IT', 'de_LI', 'de_LU', 'dje', 'dje_NE', 'doi', 'dsb', 'dsb_DE', 'dua', 'dua_CM', 'dyo', 'dyo_SN', 'dz', 'dz_BT', 'ebu', 'ebu_KE', 'ee', 'ee_GH', 'ee_TG', 'el', 'el_CY', 'el_GR', 'en', 'en_001', 'en_150', 'en_AG', 'en_AI', 'en_AS', 'en_AT', 'en_AU', 'en_BB', 'en_BE', 'en_BI', 'en_BM', 'en_BS', 'en_BW', 'en_BZ', 'en_CA', 'en_CC', 'en_CH', 'en_CK', 'en_CM', 'en_CX', 'en_CY', 'en_DE', 'en_DG', 'en_DK', 'en_DM', 'en_ER', 'en_FI', 'en_FJ', 'en_FK', 'en_FM', 'en_GB', 'en_GD', 'en_GG', 'en_GH', 'en_GI', 'en_GM', 'en_GU', 'en_GY', 'en_HK', 'en_IE', 'en_IL', 'en_IM', 'en_IN', 'en_IO', 'en_JE', 'en_JM', 'en_KE', 'en_KI', 'en_KN', 'en_KY', 'en_LC', 'en_LR', 'en_LS', 'en_MG', 'en_MH', 'en_MO', 'en_MP', 'en_MS', 'en_MT', 'en_MU', 'en_MW', 'en_MY', 'en_NA', 'en_NF', 'en_NG', 'en_NL', 'en_NR', 'en_NU', 'en_NZ', 'en_PG', 'en_PH', 'en_PK', 'en_PN', 'en_PR', 'en_PW', 'en_RW', 'en_SB', 'en_SC', 'en_SD', 'en_SE', 'en_SG', 'en_SH', 'en_SI', 'en_SL', 'en_SS', 'en_SX', 'en_SZ', 'en_TC', 'en_TK', 'en_TO', 'en_TT', 'en_TV', 'en_TZ', 'en_UG', 'en_UM', 'en_US', 'en_US_POSIX', 'en_VC', 'en_VG', 'en_VI', 'en_VU', 'en_WS', 'en_ZA', 'en_ZM', 'en_ZW', 'eo', 'eo_001', 'es', 'es_419', 'es_AR', 'es_BO', 'es_BR', 'es_BZ', 'es_CL', 'es_CO', 'es_CR', 'es_CU', 'es_DO', 'es_EA', 'es_EC', 'es_ES', 'es_GQ', 'es_GT', 'es_HN', 'es_IC', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PH', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'et', 'et_EE', 'eu', 'eu_ES', 'ewo', 'ewo_CM', 'fa', 'fa_AF', 'fa_IR', 'ff', 'ff_Adlm', 'fi', 'fi_FI', 'fil', 'fil_PH', 'fo', 'fo_DK', 'fo_FO', 'fr', 'fr_BE', 'fr_BF', 'fr_BI', 'fr_BJ', 'fr_BL', 'fr_CA', 'fr_CD', 'fr_CF', 'fr_CG', 'fr_CH', 'fr_CI', 'fr_CM', 'fr_DJ', 'fr_DZ', 'fr_FR', 'fr_GA', 'fr_GF', 'fr_GN', 'fr_GP', 'fr_GQ', 'fr_HT', 'fr_KM', 'fr_LU', 'fr_MA', 'fr_MC', 'fr_MF', 'fr_MG', 'fr_ML', 'fr_MQ', 'fr_MR', 'fr_MU', 'fr_NC', 'fr_NE', 'fr_PF', 'fr_PM', 'fr_RE', 'fr_RW', 'fr_SC', 'fr_SN', 'fr_SY', 'fr_TD', 'fr_TG', 'fr_TN', 'fr_VU', 'fr_WF', 'fr_YT', 'frr', 'fur', 'fur_IT', 'fy', 'fy_NL', 'ga', 'ga_IE', 'gd', 'gd_GB', 'gl', 'gl_ES', 'gsw', 'gsw_CH', 'gsw_FR', 'gsw_LI', 'gu', 'gu_IN', 'guz', 'guz_KE', 'gv', 'gv_IM', 'ha', 'ha_GH', 'ha_NE', 'ha_NG', 'haw', 'haw_US', 'he', 'he_IL', 'hi', 'hi_IN', 'hi_Latn', 'hr', 'hr_BA', 'hr_HR', 'hsb', 'hsb_DE', 'hu', 'hu_HU', 'hy', 'hy_AM', 'id', 'id_ID', 'ig', 'ig_NG', 'ii', 'ii_CN', 'is', 'is_IS', 'it', 'it_CH', 'it_IT', 'it_SM', 'it_VA', 'ja', 'ja_JP', 'jgo', 'jgo_CM', 'jmc', 'jmc_TZ', 'jv', 'ka', 'ka_GE', 'kab', 'kab_DZ', 'kam', 'kam_KE', 'kde', 'kde_TZ', 'kea', 'kea_CV', 'khq', 'khq_ML', 'ki', 'ki_KE', 'kk', 'kk_Arab', 'kk_KZ', 'kkj', 'kkj_CM', 'kl', 'kl_GL', 'kln', 'kln_KE', 'km', 'km_KH', 'kn', 'kn_IN', 'ko', 'ko_KP', 'ko_KR', 'kok', 'kok_IN', 'ks', 'ks_IN', 'ksb', 'ksb_TZ', 'ksf', 'ksf_CM', 'ksh', 'ksh_DE', 'ku', 'kw', 'kw_GB', 'ky', 'ky_KG', 'lag', 'lag_TZ', 'lb', 'lb_LU', 'lg', 'lg_UG', 'lij', 'lkt', 'lkt_US', 'ln', 'ln_AO', 'ln_CD', 'ln_CF', 'ln_CG', 'lo', 'lo_LA', 'lrc', 'lrc_IQ', 'lrc_IR', 'lt', 'lt_LT', 'lu', 'lu_CD', 'luo', 'luo_KE', 'luy', 'luy_KE', 'lv', 'lv_LV', 'mai', 'mas', 'mas_KE', 'mas_TZ', 'mer', 'mer_KE', 'mfe', 'mfe_MU', 'mg', 'mg_MG', 'mgh', 'mgh_MZ', 'mgo', 'mgo_CM', 'mi', 'mk', 'mk_MK', 'ml', 'ml_IN', 'mn', 'mn_MN', 'mni', 'mr', 'mr_IN', 'ms', 'ms_BN', 'ms_MY', 'ms_SG', 'mt', 'mt_MT', 'mua', 'mua_CM', 'my', 'my_MM', 'mzn', 'mzn_IR', 'naq', 'naq_NA', 'nb', 'nb_NO', 'nb_SJ', 'nd', 'nd_ZW', 'nds', 'nds_DE', 'nds_NL', 'ne', 'ne_IN', 'ne_NP', 'nl', 'nl_AW', 'nl_BE', 'nl_BQ', 'nl_CW', 'nl_NL', 'nl_SR', 'nl_SX', 'nmg', 'nmg_CM', 'nn', 'nn_NO', 'nnh', 'nnh_CM', 'no', 'no_NO', 'nso', 'nus', 'nus_SS', 'nyn', 'nyn_UG', 'oc', 'om', 'om_ET', 'om_KE', 'or', 'or_IN', 'os', 'os_GE', 'os_RU', 'pa', 'pa_Arab', 'pa_Arab_PK', 'pa_Guru', 'pa_Guru_IN', 'pap', 'pcm', 'pl', 'pl_PL', 'prg', 'prg_001', 'ps', 'ps_AF', 'pt', 'pt_AO', 'pt_BR', 'pt_CH', 'pt_CV', 'pt_GQ', 'pt_GW', 'pt_LU', 'pt_MO', 'pt_MZ', 'pt_PT', 'pt_ST', 'pt_TL', 'qu', 'qu_BO', 'qu_EC', 'qu_PE', 'quc', 'rhg', 'rm', 'rm_CH', 'rn', 'rn_BI', 'ro', 'ro_MD', 'ro_RO', 'rof', 'rof_TZ', 'root', 'ru', 'ru_BY', 'ru_KG', 'ru_KZ', 'ru_MD', 'ru_RU', 'ru_UA', 'rw', 'rw_RW', 'rwk', 'rwk_TZ', 'sa', 'sah', 'sah_RU', 'saq', 'saq_KE', 'sat', 'sbp', 'sbp_TZ', 'sc', 'sd', 'sd_PK', 'se', 'se_FI', 'se_NO', 'se_SE', 'seh', 'seh_MZ', 'ses', 'ses_ML', 'sg', 'sg_CF', 'shi', 'shi_Latn', 'shi_Latn_MA', 'shi_Tfng', 'shi_Tfng_MA', 'si', 'si_LK', 'sk', 'sk_SK', 'sl', 'sl_SI', 'smn', 'smn_FI', 'sn', 'sn_ZW', 'so', 'so_DJ', 'so_ET', 'so_KE', 'so_SO', 'sq', 'sq_AL', 'sq_MK', 'sq_XK', 'sr', 'sr_Cyrl', 'sr_Cyrl_BA', 'sr_Cyrl_ME', 'sr_Cyrl_RS', 'sr_Cyrl_XK', 'sr_Latn', 'sr_Latn_BA', 'sr_Latn_ME', 'sr_Latn_RS', 'sr_Latn_XK', 'su', 'sv', 'sv_AX', 'sv_FI', 'sv_SE', 'sw', 'sw_CD', 'sw_KE', 'sw_TZ', 'sw_UG', 'ta', 'ta_IN', 'ta_LK', 'ta_MY', 'ta_SG', 'te', 'te_IN', 'teo', 'teo_KE', 'teo_UG', 'tg', 'tg_TJ', 'th', 'th_TH', 'ti', 'ti_ER', 'ti_ET', 'tk', 'tk_TM', 'tn', 'to', 'to_TO', 'tr', 'tr_CY', 'tr_TR', 'tt', 'tt_RU', 'twq', 'twq_NE', 'tzm', 'tzm_MA', 'ug', 'ug_CN', 'uk', 'uk_UA', 'ur', 'ur_IN', 'ur_PK', 'uz', 'uz_Arab', 'uz_Arab_AF', 'uz_Cyrl', 'uz_Cyrl_UZ', 'uz_Latn', 'uz_Latn_UZ', 'vai', 'vai_Latn', 'vai_Latn_LR', 'vai_Vaii', 'vai_Vaii_LR', 'vec', 'vi', 'vi_VN', 'vo', 'vo_001', 'vun', 'vun_TZ', 'wae', 'wae_CH', 'wo', 'wo_SN', 'xh', 'xog', 'xog_UG', 'yav', 'yav_CM', 'yi', 'yi_001', 'yo', 'yo_BJ', 'yo_NG', 'yue', 'yue_Hans', 'yue_Hans_CN', 'yue_Hant', 'yue_Hant_HK', 'zgh', 'zgh_MA', 'zh', 'zh_Hans', 'zh_Hans_CN', 'zh_Hans_HK', 'zh_Hans_MO', 'zh_Hans_SG', 'zh_Hant', 'zh_Hant_HK', 'zh_Hant_MO', 'zh_Hant_TW', 'zu', 'zu_ZA', } GOOGLE_SPEECH_TO_TEXT_LANGUAGES = { # List of languages supported by the Google Cloud Speech-to-Text # speech recognition engine. # # https://cloud.google.com/speech-to-text/docs/languages # # The original list above uses identifiers in BCP-47 format. # # In the list below, “-” is replaced “_” to be able to merge # the list better with the lists of hunspell dictionaries and # cldr annotation files. 'af_ZA', 'am_ET', 'hy_AM', 'az_AZ', 'id_ID', 'ms_MY', 'bn_BD', 'bn_IN', 'ca_ES', 'cs_CZ', 'da_DK', 'de_DE', 'en_AU', 'en_CA', 'en_GH', 'en_GB', 'en_IN', 'en_IE', 'en_KE', 'en_NZ', 'en_NG', 'en_PH', 'en_ZA', 'en_TZ', 'en_US', 'es_AR', 'es_BO', 'es_CL', 'es_CO', 'es_CR', 'es_EC', 'es_SV', 'es_ES', 'es_US', 'es_GT', 'es_HN', 'es_MX', 'es_NI', 'es_PA', 'es_PY', 'es_PE', 'es_PR', 'es_DO', 'es_UY', 'es_VE', 'eu_ES', 'fil_PH', 'fr_CA', 'fr_FR', 'gl_ES', 'ka_GE', 'gu_IN', 'hr_HR', 'zu_ZA', 'is_IS', 'it_IT', 'jv_ID', 'kn_IN', 'km_KH', 'lo_LA', 'lv_LV', 'lt_LT', 'hu_HU', 'ml_IN', 'mr_IN', 'nl_NL', 'ne_NP', 'nb_NO', 'pl_PL', 'pt_BR', 'pt_PT', 'ro_RO', 'si_LK', 'sk_SK', 'sl_SI', 'su_ID', 'sw_TZ', 'sw_KE', 'fi_FI', 'sv_SE', 'ta_IN', 'ta_SG', 'ta_LK', 'ta_MY', 'te_IN', 'vi_VN', 'tr_TR', 'ur_PK', 'ur_IN', 'el_GR', 'bg_BG', 'ru_RU', 'sr_RS', 'uk_UA', 'he_IL', 'ar_IL', 'ar_JO', 'ar_AE', 'ar_BH', 'ar_DZ', 'ar_SA', 'ar_IQ', 'ar_KW', 'ar_MA', 'ar_TN', 'ar_OM', 'ar_PS', 'ar_QA', 'ar_LB', 'ar_EG', 'fa_IR', 'hi_IN', 'th_TH', 'ko_KR', 'zh_TW', 'yue_Hant_HK', 'ja_JP', 'zh_HK', 'zh', } SUPPORTED_DICTIONARIES = set() SUPPORTED_DICTIONARIES.update(HUNSPELL_DICTIONARIES) SUPPORTED_DICTIONARIES.update(CLDR_ANNOTATION_FILES) SUPPORTED_DICTIONARIES.update(GOOGLE_SPEECH_TO_TEXT_LANGUAGES) FLAGS = { 'None': '🏴', # Fallback if nothing else can be found 'root': '🌐', '001': '🌐', # World '150': '🌍', # Europe '419': '🌎', # South America 'A': '🇦', 'B': '🇧', 'C': '🇨', 'D': '🇩', 'E': '🇪', 'F': '🇫', 'G': '🇬', 'H': '🇭', 'I': '🇮', 'J': '🇯', 'K': '🇰', 'L': '🇱', 'M': '🇲', 'N': '🇳', 'O': '🇴', 'P': '🇵', 'Q': '🇶', 'R': '🇷', 'S': '🇸', 'T': '🇹', 'U': '🇺', 'V': '🇻', 'W': '🇼', 'X': '🇽', 'Y': '🇾', 'Z': '🇿', 'af': '🇿🇦', 'agq': '🇨🇲', 'ak': '🇬🇭', 'am': '🇪🇹', 'ar': '🌍', 'as': '🌏', 'asa': '🌍', 'ast': '🌍', 'az': '🌍', 'bas': '🌍', 'be': '🌍', 'bem': '🌍', 'bez': '🌍', 'bg': '🌍', 'bgn': '🇦🇫🇮🇷🇵🇰', 'bm': '🌍', 'bn': '🌏', 'bo': '🌏', 'br': '🌍', 'brx': '🌏', 'bs': '🌍', 'ca': '🌍', 'ccp': '🌏', 'ce': '🌍', 'ceb': '🇵🇭', 'cgg': '🌍', 'chr': '🌎', 'ckb': '🌍', 'cop': '🌍', 'cs': '🌍', 'cu': '🌍', 'cy': '🌍', 'cy_GB': '🏴󠁧󠁢󠁷󠁬󠁳󠁿', 'da': '🌍', 'dav': '🌍', 'de': '🌍', 'dje': '🌍', 'dsb': '🌍', 'doi': '🇮🇳', 'dua': '🌍', 'dyo': '🌍', 'dz': '🌏', 'ebu': '🌍', 'ee': '🌍', 'el': '🌍', 'en': '🌍', 'eo': '🌍', 'es': '🌍', 'et': '🌍', 'eu': '🌍', 'ewo': '🌍', 'fa': '🌍', 'ff': '🌍', 'fi': '🌍', 'fil': '🌏', 'fj': '🇫🇯', 'fo': '🌍', 'fr': '🌍', 'fur': '🌍', 'fy': '🌍', 'ga': '🌍', 'gd': '🌍', 'gd_GB': '🏴󠁧󠁢󠁳󠁣󠁴󠁿', 'gl': '🌍', 'grc': '🇬🇷', 'gsw': '🌍', 'gu': '🌏', 'guz': '🌍', 'gv': '🌍', 'ha': '🌍', 'haw': '🌎', 'he': '🌍', 'hi': '🌏', 'hr': '🌍', 'hsb': '🌍', 'ht': '🌎', 'hu': '🌍', 'hy': '🌍', 'ia': '🌍', 'id': '🌏', 'ig': '🌍', 'ii': '🌏', 'is': '🌍', 'it': '🌍', 'ja': '🌏', 'jgo': '🌍', 'jmc': '🌍', 'jv': '🌏', 'jv_ID': '🌍', 'ka': '🌍', 'kab': '🌍', 'kam': '🌍', 'kde': '🌍', 'kea': '🌍', 'khq': '🌍', 'ki': '🌍', 'kk': '🌍', 'kkj': '🌍', 'kl': '🌍', 'kln': '🌍', 'km': '🌏', 'kn': '🌏', 'ko': '🌏', 'kok': '🌏', 'ks': '🌏', 'ksb': '🌍', 'ksf': '🌍', 'ksh': '🌍', 'ku': '🌍', 'kw': '🌍', 'ky': '🌍', 'la': '🇻🇦', 'lag': '🌍', 'lb': '🌍', 'lg': '🌍', 'lkt': '🌎', 'ln': '🌍', 'lo': '🌏', 'lrc': '🌍', 'lt': '🌍', 'lu': '🌍', 'luo': '🌍', 'luo_KE': '', 'luy': '🌍', 'lv': '🌍', 'mai': '🌍', 'mas': '🌍', 'mer': '🌍', 'mfe': '🌍', 'mg': '🌍', 'mgh': '🌍', 'mgo': '🌍', 'mi': '🌏', 'mk': '🌍', 'ml': '🌏', 'mn': '🌏', 'mni': '🇮🇳🇧🇩🇲🇲', 'mos': '🌍', 'mr': '🌏', 'ms': '🌏', 'mt': '🌍', 'mua': '🌍', 'my': '🌏', 'mzn': '🌍', 'naq': '🌍', 'nb': '🌍', 'nd': '🌍', 'nds': '🌍', 'ne': '🌏', 'nl': '🌍', 'nmg': '🌍', 'nn': '🌍', 'nnh': '🌍', 'no': '🇳🇴', 'nr': '🌍', 'nso': '🌍', 'nus': '🌍', 'ny': '🌍', 'nyn': '🌍', 'oc': '🌍', 'om': '🌍', 'or': '🌏', 'os': '🌍', 'pa': '🌍', 'pcm': '🇳🇬', 'pl': '🌍', 'plt': '🌍', 'prg': '🌍', 'ps': '🌍', 'pt': '🌍', 'qu': '🌎', 'quh': '🌎', 'rm': '🌍', 'rn': '🌍', 'ro': '🌍', 'rof': '🌍', 'ru': '🌍', 'rw': '🌍', 'rwk': '🌍', 'sa': '🇮🇳', 'sah': '🌍', 'saq': '🌍', 'sat': '🇮🇳', 'sbp': '🌍', 'sc': '🇮🇹', 'sd': '🌍', 'se': '🌍', 'seh': '🌍', 'ses': '🌍', 'sg': '🌍', 'sh': '🌍', 'shi': '🌍', 'shs': '🌎', 'si': '🌍', 'sk': '🌍', 'sl': '🌍', 'smj': '🌍', 'smn': '🌍', 'sn': '🌍', 'so': '🌍', 'sq': '🌍', 'sr': '🌍', 'ss': '🌍', 'st': '🌍', 'su': '🌏', 'sv': '🌍', 'sw': '🌍', 'ta': '🌏', 'te': '🌏', 'teo': '🌍', 'tet': '🌏', 'tg': '🌍', 'th': '🌏', 'ti': '🌍', 'tk': '🌍', 'tl': '🌏', 'tn': '🌍', 'to': '🌏', 'tpi': '🌏', 'tr': '🌍', 'ts': '🌍', 'tt': '🌍', 'twq': '🌍', 'tzm': '🌍', 'ug': '🌏', 'uk': '🌍', 'ur': '🌏', 'uz': '🌍', 'vai': '🌍', 've': '🌍', 'vi': '🌏', 'vo': '🌍', 'vun': '🌍', 'wa': '🌍', 'wae': '🌍', 'wo': '🌍', 'xh': '🌍', 'xog': '🌍', 'yav': '🌍', 'yi': '🌍', 'yo': '🌍', 'yue': '🌏', 'yue_Hans': '🌏', 'yue_Hant': '🌏', 'zgh': '🌍', 'zh': '🌏', 'zh_Hans': '🌏', 'zh_Hant': '🌏', 'zu': '🌍', } def get_flag(lookup_text: str) -> str: '''Lookup a flag emoji for a lookup string or a reaonable fallback if there is no flag. Examples: >>> get_flag('en_GB') '🇬🇧' >>> get_flag('GB') '🇬🇧' There is no Yugoslavia anymore, nevertheless returning '🇾🇺' is fine, a white flag with question mark is shown in the “Noto Color Emoji” font. >>> get_flag('YU') '🇾🇺' >>> get_flag('en_001') '🌐' >>> get_flag('001') '🌐' >>> get_flag('ca_Latn_ES_VALENCIA') '🇪🇸' >>> get_flag('af') '🇿🇦' >>> get_flag('zu') '🌍' ''' if lookup_text in FLAGS: return FLAGS[lookup_text] if (len(lookup_text) == 2 and lookup_text.isalpha() and lookup_text.isupper() and lookup_text[0] in FLAGS and lookup_text[1] in FLAGS): return FLAGS[lookup_text[0]] + FLAGS[lookup_text[1]] if lookup_text.isdigit() and len(lookup_text) == 3: return FLAGS.get(lookup_text, FLAGS['None']) locale = parse_locale(lookup_text) # pylint: disable=redefined-outer-name if (len(locale.territory) == 2 and locale.territory.isalpha() and locale.territory.isupper() and locale.territory[0] in FLAGS and locale.territory[1] in FLAGS): return FLAGS[locale.territory[0]] + FLAGS[locale.territory[1]] if locale.territory.isdigit() and len(locale.territory) == 3: return FLAGS.get(locale.territory, FLAGS['None']) if locale.language and locale.language in FLAGS: return FLAGS[locale.language] return FLAGS['None'] def get_flags(dictionaries: List[str]) -> Dict[str, str]: # pylint: disable=line-too-long ''' Examples: >>> get_flags(['de_DE', 'fr_FR', 'eo']) {'de_DE': '🇩🇪', 'fr_FR': '🇫🇷', 'eo': '🌍'} >>> get_flags(['fr_FR', 'de_DE', 'fy_DE', 'eo', 'de', '150']) {'fr_FR': '🇫🇷fr_FR', 'de_DE': '🇩🇪de_DE', 'fy_DE': '🇩🇪fy_DE', 'eo': '🌍eo', 'de': '🌍de', '150': '🌍150'} ''' # pylint: enable=line-too-long flags: Dict[str, str] = {} flags_seen: Set[str] = set() duplicate_flags = False for dictionary in dictionaries: new_flag = get_flag(dictionary) flags[dictionary] = new_flag if new_flag in flags_seen: duplicate_flags = True flags_seen.add(new_flag) if duplicate_flags: for key, flag in flags.items(): if not flag.endswith(key): flags[key] += key return flags def get_effective_lc_ctype() -> str: '''Returns the effective value of LC_CTYPE''' if 'LC_ALL' in os.environ: return os.environ['LC_ALL'] if 'LC_CTYPE' in os.environ: return os.environ['LC_CTYPE'] if 'LANG' in os.environ: return os.environ['LANG'] return 'C' def get_effective_lc_messages() -> str: '''Returns the effective value of LC_MESSAGES''' if 'LC_ALL' in os.environ: return os.environ['LC_ALL'] if 'LC_MESSAGES' in os.environ: return os.environ['LC_MESSAGES'] if 'LANG' in os.environ: return os.environ['LANG'] return 'C' # For the ICU/CLDR locale pattern see: http://userguide.icu-project.org/locale # (We ignore the variant code here) _cldr_locale_pattern = re.compile( # language must be 2 or 3 lower case letters: '^(?P[a-z]{2,3}' # language is only valid if +'(?=$|@' # locale string ends here or only options follow +'|_[A-Z][a-z]{3}(?=$|@|_[A-Z0-9]{2,3}(?=$|@))' # valid script follows +'|_[A-Z0-9]{2,3}(?=$|@)' # valid territory follows +'))' # script must be 1 upper case letter followed by # 3 lower case letters: +'(?:_(?P