---@class l10n ---@field continentLookup table ---@field zoneLookup table ---@field zoneCategoryLookup table ---@field questCategoryLookup table local l10n = QuestieLoader:CreateModule("l10n") local _l10n = {} l10n.translations = {} l10n.itemLookup = {} l10n.npcNameLookup = {} l10n.objectNameLookup = {} l10n.objectLookup = {} l10n.questLookup = {} l10n.translationCache = {} ---@type QuestieDB local QuestieDB = QuestieLoader:ImportModule("QuestieDB") local locale = 'enUS' local supportedLocals = { ['enUS'] = true, ['esES'] = true, ['esMX'] = true, ['ptBR'] = true, ['frFR'] = true, ['deDE'] = true, ['ruRU'] = true, ['zhCN'] = true, ['zhTW'] = true, ['koKR'] = true, } local function ResetTranslationCache() l10n.translationCache = {} end function l10n:InitializeLocaleOverride() local overridingLocale = QUESTIE_LOCALES_OVERRIDE.locale supportedLocals[overridingLocale] = true l10n.itemLookup[overridingLocale] = QUESTIE_LOCALES_OVERRIDE.itemLookup l10n.questLookup[overridingLocale] = QUESTIE_LOCALES_OVERRIDE.questLookup l10n.npcNameLookup[overridingLocale] = QUESTIE_LOCALES_OVERRIDE.npcNameLookup l10n.objectLookup[overridingLocale] = QUESTIE_LOCALES_OVERRIDE.objectLookup for id, _ in pairs(l10n.translations) do if QUESTIE_LOCALES_OVERRIDE.translations[id] ~= nil then l10n.translations[id][overridingLocale] = QUESTIE_LOCALES_OVERRIDE.translations[id] else l10n.translations[id][overridingLocale] = false end end ResetTranslationCache() end ---@param zoneName string ---@return number function l10n:GetAreaIdByLocalName(zoneName) if not zoneName or zoneName == "" then return 0 end for _, zoneTable in pairs(l10n.zoneLookup) do for areaId, name in pairs(zoneTable) do if name == zoneName then return areaId end end end return 0 end ---@param areaId number ---@return string function l10n:GetLocalNameByAreaId(areaId) if not areaId or areaId <= 0 then return l10n("Unknown Zone") end for _, zoneTable in pairs(l10n.zoneLookup) do if zoneTable[areaId] then return zoneTable[areaId] end end return l10n("Unknown Zone") end ---@param areaId number ---@return number function l10n:GetContinentIdByAreaId(areaId) if not areaId or areaId <= 0 then return 0 end for continentId, zoneTable in pairs(l10n.zoneLookup) do if zoneTable[areaId] then return continentId end end return 0 end function l10n:Initialize() -- Load item locales if l10n.itemLookup and l10n.itemLookup[locale] then for id, name in pairs(l10n.itemLookup[locale]) do if QuestieDB.itemData[id] and name then QuestieDB.itemData[id][QuestieDB.itemKeys.name] = name end end end -- data is {, {,...}, {,...}} -- Load quest locales if l10n.questLookup and l10n.questLookup[locale] then for id, data in pairs(l10n.questLookup[locale]) do if QuestieDB.questData[id] then if data[1] then QuestieDB.questData[id][QuestieDB.questKeys.name] = data[1] end -- TODO add details text to questDB.lua (data[2]) if data[3] then -- needs to be saved as a table for tooltips to have lines if type(data[3]) == "string" then QuestieDB.questData[id][QuestieDB.questKeys.objectivesText] = {data[3]} else QuestieDB.questData[id][QuestieDB.questKeys.objectivesText] = data[3] end end end end end -- Load NPC locales if l10n.npcNameLookup and l10n.npcNameLookup[locale] then for id, data in pairs(l10n.npcNameLookup[locale]) do if QuestieDB.npcData[id] and data then if type(data) == "string" then QuestieDB.npcData[id][QuestieDB.npcKeys.name] = data else QuestieDB.npcData[id][QuestieDB.npcKeys.name] = data[1] QuestieDB.npcData[id][QuestieDB.npcKeys.subName] = data[2] end end end end -- Load object locales if l10n.objectLookup and l10n.objectLookup[locale] then for id, name in pairs(l10n.objectLookup[locale]) do if QuestieDB.objectData[id] and name then QuestieDB.objectData[id][QuestieDB.objectKeys.name] = name end end end end --Must be run in a coroutine as it yields function l10n:PostBoot() local count = 0 -- Create {['name'] = {ID, },} table for lookup of possible object IDs by name if not QuestieDB.ObjectPointers then return end for id in pairs(QuestieDB.ObjectPointers) do local name = QuestieDB.QueryObjectSingle(id, "name") if name then -- We (meaning me, BreakBB) introduced Fake IDs for objects to show additional locations, so we need to check this local entry = l10n.objectNameLookup[name] if not entry then l10n.objectNameLookup[name] = { id } else entry[#entry+1] = id end end if count > 300 then count = 0 coroutine.yield() end count = count + 1 end end local format, unpack, tostring = string.format, unpack, tostring -- Safe wrapper around string.format that never errors. string.format throws -- "invalid option 'X' to 'format'" when the format string contains a stray % -- followed by a non-format character, or when the caller's args don't match -- the placeholders. l10n callers pass user-controlled data (quest names, -- npc names, error messages from xpcall) that can contain %X edge cases; -- crashing the chat with a Lua error for a missing translation is much worse -- than showing a slightly ugly fallback string. See user report: 8x -- l10n.lua:181 "invalid option in 'format'" during initial quest log sync. local function safeFormat(fmt, args) if not args or #args == 0 then -- No args, so just return the format string as-is (no format parsing -- needed). This handles trailing-% edge cases too. return fmt end local ok, result = pcall(format, fmt, unpack(args)) if ok then return result end -- Fallback: concatenate the format string with the args separated by spaces. -- This loses the original formatting intent but never crashes the user. return fmt .. " " .. table.concat(args, " ") end function _l10n:translate(key, ...) if key == nil then return "" end key = tostring(key) local argCount = select("#", ...) if argCount == 0 then local localeCache = l10n.translationCache[locale] if localeCache and localeCache[key] ~= nil then return localeCache[key] end if not localeCache then localeCache = {} l10n.translationCache[locale] = localeCache end local translationEntry = l10n.translations[key] if not translationEntry then if (Questie.db.profile.debugEnabled) then Questie:Debug(Questie.DEBUG_ELEVATED, "ERROR: Translations for '" .. tostring(key) .. "' are missing completely!") end localeCache[key] = key return key end local translationValue = translationEntry[locale] if (not translationValue) then if (Questie.db.profile.debugEnabled) then Questie:Debug(Questie.DEBUG_ELEVATED, "ERROR: Translations for '" .. tostring(key) .. "' are missing the entry for language" , locale, "!") end localeCache[key] = key return key end if translationValue == true then -- Fallback to enUS which is the key localeCache[key] = key return key end if type(translationValue) ~= "string" then if (Questie.db.profile.debugEnabled) then Questie:Debug(Questie.DEBUG_ELEVATED, "ERROR: Translation for '" .. tostring(key) .. "' is not a string!") end localeCache[key] = key return key end localeCache[key] = translationValue return translationValue end local args = {...} for i, v in ipairs(args) do args[i] = tostring(v); end local translationEntry = l10n.translations[key] if not translationEntry then if (Questie.db.profile.debugEnabled) then Questie:Debug(Questie.DEBUG_ELEVATED, "ERROR: Translations for '" .. tostring(key) .. "' are missing completely!") end return safeFormat(key, args) end local translationValue = translationEntry[locale] if (not translationValue) then if (Questie.db.profile.debugEnabled) then Questie:Debug(Questie.DEBUG_ELEVATED, "ERROR: Translations for '" .. tostring(key) .. "' are missing the entry for language" , locale, "!") end return safeFormat(key, args) end if translationValue == true then -- Fallback to enUS which is the key return safeFormat(key, args) end if type(translationValue) ~= "string" then if (Questie.db.profile.debugEnabled) then Questie:Debug(Questie.DEBUG_ELEVATED, "ERROR: Translation for '" .. tostring(key) .. "' is not a string!") end return safeFormat(key, args) end if #args == 0 then return translationValue end return safeFormat(translationValue, args) end setmetatable(l10n, { __call = function(_, ...) return _l10n:translate(...) end}) function _l10n:GetFallbackLocale(lang) if (not lang) then return 'enUS' end if supportedLocals[lang] then return lang elseif lang == 'enGB' then return 'enUS' elseif lang == 'enCN' then return 'zhCN' elseif lang == 'enTW' then return 'zhTW' elseif lang == 'esMX' then return 'esES' elseif lang == 'ptPT' then return 'ptBR' else return 'enUS' end end function l10n:SetUILocale(lang) if lang then locale = _l10n:GetFallbackLocale(lang) else locale = _l10n:GetFallbackLocale(GetLocale()) end ResetTranslationCache() end function l10n:GetUILocale() return locale end