--todo: need to send a callback when setting a new language, this will be used by the volatile menu to refresh the menu
--todo: compress the language tables that aren't in use
--todo: check cooltip fonts

--[=[
    namespace = DetailsFramework.Language
    Register() = DetailsFramework.Language.Register()

    Register(addonId, languageId[, gameLanguageOnly])
        create a language table within an addon namespace
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
        @gameLanguageOnly: if true won't allow to register a language not supported by the game, a supported language is any language returnted by GetLocale()
        return value: return a table named languageTable, this table holds translations for the registered language

        The returned table can be used to add localized phrases:
        --example 1:
        local newLanguageTable = DetailsFramework.Language.Register("Details", "enUS", true)
        newLanguageTable["My Phrase"] = "My Phrase"

        --example 2:
        local newLanguageTable = DetailsFramework.Language.Register(_G.Details, "valyrianValyria", false)
        newLanguageTable["STRING_MY_PHRASE"] = "ñuha udrir"

    GetLanguageTable(addonId[, languageId])
        get the languageTable for the requested languageId within the addon namespace
        if languageId is not passed, uses the current language set for the addonId
        the default languageId for the addon is the first language registered with DetailsFramework.Language.Register()
        the languageId will be overrided when the language used by the client is registered with DetailsFramework.Language.Register()
        the default languageId can also be changed by calling DetailsFramework.Language.SetCurrentLanguage() as seen below
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
        return value: languageTable

        --example 1:
        local languageTable = DetailsFramework.Language.GetLanguageTable("Details")
        fontString:SetText(languageTable["My Phrase"])
        --example 2:
        local languageTable = DetailsFramework.Language.GetLanguageTable("Details", "valyrianValyria")
        fontString:SetText(languageTable["STRING_MY_PHRASE"])

    GetText(addonId, phraseId[, silent])
        get a text from a registered addonId and phraseId, return a localized string and the languageId where the string was found, otherwise return the phraseId
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @phraseId: any string to identify the a translated text, example: phraseId: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
        @silent: if true won't error on invalid phrase text and instead use the phraseId as the text, it will still error on invalid addonId

    ShowOptionsHelp()
        print to chat the available options ids, use SetOption to set them

    SetOption(addonId, optionId, value)
        set an option

    SetCurrentLanguage(addonId, languageId)
        set the language used by default when retriving a languageTable with DF.Language.GetLanguageTable() and not passing the second argument (languageId) within the call
        use this in combination with a savedVariable to use a language of the user choice
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)

    CreateLanguageSelector(addonId, parent, callback, selectedLanguage)
        create and return a dropdown (using details framework dropdown widget) to select the laguage
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @parent: a frame to use as parent while creating the language selector dropdown
        @callback: a function which will be called when the user select a new language function(languageId) print("new language:", languageId) end
        @selectedLanguage: default selected language

    SetFontForLanguageId(addonId, languageId, fontPath)
    SetFontByAlphabetOrRegion(addonId, latin_FontPath, cyrillic_FontPath, china_FontPath, korean_FontPath, taiwan_FontPath)
        set the font to be used for different languages, if no font is registered for a language, the lib will guess if the font need to be changed and change to a compatible with the language
        if a font name is passed the lib will attempt to retrive from LibSharedMedia
        latin changes the font for "deDE", "enUS", "esES", "esMX", "frFR", "itIT" and "ptBR"
        cyrillic for "ruRU", china for "zhCN", korean for "koKR" and taiwan for "zhTW"
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other string value to represent a language already registered

    RegisterObject(addonId, object, phraseId[, silent[, ...]])
        to be registered, the Object need to have a SetText method
        when setting a languageId with DetailsFramework.Language.SetCurrentLanguage(), automatically change the text of all registered Objects
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @object: any UIObject or table with SetText method
        @phraseId: any string to identify the a translated text, example: "My Phrase", "STRING_TEXT_LENGTH", text: "This is my phrase"
        @silent: if true won't error on invalid phrase text and instead use the phraseId as the text, it will still error on invalid addonId and Object
        @vararg: arguments to pass for format(text, ...)

    UpdateObjectArguments(addonId, object, ...)
        update the arguments (...) of a registered Object, if no argument passed it'll erase the arguments previously set
        the Object need to be already registered with DetailsFramework.Language.RegisterObject()
        the font string text will be changed to update the text with the new arguments
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @object: any UIObject or table with SetText method
        @vararg: arguments to pass for format(text, ...)

    RegisterTableKey(addonId, table, key, phraseId[, silent[, ...]])
        when setting a languageId with DetailsFramework.Language.SetCurrentLanguage(), automatically change the text of all registered tables, table[key] = 'new translated text'
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @table: a lua table
        @key: any value except nil or boolean
        @phraseId: any string to identify the a translated text, example: token: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
        @silent: if true won't error on invalid phrase text or table already registered, it will still error on invalid addonId, table, key and phraseId
        @vararg: arguments to pass for format(text, ...)

    UpdateTableKeyArguments(addonId, table, key, ...)
        same as UpdateObjectArguments() but for table keys
        @addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
        @table: a lua table
        @key: any value except nil or boolean
        @vararg: arguments to pass for format(text, ...)

    RegisterObjectWithDefault(addonId, object, phraseId, defaultText[, ...])
        (helper function) register an object if a phraseID is valid or object:SetText(defaultText) is called

    RegisterTableKeyWithDefault(addonId, table, key, phraseId, defaultText[, ...])
        (helper function) register a tableKey if a phraseID is valid or table[key] = defaultText

    CreateLocTable(addonId, phraseId[, shouldRegister = true[, ...]])
        make a table to pass instead of the text while using DetailsFramework widgets
        this avoid to call the register object function right after creating the widget
        also make easy to register the same phraseID in many different widgets

    SetTextWithLocTable(object, locTable)
        set the text of an object using a locTable, the object need the method SetText

    SetTextWithLocTableWithDefault(object, locTable, defaultText)
        set the text from the locTable if passed or use the defaultText

    SetTextIfLocTableOrDefault(object, locTable)
        set the text if locTable is a locTable or SetText(locTable)

    RegisterTableKeyWithLocTable(table, key, locTable[, silence])
        same as RegisterTableKey() but get addonId, phraseId and arguments from the locTable

    RegisterObjectWithLocTable(object, locTable[, silence])
        same as RegisterObject() but get addonId, phraseId and arguments from the locTable

--]=]


---@class addonNamespaceTable : table store language settings for an addon
---@class fontPath : string font path for a font file on the hardware of the user

local DF = _G["DetailsFramework"]
if (not DF or not DetailsFrameworkCanLoad) then
	return
end

local format = string.format
local unpack = table.unpack or unpack
local GetLocale = _G.GetLocale

local CONST_LANGUAGEID_ENUS = "enUS"
local gameLanguage = GetLocale()

local addonNamespaceOptions = {
    ChangeOnlyRegisteredFont = false,
}
local optionsHelp = {
    ChangeOnlyRegisteredFont = "when changing the language, won't change the font if the font isn't registered for the language or region",
}

local debugElabled = false
local printDebug = function(functionName, ...)
    if (debugElabled) then
        print("|cFFFFAA00Languages|r:", "|cFFFFFF00" .. functionName .. "|r", ...)
    end
end

local supportedGameLanguages = {
    ["deDE"] = true,
    [CONST_LANGUAGEID_ENUS] = true,
    ["esES"] = true,
    ["esMX"] = true,
    ["frFR"] = true,
    ["itIT"] = true,
    ["ptBR"] = true,
    ["koKR"] = true,
    ["ruRU"] = true,
    ["zhCN"] = true,
    ["zhTW"] = true,
}

local fontLanguageCompatibility = {
    ["deDE"] = 1,
    [CONST_LANGUAGEID_ENUS] = 1,
    ["esES"] = 1,
    ["esMX"] = 1,
    ["frFR"] = 1,
    ["itIT"] = 1,
    ["ptBR"] = 1,
    ["zhCN"] = 2,
    ["zhTW"] = 3,
    ["koKR"] = 4,
    ["ruRU"] = 5,
}

--this table contains all the registered languages with their name and fonts
--by default it has all languages the game supports
--it is used when the dropdown shows the languages available for an addon, it take from here the name and font
--new non-native game languages registered with DetailsFramework.Language.RegisterLanguage() will be added to this table
local languagesAvailable = {
    deDE = {text = "Deutsch", font = "Fonts\\FRIZQT__.TTF"},
    enUS = {text = "English (US)", font = "Fonts\\FRIZQT__.TTF"},
    esES = {text = "Español (ES)", font = "Fonts\\FRIZQT__.TTF"},
    esMX = {text = "Español (MX)", font = "Fonts\\FRIZQT__.TTF"},
    frFR = {text = "Français", font = "Fonts\\FRIZQT__.TTF"},
    itIT = {text = "Italiano", font = "Fonts\\FRIZQT__.TTF"},
    koKR = {text = "한국어", font = [[Fonts\2002.TTF]]},
    ptBR = {text = "Português (BR)", font = "Fonts\\FRIZQT__.TTF"},
    ruRU = {text = "Русский", font = "Fonts\\FRIZQT___CYR.TTF"},
    zhCN = {text = "简体中文", font = [[Fonts\ARHei.ttf]]},
    zhTW = {text = "繁體中文", font = [[Fonts\ARHei.ttf]]},
}

local ignoredCharacters = {}
local punctuations = ".,;:!?-–—()[]{}'\"`/\\@_+*^%$#&~=<>|"
for character in punctuations:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
    ignoredCharacters[character] = true
end

local accentedLetters = "áàâäãåæçéèêëíìîïñóòôöõøœúùûüýÿ"
for character in accentedLetters:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
    ignoredCharacters[character] = true
end

--store a list of letters, characters and symbols used on each language
---@type table<string, string>
DF.LanguageKnowledge = DF.LanguageKnowledge or {}

local latinAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for character in latinAlphabet:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
    DF.LanguageKnowledge[character] = CONST_LANGUAGEID_ENUS
end

--version 1:
---register the letters and symbols used on phrases
---@param languageId any
---@param languageTable any
local registerCharacters = function(languageId, languageTable)
    for stringId, textString in pairs(languageTable) do
        for character in textString:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
            if (not ignoredCharacters[character]) then
                if (not DF.LanguageKnowledge[character]) then
                    if (fontLanguageCompatibility[languageId] ~= 1) then
                        DF.LanguageKnowledge[character] = languageId
                    end
                end
            end
        end
    end
end

---receives a character and attempt to get its byte code
---@param character string
---@return string, number
local getByteCodeForCharacter = function(character)
    local byteCode = ""
    local amountOfBytes = 0
    for symbolPiece in character:gmatch(".") do --separate each byte, can one, two or three bytes
        byteCode = byteCode .. string.byte(symbolPiece)
        amountOfBytes = amountOfBytes + 1
    end
    return byteCode, amountOfBytes
end

---receives a character and attempt to get its byte code
---@param character string
---@return string
local getQuickByteCodeForCharacter = function(character)
    local byte1, byte2, byte3, byte4 = string.byte(character, 1, #character)
    return (byte1 or "") .. "" .. (byte2 or "") .. "" .. (byte3 or "") .. "" .. (byte4 or "")
end

--version 2:
---register the letters and symbols used on phrases
---@param languageId any
---@param languageTable any
local registerCharacters = function(languageId, languageTable)
    if (fontLanguageCompatibility[languageId] ~= 1) then
        for stringId, textString in pairs(languageTable) do
            for character in textString:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
                local byteCode, amountOfBytes = getByteCodeForCharacter(character)

                --latin letters and escape sequences always use one byte per character
                if (amountOfBytes >= 2) then --at least 2 bytes
                    if (not DF.LanguageKnowledge[byteCode]) then
                        DF.LanguageKnowledge[byteCode] = languageId
                    end
                end
            end
        end
    end
end

local functionSignature = {
    ["RegisterLanguage"] = "RegisterLanguage(addonID, languageID[, gameLanguageOnly])",
    ["SetCurrentLanguage"] = "SetCurrentLanguage(addonID, languageID)",
    ["GetLanguageTable"] = "GetLanguageTable(addonID[, languageID])",
    ["SetFontByAlphabetOrRegion"] = "SetFontByAlphabetOrRegion(addonId, latin, cyrillic, china, korean, taiwan)",
    ["SetFontForLanguageId"] = "SetFontForLanguageId(addonId, languageId, fontPath)",
    ["GetText"] = "GetText(addonID, phraseID[, silent])",

    ["RegisterObject"] = "RegisterObject(addonID, object, phraseID[, silent[, ...]])",
    ["UpdateObjectArguments"] = "UpdateObjectArguments(addonID, object, ...)",
    ["RegisterTableKey"] = "RegisterTableKey(addonId, table, key, phraseId[[, silent[, ...]])",
    ["UpdateTableKeyArguments"] = "UpdateTableKeyArguments(addonId, table, key, ...)",

    ["RegisterObjectWithDefault"] = "RegisterObjectWithDefault(addonId, object, phraseId, defaultText[, ...])",
    ["RegisterTableKeyWithDefault"] = "RegisterTableKeyWithDefault(addonId, table, key, phraseId, defaultText[, ...])",

    ["SetOption"] = "SetOption(addonId, optionId, value)",

    ["CreateLocTable"] = "CreateLocTable(addonId, phraseId, shouldRegister[, silent[, ...]])",
    ["UnpackLocTable"] = "UnpackLocTable(locTable)",
    ["IsLocTable"] = "IsLocTable(locTable)",
    ["CanRegisterLocTable"] = "CanRegisterLocTable(locTable)",
    ["RegisterObjectWithLocTable"] = "RegisterObjectWithLocTable(object, locTable[, silence])",
    ["RegisterTableKeyWithLocTable"] = "RegisterTableKeyWithLocTable(table, key, locTable[, silence])",
    ["SetTextWithLocTable"] = "SetTextWithLocTable(object, locTable)",
    ["SetTextWithLocTableWithDefault"] = "SetTextWithLocTableWithDefault(object, locTable, defaultText)",
    ["SetTextIfLocTableOrDefault"] = "SetTextIfLocTableOrDefault(object, locTable or string)",
    ["CreateLanguageSelector"] = "DetailsFramework.Language.CreateLanguageSelector",
}

local functionCallPath = {
    ["RegisterLanguage"] = "DetailsFramework.Language.RegisterLanguage",
    ["SetCurrentLanguage"] = "DetailsFramework.Language.SetCurrentLanguage",
    ["GetLanguageTable"] = "DetailsFramework.Language.GetLanguageTable",
    ["SetFontByAlphabetOrRegion"] = "DetailsFramework.Language.SetFontByAlphabetOrRegion",
    ["SetFontForLanguageId"] = "DetailsFramework.Language.SetFontForLanguageId",
    ["GetText"] = "DetailsFramework.Language.GetText",

    ["RegisterObject"] = "DetailsFramework.Language.RegisterObject",
    ["UpdateObjectArguments"] = "DetailsFramework.Language.UpdateObjectArguments",
    ["RegisterTableKey"] = "DetailsFramework.Language.RegisterTableKey",
    ["UpdateTableKeyArguments"] = "DetailsFramework.Language.UpdateTableKeyArguments",

    ["RegisterObjectWithDefault"] = "DetailsFramework.Language.RegisterObjectWithDefault",
    ["RegisterTableKeyWithDefault"] = "DetailsFramework.Language.RegisterTableKeyWithDefault",

    ["SetOption"] = "DetailsFramework.Language.SetOption",

    ["CreateLocTable"] = "DetailsFramework.Language.CreateLocTable",
    ["UnpackLocTable"] = "DetailsFramework.Language.UnpackLocTable",
    ["CanRegisterLocTable"] = "DetailsFramework.Language.CanRegisterLocTable",
    ["RegisterObjectWithLocTable"] = "DetailsFramework.Language.RegisterObjectWithLocTable",
    ["SetTextWithLocTable"] = "DetailsFramework.Language.SetTextWithLocTable",
    ["IsLocTable"] = "DetailsFramework.Language.IsLocTable",
    ["SetTextWithLocTableWithDefault"] = "DetailsFramework.Language.SetTextWithLocTableWithDefault",
    ["SetTextIfLocTableOrDefault"] = "DetailsFramework.Language.SetTextIfLocTableOrDefault",
    ["RegisterTableKeyWithLocTable"] = "DetailsFramework.Language.RegisterTableKeyWithLocTable",
    ["CreateLanguageSelector"] = "CreateLanguageSelector(addonId, parent, callback, selectedLanguage)",
}

local errorText = {
    ["AddonID"] = "require a valid addonID (table or string) on #%d argument",
    ["AddonIDInvalidOrNotRegistered"] = "invalid addonID or no languages registered",
    ["LanguageID"] = "require a languageID supported by the game on #%d argument",
    ["PhraseID"] = "require a string (phrase id) on #%d argument",
    ["LanguageIDInvalid"] = "require a string (language id) on #%d argument",
    ["FontPathInvalid"] = "require a string (font path) on #%d argument",
    ["NoLanguages"] = "no languages registered for addonId",
    ["LanguageIDNotRegistered"] = "languageID not registered",
    ["PhraseIDNotRegistered"] = "phraseID not registered",
    ["InvalidObject"] = "invalid object on #%d argument, object must have SetText method and be an UIObject or table",
    ["ObjectNotRegistered"] = "Object not registered yet",
    ["TableKeyNotRegistered"] = "table not registered yet",
    ["KeyNotRegistered"] = "key not registered yet",
    ["InvalidTable"] = "require a table on #%d argument",
    ["InvalidTableKey"] = "require a table key on #%d argument",
    ["TableKeyAlreadyRegistered"] = "table already registered", --not in use
    ["InvalidLocTable"] = "invalid locTable on #%d argument",
    ["LocTableCantRegister"] = "cannot register object or tableKey, locTable.register == false",
    ["InvalidOptionId"] = "invalid option on #%d argument",
}


--create language namespace
DF.Language = DF.Language or {version = 1}
DF.Language.RegisteredNamespaces = DF.Language.RegisteredNamespaces or {}

DF.Language.LanguageMixin = {
    SetAddonID = function(self, addonId)
        self.addonId = addonId
    end,

    GetAddonID = function(self, addonId)
        return self.addonId
    end,
}

--internal functions
local isValid_AddonID = function(addonId)
    if (type(addonId) ~= "string" and type(addonId) ~= "table") then
        return false
    end
    return true
end

local isValid_LanguageID = function(languageId)
    if (type(languageId) ~= "string") then
        return false
    end
    return true
end

local isValid_PhraseID = function(phraseId)
    return type(phraseId) == "string"
end

local isValid_Text = function(text)
    return type(text) == "string"
end

local isValid_Object = function(object)
    if (type(object) ~= "table" or not object.SetText) then
        return false
    end
    return true
end


--always create a new namespace if isn't registered yet
local getOrCreateAddonNamespace = function(addonId, languageId)
    local addonNamespaceTable = DF.Language.RegisteredNamespaces[addonId]
    if (not addonNamespaceTable) then
        addonNamespaceTable = {
            addonId = addonId,

            --store registered functions to call when the language is changed
            callbacks = {},

            --by default, the current language is the first registered language
            currentLanguageId = languageId,
            languages = {},
            registeredObjects = {},
            tableKeys = setmetatable({}, {__mode = "k"}),
            fonts = {},

            --set when the first language table is registered
            defaultLanguageTable = false,

            options = {},
        }

        DF.table.copy(addonNamespaceTable.options, addonNamespaceOptions)
        DF.Language.RegisteredNamespaces[addonId] = addonNamespaceTable

        printDebug("getOrCreateAddonNamespace", "created new addon namespace for:", addonId)
    end

    --if the language being register is the language being in use by the client, set this language as current language
    --this can be changed later with DF.Language.SetCurrentLanguage(addonId, languageId)
    local clientLanguage = GetLocale()
    if (languageId == clientLanguage) then
        addonNamespaceTable.currentLanguageId = languageId
    end

    return addonNamespaceTable
end


--just get the addon namespace returning nil if not registered yet
local getAddonNamespace = function(addonId)
    return DF.Language.RegisteredNamespaces[addonId]
end

local triggerRegisteredCallbacksForAddonId = function(addonId)
    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        return
    end

    local callbacks = addonNamespaceTable.callbacks

    for i = 1, #callbacks do
        local callback = callbacks[i]
        --trigger a secure callback using xpcall
        xpcall(callback.callback, _G["geterrorhandler"](), addonId, addonNamespaceTable.currentLanguageId, unpack(callback.payload))
    end
end

--these two are only used for the dropdown create the change the language
--for custom callbacks use DF.Language.RegisterCallback()
local setLanguageChangedCallback = function(addonNamespaceTable, callback)
    printDebug("setLanguageChangedCallback", "addonId:", addonNamespaceTable.addonId, "callbackType:", type(callback))
    addonNamespaceTable.onLanguageChangeCallback = callback
end
local getLanguageChangedCallback = function(addonNamespaceTable)
    return addonNamespaceTable.onLanguageChangeCallback
end

local getLanguageTable = function(addonNamespaceTable, languageId)
    local languageTable = addonNamespaceTable.languages[languageId]
    if (not languageTable) then
        return false
    end
    return languageTable
end

local getRegisteredLanguages = function(addonNamespaceTable)
    return addonNamespaceTable.languages
end

local getCurrentLanguageId = function(addonNamespaceTable)
    return addonNamespaceTable.currentLanguageId
end

local setOption = function(addonNamespaceTable, optionId, value)
    printDebug("setOption", "addonId:", addonNamespaceTable.addonId, "optionId:", optionId, "value:", value, "valueType:", type(value))
    addonNamespaceTable.options[optionId] = value
end

local getRegisteredObjects = function(addonNamespaceTable)
    return addonNamespaceTable.registeredObjects
end

--return a string representing a translated text and the languageId where the string was found
--attempt to get from the current selected language, then from the game language and then from english if the other two fails
--return false if the phraseId isn't found at all
local getText = function(addonNamespaceTable, phraseId)
    local currentLanguageId = getCurrentLanguageId(addonNamespaceTable)
    local languageTable = getLanguageTable(addonNamespaceTable, currentLanguageId)

    local text = phraseId

    --embed phraseId is when the phraseId is within the string but is surrounded by @
    local embedPhraseId = phraseId:match("@(.-)@")

    --get the text from the current language table
    if (languageTable) then
        text = rawget(languageTable, embedPhraseId or phraseId)
        if (isValid_Text(text)) then
            if (embedPhraseId) then
                --replace the embed phraseId with the translated text
                text = phraseId:gsub("@" .. embedPhraseId .. "@", text)
                return text, currentLanguageId
            else
                return text, currentLanguageId
            end
        end
    end

    --the translated string wasn't found on the current language table
    --attempt to get the text from the default language used in the client
    local clientLanguage = GetLocale()
    if (currentLanguageId ~= clientLanguage) then
        languageTable = getLanguageTable(addonNamespaceTable, clientLanguage)
        if (languageTable) then
            text = rawget(languageTable, embedPhraseId or phraseId)
            if (isValid_Text(text)) then
                if (embedPhraseId) then
                    --replace the embed phraseId with the translated text
                    text = phraseId:gsub("@" .. embedPhraseId .. "@", text)
                    return text, currentLanguageId
                else
                    return text, clientLanguage
                end
            end
        end
    end

    --attempt to get from english
    if (currentLanguageId ~= CONST_LANGUAGEID_ENUS and clientLanguage ~= CONST_LANGUAGEID_ENUS) then
        languageTable = getLanguageTable(addonNamespaceTable, CONST_LANGUAGEID_ENUS)
        if (languageTable) then
            text = rawget(languageTable, embedPhraseId or phraseId)
            if (isValid_Text(text)) then
                if (embedPhraseId) then
                    --replace the embed phraseId with the translated text
                    text = phraseId:gsub("@" .. embedPhraseId .. "@", text)
                    return text, CONST_LANGUAGEID_ENUS
                else
                    return text, CONST_LANGUAGEID_ENUS
                end
            end
        end
    end

    return false, CONST_LANGUAGEID_ENUS
end

local setLanguageTableForLanguageId = function(addonNamespaceTable, languageId, languageTable)
    local isFirstLanguage = not next(addonNamespaceTable.languages)
    if (isFirstLanguage) then
        printDebug("setLanguageTableForLanguageId", "(first to be registered) addonId:", addonNamespaceTable.addonId, "languageId:", languageId, "languageTable:", languageTable, "languageIdType:", type(languageId), "languageTableType:", type(languageTable))
        addonNamespaceTable.defaultLanguageTable = languageTable
    else
        printDebug("setLanguageTableForLanguageId", "addonId:", addonNamespaceTable.addonId, "languageId:", languageId, "languageTable:", languageTable, "languageIdType:", type(languageId), "languageTableType:", type(languageTable))

        local defaultLanguageMetatable = {__index = function(table, key)
            local value = rawget(table, key)
            if (value) then
                return value
            end

            local defaultLanguageTable = addonNamespaceTable.defaultLanguageTable
            value = defaultLanguageTable[key]
            if (value) then
                return value
            end

            return key
        end}

        setmetatable(languageTable, defaultLanguageMetatable)
    end

    addonNamespaceTable.languages[languageId] = languageTable
    return languageTable
end

local setCurrentLanguageId = function(addonNamespaceTable, languageId)
    printDebug("setCurrentLanguageId", "addonId:", addonNamespaceTable.addonId, "languageId:", languageId, "languageIdType:", type(languageId))

    addonNamespaceTable.currentLanguageId = languageId

    local callbackFunc = getLanguageChangedCallback(addonNamespaceTable)
    if (callbackFunc) then
        printDebug("setCurrentLanguageId", "addonId:", addonNamespaceTable.addonId, "calling callback", "callbackFuncType:", type(callbackFunc))
        xpcall(callbackFunc, _G["geterrorhandler"](), languageId, addonNamespaceTable.addonId)
    end

    triggerRegisteredCallbacksForAddonId(addonNamespaceTable.addonId)
end

local parseArguments = function(...)
    local argumentAmount = select("#", ...)
    if (argumentAmount > 0) then
        return {...}
    else
        return nil
    end
end

--hold information about a localization, used by registered objects and keyTables, has .phraesId, .arguments and .key (on keyTables)
local createPhraseInfoTable = function(phraseId, key, ...)
    return {
        phraseId = phraseId,
        key = key,
        arguments = parseArguments(...)
    }
end

local updatePhraseInfo_PhraseId = function(phraseInfoTable, phraseId)
    phraseInfoTable.phraseId = phraseId
end

local updatePhraseInfo_Arguments = function(phraseInfoTable, ...)
    phraseInfoTable.arguments = parseArguments(...)
end

---return the fontPath for the languageId or nil if the languageId is not registered for the addon
---@param addonNamespaceTable table
---@param languageId string
---@return fontPath|nil
local getFontForLanguageIdFromAddonNamespace = function(addonNamespaceTable, languageId)
    return addonNamespaceTable.fonts[languageId]
end

local shouldChangeFontForNewLanguage = function(addonNamespaceTable, oldLanguageId, newLanguageId)
    --does it need to change the font?
    local oldLanguageClusterId = fontLanguageCompatibility[oldLanguageId]
    local newLanguageClusterId = fontLanguageCompatibility[newLanguageId]

    if (oldLanguageClusterId == newLanguageClusterId) then
        --does not require to change the font
        return false
    else
        if (addonNamespaceTable.options.ChangeOnlyRegisteredFont) then
            --can change only if the font was previously registered with SetFontForLanguageId() or SetFontByAlphabetOrRegion()
            local languageFontPath = getFontForLanguageIdFromAddonNamespace(addonNamespaceTable, newLanguageId)
            if (languageFontPath) then
                --the font is registered
                return true, languageFontPath
            end
        else
            local languageFontPath = getFontForLanguageIdFromAddonNamespace(addonNamespaceTable, newLanguageId)
            if (languageFontPath) then
                --the font is registered
                return true, languageFontPath
            else
                --the font is not registered for this language, get the default font from the framework
                languageFontPath = DF.Language.GetFontForLanguageID(newLanguageId)
                return true, languageFontPath
            end
        end
    end
end

--get a phraseInfo and text returning a formatted text using arguments if they exists
local getFormattedText = function(phraseInfoTable, text)
    if (phraseInfoTable.arguments) then
        return format(text, unpack(phraseInfoTable.arguments))
    else
        return text
    end
end

local getObjectPhraseInfoTable = function(addonNamespaceTable, object)
    return addonNamespaceTable.registeredObjects[object]
end

local setObject_InternalMembers = function(object, addonId, phraseId, arguments, languageId)
    object.__languageAddonId = addonId or object.__languageAddonId
    object.__languagePhraseId = phraseId or object.__languagePhraseId
    object.__languageArguments = arguments or object.__languageArguments
    object.__languageId = languageId or object.__languageId
end

local setObject_Text = function(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)
    if (textLanguageId ~= object.__languageId) then
        local bShouldChangeFont, fontPath = shouldChangeFontForNewLanguage(addonNamespaceTable, object.__languageId, textLanguageId)
        if (bShouldChangeFont) then
            if (object:GetObjectType() == "Button") then
                local fontString = object:GetFontString()
                if (fontString) then
                    local font, size, flags = fontString:GetFont()
                    fontString:SetFont(fontPath, size, flags)
                end
            else
                local font, size, flags = object:GetFont()
                object:SetFont(fontPath, size, flags)
            end
            setObject_InternalMembers(object, false, false, false, textLanguageId)
        end
    end

    local formattedText = getFormattedText(phraseInfoTable, text)

    object:SetText(formattedText)
end

--this method only exists on registered Objects
local objectMethod_SetTextByPhraseID = function(object, phraseId, ...)
    local addonId = object.__languageAddonId
    local addonNamespaceTable = getAddonNamespace(addonId)

    local phraseInfoTable = getObjectPhraseInfoTable(addonNamespaceTable, object)
    updatePhraseInfo_PhraseId(phraseInfoTable, phraseId)
    updatePhraseInfo_Arguments(phraseInfoTable, ...)

    local currentLanguageId = getCurrentLanguageId(addonNamespaceTable)

    --when registering a new object, consider the font already set on the obejct to be a font compatible with the client languageId
    setObject_InternalMembers(object, addonId, phraseId, phraseInfoTable.arguments, currentLanguageId)

    local text, textLanguageId = getText(addonNamespaceTable, phraseId)
    setObject_Text(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)

    return true
end

local registerObject = function(addonNamespaceTable, object, phraseId, ...)
    local phraseInfoTable = getObjectPhraseInfoTable(addonNamespaceTable, object)

    if (phraseInfoTable) then
        --the object is already registered, update the phraseId and arguments
        updatePhraseInfo_PhraseId(phraseInfoTable, phraseId)
        updatePhraseInfo_Arguments(phraseInfoTable, ...)
    else
        phraseInfoTable = createPhraseInfoTable(phraseId, nil, ...)
        addonNamespaceTable.registeredObjects[object] = phraseInfoTable
    end

    local currentLanguageId = getCurrentLanguageId(addonNamespaceTable)

    --save internal information about the language directly in the object
    setObject_InternalMembers(object, addonNamespaceTable.addonId, phraseId, phraseInfoTable.arguments, gameLanguage)

    --give the object a new method
    object.SetTextByPhraseID = objectMethod_SetTextByPhraseID

    return phraseInfoTable
end

--iterate among all registered objects of an addon namespace and set the new text on them
local updateAllRegisteredObjectsText = function(addonNamespaceTable)
    local objects = getRegisteredObjects(addonNamespaceTable)
    for object, phraseInfoTable in pairs(objects) do
        local phraseId = phraseInfoTable.phraseId
        --note: text is always valid when the callstack started at from DF.Language.SetCurrentLanguage
        local text, textLanguageId = getText(addonNamespaceTable, phraseId)
        setObject_Text(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)
    end
end

--internal tableKey looks like:
--addonNamespaceTable.tableKeys = {} -> this table is a weaktable 'k'
--addonNamespaceTable.tableKeys[table] = {} -> table is a table from code elsewhere, it is used as a key for the internal code here, table created is what stores the keys
--addonNamespaceTable.tableKeys[table][key] -> key is the key from the table elsewhere which points to a string
--addonNamespaceTable.tableKeys[table][key] = {phraseId = phraseId, arguments = {...}, key = key}

--when registring a tableKey in practice, look like:
--Details.StoredStrings = {}; Details.StoredStrings["Height"] = "height": table is 'Details.StoredStrings' key is "Height"
--registerTableKey(_, Details.StoredStrings, "Height", _, _)

local setTableKey_Text = function(table, key, phraseInfoTable, text)
    local formattedText = getFormattedText(phraseInfoTable, text)
    table[key] = formattedText
end

local getTableKeyTable = function(addonNamespaceTable, table)
    return addonNamespaceTable.tableKeys[table]
end

--get the phraseInfo from the addon namespace
local getTableKeyPhraseInfoTable = function(addonNamespaceTable, table, key)
    return addonNamespaceTable.tableKeys[table][key]
end

--get the phraseInfo from the tableKey
local getPhraseInfoFromTableKey = function(tableKeyTable, key)
    return tableKeyTable[key]
end

local isTableKeyRegistered = function(addonNamespaceTable, table)
    return getTableKeyTable(addonNamespaceTable, table) and true
end

--return true if the phraseInfo is present in the tableKey
local isKeyRegisteredInTableKey = function(tableKeyTable, key)
    return getPhraseInfoFromTableKey(tableKeyTable, key) and true
end

local getRegisteredTableKeys = function(addonNamespaceTable)
    return addonNamespaceTable.tableKeys
end

local registerTableKeyTable = function(addonNamespaceTable, table, tableKeyTable)
    addonNamespaceTable.tableKeys[table] = tableKeyTable
end

local registerTableKey = function(addonNamespaceTable, table, key, phraseId, ...) --~registerTableKey
    local tableKeyTable = getTableKeyTable(addonNamespaceTable, table)
    if (not tableKeyTable) then
        tableKeyTable = {}
        registerTableKeyTable(addonNamespaceTable, table, tableKeyTable)
    end

    local phraseInfoTable = getPhraseInfoFromTableKey(tableKeyTable, key)

    if (phraseInfoTable) then
        --the key is already registered for this table, update the phraseId and arguments
        phraseInfoTable.phraseId = phraseId
        phraseInfoTable.arguments = parseArguments(...)
    else
        phraseInfoTable = createPhraseInfoTable(phraseId, key, ...)
        tableKeyTable[key] = phraseInfoTable
    end

    return tableKeyTable
end

--iterate among all registered tableKey of an addon namespace and set the new text on them
local updateAllRegisteredTableKeyText = function(addonNamespaceTable)
    local tableKeys = getRegisteredTableKeys(addonNamespaceTable)
    for table, tableKeyTable in pairs(tableKeys) do
        for key, phraseInfoTable in pairs(tableKeyTable) do
            local phraseId = phraseInfoTable.phraseId
            --note: text is always valid when the callstack started at from DF.Language.SetCurrentLanguage
            local text, textLanguageId = getText(addonNamespaceTable, phraseId)
            setTableKey_Text(table, key, phraseInfoTable, text)
        end
    end
end

local updateTextOnAllObjectsAndTableKeys = function(addonNamespaceTable)
    updateAllRegisteredObjectsText(addonNamespaceTable)
    updateAllRegisteredTableKeyText(addonNamespaceTable)
end

local setFontForLanguageId = function(addonNamespaceTable, languageId, fontPath)
    addonNamespaceTable.fonts[languageId] = fontPath
    --add into the font combatibility table (which fonts can be used for a language)
    if (not fontLanguageCompatibility[languageId]) then
        fontLanguageCompatibility[languageId] = fontPath
    end
end

---when the language has changed for an addon, call all callbacks registered for that addon
---the function will be called with the following parameters: callback(addonId, languageId, unpack(payload))
---@param addonId string
---@param callback function
---@vararg any
---@return boolean
function DF.Language.RegisterCallback(addonId, callback, ...)
    if (not isValid_AddonID(addonId)) then
        error("RegisterCallback() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
    end
    if (type(callback) ~= "function") then
        error("RegisterCallback() param #2 'callback' must be a function, got: " .. type(callback) .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        return false
    end

    addonNamespaceTable.callbacks[#addonNamespaceTable.callbacks+1] = {callback = callback, payload = {...}}
    return true
end

---unregister a registered addon callback
---@param addonId string
---@param callback function
---@return boolean
function DF.Language.UnregisterCallback(addonId, callback)
    if (not isValid_AddonID(addonId)) then
        error("UnregisterCallback() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
    end
    if (type(callback) ~= "function") then
        error("UnregisterCallback() param #2 'callback' must be a function, got: " .. type(callback) .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        return false
    end

    for i = 1, #addonNamespaceTable.callbacks do
        local callbackTable = addonNamespaceTable.callbacks[i]
        if (callbackTable.callback == callback) then
            table.remove(addonNamespaceTable.callbacks, i)
            return true
        end
    end
    return false
end

---return the languageId of the text passed, if not found return the default languageId (enUS)
---@param text string
---@return string
function DF.Language.DetectLanguageId(text)
    --if the text is not a string, return the default languageId
    if (type(text) ~= "string") then
        return "enUS"
    end

    for character in text:gmatch("[%z\1-\127\194-\244][\128-\191]*") do
        local byteCode, amountOfBytes = getByteCodeForCharacter(character)
        local languageId = DF.LanguageKnowledge[byteCode]
        if (languageId) then
            return languageId
        end
    end
    return "enUS"
end

---get the languageId set to be used on an addon
---@param addonId string
---@return string
function DF.Language.GetLanguageIdForAddonId(addonId)
    if (not isValid_AddonID(addonId)) then
        error("GetLanguageIdForAddonId() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        return "enUS"
    end

    return addonNamespaceTable.currentLanguageId
end

---check if the key exists in the default language table for the addon
---@param addonId string
---@param key string
---@return boolean
function DF.Language.DoesPhraseIDExistsInDefaultLanguage(addonId, key)  --~DoesPhraseIDExistsInDefaultLanguage
    if (not isValid_AddonID(addonId)) then
        error("DoesPhraseIDExistsInDefaultLanguage() param #1 'addonId' must be a string or a table, got: " .. type(addonId) .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        return false
    end

    local defaultLanguageTable = addonNamespaceTable.defaultLanguageTable
    if (not defaultLanguageTable) then
        return false
    end

    return rawget(defaultLanguageTable, key) and true
end


---create a language table within an addon namespace
---if bIsNativeGameLanguage is true, languageName and languageFont are required
---@param addonId string an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
---@param languageId string game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other value if 'gameLanguageOnly' is false (default)
---@param bNotSupportedWoWLanguage boolean if true it indicates that this is not a native game language
---@return table: return a languageTable, this table holds translations for the registered language
function DF.Language.RegisterLanguage(addonId, languageId, bNotSupportedWoWLanguage, languageName, languageFont)  --~RegisterLanguage
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["RegisterLanguage"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")

    elseif (not bNotSupportedWoWLanguage and not supportedGameLanguages[languageId]) then
        error(functionCallPath["RegisterLanguage"] .. ": " .. format(errorText["LanguageID"], 2) .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")

    elseif (bNotSupportedWoWLanguage) then
        if (not languageName) then
            error(functionCallPath["RegisterLanguage"] .. ": " .. "Invalid Language Name" .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
        end
        if (not languageFont) then
            error(functionCallPath["RegisterLanguage"] .. ": " .. "Invalid Language Font" .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
        end

        if (not supportedGameLanguages[languageId]) then
            languagesAvailable[languageId] = {text = languageName, font = languageFont}
        end
    end

    --get the language namespace, the namespace can be a string or a table.
    --if the namespace isn't created yet, this function will create
    local addonNamespaceTable = getOrCreateAddonNamespace(addonId, languageId)

    if (bNotSupportedWoWLanguage) then
        setFontForLanguageId(addonNamespaceTable, languageId, languageFont)
        DF.registeredFontPaths[languageId] = languageFont
    end

    --create a table to hold traslations for this languageId
    local languageTable = {}
    setLanguageTableForLanguageId(addonNamespaceTable, languageId, languageTable)

    --after the language is registered, usualy comes the registration of phrases of the language registered
    --let the phrases be registered and after that register the characters and symbols used on that language
    C_Timer.After(0, function()
        registerCharacters(languageId, languageTable)
    end)

    return languageTable
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@optionId: the ID of the option, check 
function DF.Language.SetOption(addonId, optionId, value)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["SetOption"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["SetOption"] .. ".")
    end

    if (not addonNamespaceOptions[optionId]) then
        error(functionCallPath["SetOption"] .. ": " .. format(errorText["InvalidOptionId"], 2) .. ", use: " .. functionSignature["SetOption"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["SetOption"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    setOption(addonNamespaceTable, optionId, value)
end

--print to chat the available option
function DF.Language.ShowOptionsHelp()
    for optionId, descriptionString in pairs(optionsHelp) do
        print(optionId .. ": " .. descriptionString)
    end
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other string value to represent a language
--@fontPath: a path for a font
function DF.Language.SetFontForLanguageId(addonId, languageId, fontPath)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["SetFontForLanguageId"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["SetFontForLanguageId"] .. ".")
    end

    if (not isValid_LanguageID(languageId)) then
        error(functionCallPath["SetFontForLanguageId"] .. ": " .. format(errorText["LanguageIDInvalid"], 2) .. ", use: " .. functionSignature["SetFontForLanguageId"] .. ".")
    end

    if (type(fontPath) ~= "string") then
        error(functionCallPath["SetFontForLanguageId"] .. ": " .. format(errorText["FontPathInvalid"], 3) .. ", use: " .. functionSignature["SetFontForLanguageId"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["SetFontForLanguageId"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    setFontForLanguageId(addonNamespaceTable, languageId, fontPath)
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@regions: accept a font name, ignored is nil is passed
function DF.Language.SetFontByAlphabetOrRegion(addonId, latin, cyrillic, china, korean, taiwan)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["SetFontByAlphabetOrRegion"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["SetFontByAlphabetOrRegion"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["SetFontByAlphabetOrRegion"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    if (latin) then
        local fontPath = latin
        if (type(fontPath) == "string") then
            setFontForLanguageId(addonNamespaceTable, "deDE", fontPath)
            setFontForLanguageId(addonNamespaceTable, "enUS", fontPath)
            setFontForLanguageId(addonNamespaceTable, "esES", fontPath)
            setFontForLanguageId(addonNamespaceTable, "esMX", fontPath)
            setFontForLanguageId(addonNamespaceTable, "frFR", fontPath)
            setFontForLanguageId(addonNamespaceTable, "itIT", fontPath)
            setFontForLanguageId(addonNamespaceTable, "ptBR", fontPath)
        end
    end

    if (cyrillic) then
        local fontPath = cyrillic
        if (type(fontPath) == "string") then
            setFontForLanguageId(addonNamespaceTable, "ruRU", fontPath)
        end
    end

    if (china) then
        local fontPath = china
        if (type(fontPath) == "string") then
            setFontForLanguageId(addonNamespaceTable, "zhCN", fontPath)
        end
    end

    if (korean) then
        local fontPath = korean
        if (type(fontPath) == "string") then
            setFontForLanguageId(addonNamespaceTable, "zhTW", fontPath)
        end
    end

    if (taiwan) then
        local fontPath = taiwan
        if (type(fontPath) == "string") then
            local taiwanCountryLanguageId = "zhTW"
            setFontForLanguageId(addonNamespaceTable, taiwanCountryLanguageId, fontPath)
        end
    end
end


--get the languageTable for the requested languageId within the addon namespace
--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other string value to represent a language if 'gameLanguageOnly' is false (default)
--return value: languageTable
function DF.Language.GetLanguageTable(addonId, languageId)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["GetLanguageTable"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["GetLanguageTable"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["GetLanguageTable"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    --if languageId was not been passed, use the current language
    if (not languageId) then
        languageId = getCurrentLanguageId(addonNamespaceTable)
    end

    local languageTable = getLanguageTable(addonNamespaceTable, languageId)
    if (not languageTable) then
        error(functionCallPath["GetLanguageTable"] .. ": " .. errorText["LanguageIDNotRegistered"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    return languageTable
end


--set the language used when retriving a languageTable with DF.Language.GetLanguageTable() without passing the second argument (languageId)
--use this in combination with a savedVariable to use a language of the user choice
--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@languageId: game languages: "deDE", "enUS", "esES", "esMX", "frFR", "itIT", "koKR", "ptBR", "ruRU", "zhCN", "zhTW", or any other string value to represent a language if 'gameLanguageOnly' is false (default)
function DF.Language.SetCurrentLanguage(addonId, newLanguageId)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["SetCurrentLanguage"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["SetCurrentLanguage"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["SetCurrentLanguage"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    local languageTable = getLanguageTable(addonNamespaceTable, newLanguageId)
    if (not languageTable) then
        error(functionCallPath["SetCurrentLanguage"] .. ": " .. errorText["LanguageIDNotRegistered"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    setCurrentLanguageId(addonNamespaceTable, newLanguageId)

    --go into the registered objects and KeyTables and change their text
    updateTextOnAllObjectsAndTableKeys(addonNamespaceTable)

    return true
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@phraseId: any string to identify the a translated text, example: phraseId: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
--@silent: if true won't error on invalid phrase text and instead use the phraseId as the text, it will still error on invalid addonId
function DF.Language.GetText(addonId, phraseId, silent)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["GetText"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["GetText"] .. ".")

    elseif (not isValid_PhraseID(phraseId)) then
        error(functionCallPath["GetText"] .. ": " .. format(errorText["PhraseID"], 2) .. ", use: " .. functionSignature["GetText"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["GetText"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    local text, textLanguageId = getText(addonNamespaceTable, phraseId)
    if (isValid_Text(text)) then
        return text, textLanguageId
    end

    if (not silent) then
        error(functionCallPath["GetText"] .. ": " .. errorText["PhraseIDNotRegistered"] .. ", use: " .. functionSignature["GetLanguageTable"] .. "['PhraseID'] = 'translated text'.")
    end

    return phraseId
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@object: any UIObject or table with SetText method
--@phraseId: any string to identify the a translated text, example: token: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
--@silent: if true won't error on invalid phrase text and instead use the phraseId as the text, it will still error on invalid addonId and object
--@vararg: arguments to pass for format(text, ...)
function DF.Language.RegisterObject(addonId, object, phraseId, silent, ...)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["RegisterObject"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["RegisterObject"] .. ".")
    end

    if (not isValid_PhraseID(phraseId)) then
        error(functionCallPath["RegisterObject"] .. ": " .. format(errorText["PhraseID"], 3) .. ", use: " .. functionSignature["RegisterObject"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["RegisterObject"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    if (not isValid_Object(object)) then
        error(functionCallPath["RegisterObject"] .. ": " .. format(errorText["InvalidObject"], 2) .. ", use: " .. functionSignature["RegisterObject"] .. ".")
    end

    local objectTable = registerObject(addonNamespaceTable, object, phraseId, ...)

    --on being registered the FontObject is not being checked for the current font set on it
    --this causes the font to have a enUS font from when it was created but wronfully signed as having ruRU font
    --causing the fontstring to not change the font when settings the text

    --solution: when registering a font string, get the font on it a attempt to identify the font set on it
    --solution 2: will be most likely to the font to be the default from the client language, e.g. frizz_quadrata_tt if the client is running enUS
    --solution 3: consider the font being used as the languageId of the client

    local text, textLanguageId = getText(addonNamespaceTable, phraseId)
    if (not isValid_Text(text)) then
        if (not silent) then
            error(functionCallPath["RegisterObject"] .. ": " .. errorText["PhraseIDNotRegistered"] .. ", use: " .. functionSignature["GetLanguageTable"] .. "['PhraseID'] = 'translated text'.")
        else
            text = phraseId
        end
    end

    setObject_Text(addonNamespaceTable, object, objectTable, text, textLanguageId)
    return true
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@object: any UIObject or table with SetText method
--@vararg: arguments to pass for format(text, ...)
function DF.Language.UpdateObjectArguments(addonId, object, ...)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["UpdateObjectArguments"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["UpdateObjectArguments"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["UpdateObjectArguments"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    if (not isValid_Object(object)) then
        error(functionCallPath["UpdateObjectArguments"] .. ": " .. format(errorText["InvalidObject"], 2) .. ", use: " .. functionSignature["UpdateObjectArguments"] .. ".")
    end

    local phraseInfoTable = getObjectPhraseInfoTable(addonNamespaceTable, object)
    if (not phraseInfoTable) then
        error(functionCallPath["UpdateObjectArguments"] .. ": " .. errorText["ObjectNotRegistered"] .. ", use: " .. functionSignature["RegisterObject"] .. ".")
    end
    updatePhraseInfo_Arguments(phraseInfoTable, ...)

    local text, textLanguageId = getText(addonNamespaceTable, phraseInfoTable.phraseId)
    setObject_Text(addonNamespaceTable, object, phraseInfoTable, text, textLanguageId)
    return true
end


--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@table: a lua table
--@key: any value except nil or boolean
--@phraseId: any string to identify the a translated text, example: token: "OPTIONS_FRAME_WIDTH" text: "Adjust the Width of the frame."
--@silent: if true won't error on invalid phrase text or table already registered, it will still error on invalid addonId, table, key and phraseId
--@vararg: arguments to pass for format(text, ...)
function DF.Language.RegisterTableKey(addonId, table, key, phraseId, silent, ...) --~RegisterTableKey
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["RegisterTableKey"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["RegisterTableKey"] .. ".")
    end

    if (type(table) ~= "table") then
        error(functionCallPath["RegisterTableKey"] .. ": " .. format(errorText["InvalidTable"], 2) .. ", use: " .. functionSignature["RegisterTableKey"] .. ".")
    end

    if (key == nil or type(key) == "boolean") then
        error(functionCallPath["RegisterTableKey"] .. ": " .. format(errorText["InvalidTableKey"], 3) .. ", use: " .. functionSignature["RegisterTableKey"] .. ".")
    end

    if (not isValid_PhraseID(phraseId)) then
        error(functionCallPath["RegisterTableKey"] .. ": " .. format(errorText["PhraseID"], 4) .. ", use: " .. functionSignature["RegisterTableKey"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["RegisterTableKey"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    local tableKeyTable = registerTableKey(addonNamespaceTable, table, key, phraseId, ...)

    local text, textLanguageId = getText(addonNamespaceTable, phraseId)
    if (not isValid_Text(text)) then
        if (not silent) then
            error(functionCallPath["RegisterTableKey"] .. ": " .. errorText["PhraseIDNotRegistered"] .. ": " .. phraseId .. ", use: " .. functionSignature["GetLanguageTable"] .. "['PhraseID'] = 'translated text'.")
        else
            text = phraseId
        end
    end

    setTableKey_Text(table, key, tableKeyTable, text)
    return true
end

--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@table: a lua table
--@key: any value except nil or boolean
--@vararg: arguments to pass for format(text, ...)
function DF.Language.UpdateTableKeyArguments(addonId, table, key, ...)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["UpdateTableKeyArguments"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["UpdateTableKeyArguments"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["UpdateTableKeyArguments"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    if (type(table) ~= "table") then
        error(functionCallPath["UpdateTableKeyArguments"] .. ": " .. format(errorText["InvalidTable"], 2) .. ", use: " .. functionSignature["UpdateTableKeyArguments"] .. ".")
    end

    if (key == nil or type(key) == "boolean") then
        error(functionCallPath["UpdateTableKeyArguments"] .. ": " .. format(errorText["InvalidTableKey"], 3) .. ", use: " .. functionSignature["UpdateTableKeyArguments"] .. ".")
    end

    if (not isTableKeyRegistered(addonNamespaceTable, table)) then
        error(functionCallPath["UpdateTableKeyArguments"] .. ": " .. errorText["TableKeyNotRegistered"] .. ", use: " .. functionSignature["RegisterTableKey"] .. ".")
    end

    local tableKeyTable = getTableKeyTable(addonNamespaceTable, table) --can't nil as the line above checked if it exists

    if (not isKeyRegisteredInTableKey(tableKeyTable, key)) then
        error(functionCallPath["UpdateTableKeyArguments"] .. ": " .. errorText["KeyNotRegistered"] .. ", use: " .. functionSignature["RegisterTableKey"] .. ".")
    end

    local phraseInfo = getPhraseInfoFromTableKey(tableKeyTable, key) --can't nil as the line above checked if it exists
    updatePhraseInfo_Arguments(phraseInfo, key, ...)

    local text, textLanguageId = getText(addonNamespaceTable, phraseInfo.phraseId)
    setTableKey_Text(table, key, tableKeyTable, text)
    return true
end


function DF.Language.RegisterTableKeyWithDefault(addonId, table, key, phraseId, defaultText, ...) --~RegisterTableKeyWithDefault
    if (addonId and phraseId) then
        DetailsFramework.Language.RegisterTableKey(addonId, table, key, phraseId, ...)
    else
        table[key] = defaultText
    end
end


function DF.Language.RegisterObjectWithDefault(addonId, object, phraseId, defaultText, ...)
    if (not isValid_Object(object)) then
        error(functionCallPath["RegisterObjectWithDefault"] .. ": " .. format(errorText["InvalidObject"], 2) .. ", use: " .. functionSignature["RegisterObjectWithDefault"] .. ".")
    end

    if (phraseId) then
        DetailsFramework.Language.RegisterObject(addonId, object, phraseId, ...)
    else
        object:SetText(defaultText)
    end
end


function DF.Language.CreateLocTable(addonId, phraseId, shouldRegister, silent, ...)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["CreateLocTable"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["CreateLocTable"] .. ".")
    end

    if (not isValid_PhraseID(phraseId)) then
        error(functionCallPath["CreateLocTable"] .. ": " .. format(errorText["PhraseID"], 2) .. ", use: " .. functionSignature["CreateLocTable"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["CreateLocTable"] .. ": " .. errorText["NoLanguages"] .. ", use: " .. functionSignature["CreateLocTable"] .. ".")
    end

    local text, textLanguageId = getText(addonNamespaceTable, phraseId)
    if (not text) then
        if (not silent) then
            error(functionCallPath["CreateLocTable"] .. ": " .. errorText["PhraseIDNotRegistered"] .. ", use: " .. functionSignature["CreateLocTable"] .. ".")
        end
        return
    end

    if (type(shouldRegister) == "nil") then
        shouldRegister = true
    end

    local newLocTable = {
        addonId = addonId,
        phraseId = phraseId,
        shouldRegister = shouldRegister,
        arguments = parseArguments(...),
    }

    return newLocTable
end

function DF.Language.IsLocTable(locTable)
    if (type(locTable) ~= "table") then
        return false

    elseif (locTable.addonId and locTable.phraseId) then
        return true
    end

    return false
end


function DF.Language.CanRegisterLocTable(locTable)
    if (not DF.Language.IsLocTable(locTable)) then
        error(functionCallPath["CanRegisterLocTable"] .. ": " .. format(errorText["InvalidLocTable"], 1) .. ", use: " .. functionSignature["CanRegisterLocTable"] .. ".")
    end
    return locTable.shouldRegister
end


function DF.Language.UnpackLocTable(locTable)
    if (type(locTable) ~= "table") then
        error(functionCallPath["UnpackLocTable"] .. ": " .. format(errorText["InvalidLocTable"], 1) .. ", use: " .. functionSignature["UnpackLocTable"] .. ".")
    end
    return locTable.addonId, locTable.phraseId, locTable.shouldRegister or false, locTable.arguments
end


function DF.Language.RegisterTableKeyWithLocTable(table, key, locTable, silence)
    if (not DF.Language.IsLocTable(locTable)) then
        error(functionCallPath["RegisterTableKeyWithLocTable"] .. ": " .. format(errorText["InvalidLocTable"], 3) .. ", use: " .. functionSignature["RegisterTableKeyWithLocTable"] .. ".")
    end

    local addonId, phraseId, shouldRegister, arguments = DF.Language.UnpackLocTable(locTable)

    if (not shouldRegister) then
        error(functionCallPath["RegisterTableKeyWithLocTable"] .. ": " .. errorText["LocTableCantRegister"] .. ", use: " .. functionSignature["RegisterTableKeyWithLocTable"] .. ".")
    end

    DF.Language.RegisterTableKey(addonId, table, key, phraseId, silence, arguments and unpack(arguments))
end


function DF.Language.RegisterObjectWithLocTable(object, locTable, silence)
    if (not isValid_Object(object)) then
        error(functionCallPath["RegisterObjectWithLocTable"] .. ": " .. format(errorText["InvalidObject"], 1) .. ", use: " .. functionSignature["RegisterObjectWithLocTable"] .. ".")
    end

    if (not DF.Language.IsLocTable(locTable)) then
        error(functionCallPath["RegisterObjectWithLocTable"] .. ": " .. format(errorText["InvalidLocTable"], 2) .. ", use: " .. functionSignature["RegisterObjectWithLocTable"] .. ".")
    end

    local addonId, phraseId, shouldRegister, arguments = DF.Language.UnpackLocTable(locTable)

    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["RegisterObjectWithLocTable"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["RegisterObjectWithLocTable"] .. ".")
    end

    if (not isValid_PhraseID(phraseId)) then
        error(functionCallPath["RegisterObjectWithLocTable"] .. ": " .. format(errorText["PhraseID"], 2) .. ", use: " .. functionSignature["RegisterObjectWithLocTable"] .. ".")
    end

    if (not shouldRegister) then
        error(functionCallPath["RegisterObjectWithLocTable"] .. ": " .. errorText["LocTableCantRegister"] .. ", use: " .. functionSignature["RegisterObjectWithLocTable"] .. ".")
    end

    DF.Language.RegisterObject(addonId, object, phraseId, silence, arguments and unpack(arguments))
end


function DF.Language.SetTextWithLocTable(object, locTable)
    if (not isValid_Object(object)) then
        error(functionCallPath["SetTextWithLocTable"] .. ": " .. format(errorText["InvalidObject"], 1) .. ", use: " .. functionSignature["SetTextWithLocTable"] .. ".")
    end

    if (not DF.Language.IsLocTable(locTable)) then
        error(functionCallPath["SetTextWithLocTable"] .. ": " .. format(errorText["InvalidLocTable"], 2) .. ", use: " .. functionSignature["SetTextWithLocTable"] .. ".")
    end

    local addonId, phraseId, shouldRegister, arguments = DF.Language.UnpackLocTable(locTable)

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["SetTextWithLocTable"] .. ": " .. errorText["AddonIDInvalidOrNotRegistered"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    if (DF.Language.CanRegisterLocTable(locTable)) then
        DF.Language.RegisterObjectWithLocTable(object, locTable)
        return true
    end

    local text, textLanguageId = getText(addonNamespaceTable, phraseId)

    --can use the locTable instead of the phraseInfoTable because both has the .arguments member
    setObject_Text(addonNamespaceTable, object, locTable, text, textLanguageId)
    return true
end

--use the locTable is valid or set the text using 'defaultText'
--@object: any UIObject or table with SetText method
--@locTable: a locTable created from CreateLocTable()
--@defaultText: a text string
function DF.Language.SetTextWithLocTableWithDefault(object, locTable, defaultText)
    if (not isValid_Object(object)) then
        error(functionCallPath["SetTextWithLocTableWithDefault"] .. ": " .. format(errorText["InvalidObject"], 1) .. ", use: " .. functionSignature["SetTextWithLocTableWithDefault"] .. ".")
    end

    if (not DF.Language.IsLocTable(locTable)) then
        object:SetText(defaultText or "")
    else
        DF.Language.SetTextWithLocTable(object, locTable)
    end
end

--if the second parameter is a regular string, the text set is the string, otherwise it'll handle the locTable and its parameters
--@object: any UIObject or table with SetText method
--@locTable: a locTable created from CreateLocTable()
function DF.Language.SetTextIfLocTableOrDefault(object, locTable)
    if (not isValid_Object(object)) then
        error(functionCallPath["SetTextIfLocTableOrDefault"] .. ": " .. format(errorText["InvalidObject"], 1) .. ", use: " .. functionSignature["SetTextIfLocTableOrDefault"] .. ".")
    end

    if (not DF.Language.IsLocTable(locTable)) then
        local textString = locTable
        object:SetText(textString)
    else
        DF.Language.SetTextWithLocTable(object, locTable)
    end
end

--@addonId: an identifier, can be any table or string, will be used when getting the table with phrase translations, example: "DetailsLocalization", "Details", "PlaterLoc", _G.Plater
--@parent: a frame to use as parent while creating the language selector dropdown
--@callback: a function which will be called when the user select a new language function(languageId) print("new language:", languageId) end
--@selectedLanguage: default selected language
function DF.Language.CreateLanguageSelector(addonId, parent, callback, selectedLanguage)
    if (not isValid_AddonID(addonId)) then
        error(functionCallPath["CreateLanguageSelector"] .. ": " .. format(errorText["AddonID"], 1) .. ", use: " .. functionSignature["CreateLanguageSelector"] .. ".")
    end

    if (type(parent) ~= "table" or not parent.GetObjectType or not parent.CreateTexture) then
        error(functionCallPath["CreateLanguageSelector"] .. ": " .. format("Require a frame on #%2 argument", 2) .. ", use: " .. functionSignature["CreateLanguageSelector"] .. ".")
    end

    local addonNamespaceTable = getAddonNamespace(addonId)
    if (not addonNamespaceTable) then
        error(functionCallPath["CreateLanguageSelector"] .. ": " .. errorText["AddonIDInvalidOrNotRegistered"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
    end

    setLanguageChangedCallback(addonNamespaceTable, callback)

    local allLanguagesRegistered = getRegisteredLanguages(addonNamespaceTable)

    local onSelectLanguage = function(self, addonId, languageId)
        DF.Language.SetCurrentLanguage(addonId, languageId)
        C_Timer.After(0.5, function()
            self:Select(languageId)
        end)
    end

    ---build a table with the all languageIDs registered to show as options in the dropdown
    ---@return table
    local buildOptionsFunc = function()
        ---@type {value: string, label: string, onclick: function, font: string}
        local resultTable = {}

        for languageId in pairs(allLanguagesRegistered) do
            local languageIdInfo = languagesAvailable[languageId]
            if (not languageIdInfo) then
                --for debug
                print("DetailsFramework: languageId is registered but has no languageInfo:", languageId)
            else
                resultTable[#resultTable+1] = {value = languageId, label = languageIdInfo.text, onclick = onSelectLanguage, font = languageIdInfo.font}
            end
        end

        return resultTable
    end

    local languageSelector = DF:CreateDropDown(parent, buildOptionsFunc, selectedLanguage or getCurrentLanguageId(addonNamespaceTable), 120, 20, nil, nil, DF:GetTemplate("dropdown", "OPTIONS_DROPDOWN_TEMPLATE"))
    languageSelector:SetAddonID(addonId)
    languageSelector:SetFixedParameter(addonId)

    local languageLabel = DF:CreateLabel(parent, _G.LANGUAGE  .. ":", 10, "silver")
    languageLabel:SetPoint("right", languageSelector, "left", -3, 0)
    languageSelector.languageLabel = languageLabel

    return languageSelector
end


---return a font (path for a file) which works for the languageId passed, if the languageId is not registered, it'll return a compatible font registered by another addon or the default font
---@param languageId string
---@param addonId string|nil
---@return string
function DF.Language.GetFontForLanguageID(languageId, addonId)
    if (addonId) then
        ---@type addonNamespaceTable
        local addonNamespaceTable = getAddonNamespace(addonId)
        if (not addonNamespaceTable) then
            error(functionCallPath["CreateLanguageSelector"] .. ": " .. errorText["AddonIDInvalidOrNotRegistered"] .. ", use: " .. functionSignature["RegisterLanguage"] .. ".")
        end

        --get the font from the addon namespace table
        ---@type fontPath|nil
        local fontPath = getFontForLanguageIdFromAddonNamespace(addonNamespaceTable, languageId)
        if (fontPath) then
            return fontPath
        end
    end

    local languageIdInfo = languagesAvailable[languageId]
    if (languageIdInfo) then
        return languageIdInfo.font
    end
    return "Fonts\\FRIZQT__.TTF"
end