diff --git a/.generate_database/.gitignore b/.generate_database/.gitignore
index 87b5aa52..46131a86 100644
--- a/.generate_database/.gitignore
+++ b/.generate_database/.gitignore
@@ -1,4 +1,5 @@
__pycache__
_data
*.lua-table
-*.log
\ No newline at end of file
+*.log
+Translations
\ No newline at end of file
diff --git a/.generate_database_lua/Dockerfile b/.generate_database_lua/Dockerfile
new file mode 100644
index 00000000..a20ef736
--- /dev/null
+++ b/.generate_database_lua/Dockerfile
@@ -0,0 +1,13 @@
+FROM nickblah/lua:5.1-luarocks
+
+RUN apt-get update && apt-get install -y git gcc
+# Install OpenSSL for LuaSec
+RUN apt-get install -y libssl-dev
+
+RUN luarocks install bit32
+RUN luarocks install argparse
+RUN luarocks install luafilesystem
+RUN luarocks install luasocket
+RUN luarocks install luasec
+
+RUN apt-get update && apt-get install -y wget
\ No newline at end of file
diff --git a/.generate_database_lua/createStatic.lua b/.generate_database_lua/createStatic.lua
new file mode 100644
index 00000000..fc905bc4
--- /dev/null
+++ b/.generate_database_lua/createStatic.lua
@@ -0,0 +1,170 @@
+-- Allow accessing private fields
+---@diagnostic disable: invisible
+require("cli.dump")
+local argparse = require("argparse")
+local helpers = require(".helpers")
+
+require("cli.Addon_Meta")
+require("cli.CLI_Helpers")
+
+assert(Is_CLI, "This function should only be called from the CLI environment")
+
+local f = string.format
+
+Is_Create_Static = true
+
+function DumpDatabase(version)
+ local lowerVersion = version:lower()
+ local capitalizedVersion = lowerVersion:gsub("^%l", string.upper)
+ print(f("\n\27[36mCompiling %s database...\27[0m", capitalizedVersion))
+
+ -- Reset data objects, load the files and set wow version
+ LibQuestieDBTable = AddonInitializeVersion(capitalizedVersion)
+
+ -- Drain all the timers
+ C_Timer.drainTimerList()
+
+ local itemOverride = {}
+ local npcOverride = {}
+ local objectOverride = {}
+ local questOverride = {}
+
+ local Corrections = LibQuestieDBTable.Corrections
+
+ Corrections.DumpFunctions.testDumpFunctions()
+
+ do
+ CLI_Helpers.loadFile(f(".generate_database/_data/%sItemDB.lua", lowerVersion))
+ itemOverride = loadstring(QuestieDB.itemData)()
+ LibQuestieDBTable.Item.LoadOverrideData(false, true)
+ local itemMeta = Corrections.ItemMeta
+ for itemId, corrections in pairs(LibQuestieDBTable.Item.override) do
+ if not itemOverride[itemId] then
+ itemOverride[itemId] = {}
+ end
+ for key, correction in pairs(corrections) do
+ local correctionIndex = itemMeta.itemKeys[key]
+ itemOverride[itemId][correctionIndex] = correction
+ end
+ end
+ end
+
+ do
+ CLI_Helpers.loadFile(f(".generate_database/_data/%sNpcDB.lua", lowerVersion))
+ npcOverride = loadstring(QuestieDB.npcData)()
+ LibQuestieDBTable.Npc.LoadOverrideData(false, true)
+ local npcMeta = Corrections.NpcMeta
+ for npcId, corrections in pairs(LibQuestieDBTable.Npc.override) do
+ if not npcOverride[npcId] then
+ npcOverride[npcId] = {}
+ end
+ for key, correction in pairs(corrections) do
+ local correctionIndex = npcMeta.npcKeys[key]
+ npcOverride[npcId][correctionIndex] = correction
+ end
+ end
+ end
+
+ do
+ CLI_Helpers.loadFile(f(".generate_database/_data/%sObjectDB.lua", lowerVersion))
+ objectOverride = loadstring(QuestieDB.objectData)()
+ LibQuestieDBTable.Object.LoadOverrideData(false, true)
+ local objectMeta = Corrections.ObjectMeta
+ for objectId, corrections in pairs(LibQuestieDBTable.Object.override) do
+ if not objectOverride[objectId] then
+ objectOverride[objectId] = {}
+ end
+ for key, correction in pairs(corrections) do
+ local correctionIndex = objectMeta.objectKeys[key]
+ objectOverride[objectId][correctionIndex] = correction
+ end
+ end
+ end
+
+ do
+ CLI_Helpers.loadFile(f(".generate_database/_data/%sQuestDB.lua", lowerVersion))
+ questOverride = loadstring(QuestieDB.questData)()
+ LibQuestieDBTable.Quest.LoadOverrideData(false, true)
+ local questMeta = Corrections.QuestMeta
+ for questId, corrections in pairs(LibQuestieDBTable.Quest.override) do
+ if not questOverride[questId] then
+ questOverride[questId] = {}
+ end
+ for key, correction in pairs(corrections) do
+ local correctionIndex = questMeta.questKeys[key]
+ questOverride[questId][correctionIndex] = correction
+ end
+ end
+ end
+
+ -- Write all the overrides to disk
+ ---@diagnostic disable-next-line: param-type-mismatch
+ -- local file = io.open(f(".generate_database/_data/output/Item/%s/ItemData.lua-table", capitalizedVersion), "w")
+ print("Dumping item overrides")
+ -- assert(file, "Failed to open file for writing")
+ local itemData = helpers.dumpData(itemOverride, Corrections.ItemMeta.itemKeys, Corrections.ItemMeta.dumpFuncs, Corrections.ItemMeta.combine)
+ ---@diagnostic disable-next-line: undefined-field
+ -- file:write(itemData)
+ ---@diagnostic disable-next-line: undefined-field
+ -- file:close()
+
+ -- ---@diagnostic disable-next-line: param-type-mismatch
+ -- file = io.open(f(".generate_database/_data/output/Quest/%s/QuestData.lua-table", capitalizedVersion), "w")
+ -- print("Dumping quest overrides")
+ -- assert(file, "Failed to open file for writing")
+ -- local questData = helpers.dumpData(questOverride, Corrections.QuestMeta.questKeys, Corrections.QuestMeta.dumpFuncs)
+ -- ---@diagnostic disable-next-line: undefined-field
+ -- file:write(questData)
+ -- ---@diagnostic disable-next-line: undefined-field
+ -- file:close()
+
+ -- ---@diagnostic disable-next-line: param-type-mismatch
+ -- file = io.open(f(".generate_database/_data/output/Npc/%s/NpcData.lua-table", capitalizedVersion), "w")
+ -- print("Dumping npc overrides")
+ -- assert(file, "Failed to open file for writing")
+ -- local npcData = helpers.dumpData(npcOverride, Corrections.NpcMeta.npcKeys, Corrections.NpcMeta.dumpFuncs, Corrections.NpcMeta.combine)
+ -- ---@diagnostic disable-next-line: undefined-field
+ -- file:write(npcData)
+ -- ---@diagnostic disable-next-line: undefined-field
+ -- file:close()
+
+ -- ---@diagnostic disable-next-line: param-type-mismatch
+ -- file = io.open(f(".generate_database/_data/output/Object/%s/ObjectData.lua-table", capitalizedVersion), "w")
+ -- print("Dumping object overrides")
+ -- assert(file, "Failed to open file for writing")
+ -- local objectData = helpers.dumpData(objectOverride, Corrections.ObjectMeta.objectKeys, Corrections.ObjectMeta.dumpFuncs)
+ -- ---@diagnostic disable-next-line: undefined-field
+ -- file:write(objectData)
+ -- ---@diagnostic disable-next-line: undefined-field
+ -- file:close()
+
+ print(f("\n\27[32m%s corrections dumped successfully\27[0m", capitalizedVersion))
+end
+
+-- local validVersions = {
+-- ["era"] = true,
+-- ["tbc"] = true,
+-- ["wotlk"] = true,
+-- }
+-- local versionString = ""
+-- for version in pairs(validVersions) do
+-- local v = string.gsub(version, "^%l", string.upper)
+-- versionString = versionString .. v .. "/"
+-- end
+-- -- Add all
+-- versionString = versionString .. "All"
+
+-- local parser = argparse("createStatic", "createStatic.lua Era")
+-- parser:argument("version", f("Game version, %s.", versionString))
+
+-- local args = parser:parse()
+
+-- if args.version and validVersions[args.version:lower()] then
+-- DumpDatabase(args.version)
+-- elseif args.version and args.version:lower() == "all" then
+-- for version in pairs(validVersions) do
+-- DumpDatabase(version)
+-- end
+-- else
+-- print("No version specified")
+-- end
diff --git a/.generate_database_lua/docker-compose.yml b/.generate_database_lua/docker-compose.yml
new file mode 100644
index 00000000..e3bb49d5
--- /dev/null
+++ b/.generate_database_lua/docker-compose.yml
@@ -0,0 +1,9 @@
+version: '3'
+
+services:
+ lua_2:
+ #image: nickblah/lua:5.1-luarocks
+ build: .
+ command: sh /QuestieDB/.generate_database_lua/run.sh
+ volumes:
+ - '../:/QuestieDB'
diff --git a/.generate_database_lua/generate_translation_trie.lua b/.generate_database_lua/generate_translation_trie.lua
new file mode 100644
index 00000000..a852747b
--- /dev/null
+++ b/.generate_database_lua/generate_translation_trie.lua
@@ -0,0 +1,183 @@
+require("cli.dump")
+
+-- Define the maximum number of translations per file
+local MAX_TRANSLATIONS_PER_FILE = 35
+local SEGMENT_SIZE = 4000
+local REDUCE_SEGMENT_SIZE = math.max(math.min(SEGMENT_SIZE * 0.05, 100), 10)
+print("Max translations per file: " .. MAX_TRANSLATIONS_PER_FILE)
+print("Segment size: " .. SEGMENT_SIZE, "Reduced segment size: " .. SEGMENT_SIZE - REDUCE_SEGMENT_SIZE)
+
+-- Function to sanitize translation strings by replacing special characters with HTML entities
+local function sanitize_translation(str)
+ str = string.gsub(str, "&", "&")
+ str = string.gsub(str, "<", "<")
+ str = string.gsub(str, ">", ">")
+ return str
+end
+
+-- Function to create directories recursively using os.execute
+local function mkdir(path)
+ os.execute("mkdir -p " .. path)
+end
+
+-- Function to split a string into segments based on maximum characters per segment
+local function split_into_segments(str, max_chars)
+ local segments = {}
+ local total_segments = math.ceil(#str / max_chars)
+
+ -- Loop through the string and create segments
+ for i = 1, total_segments do
+ local start_pos = (i - 1) * max_chars + 1
+ local end_pos = math.min(i * max_chars, #str)
+ local segment = string.sub(str, start_pos, end_pos)
+ -- Add segment number prefix for all segments except the first one
+ if i > 1 then
+ segment = tostring(i) .. segment
+ end
+ table.insert(segments, segment)
+ end
+
+ -- Add total segments count to the first segment
+ segments[1] = total_segments .. segments[1]
+
+ return segments
+end
+
+-- Function to write translations to an HTML file with segmentation
+local function write_html_file(key_path, translations)
+ -- Define the file path
+ local filename = key_path .. ".html"
+
+ -- Open the file for writing
+ local file, err = io.open(filename, "w")
+ if not file then
+ print("Error opening file " .. filename .. ": " .. err)
+ return
+ end
+
+ -- Write the HTML structure
+ file:write("
\n")
+ for _, translation in ipairs(translations) do
+ -- Check if the translation needs to be segmented
+ if #translation > SEGMENT_SIZE - REDUCE_SEGMENT_SIZE then
+ print("Splitting translation into segments", translation)
+ -- Split the translation into segments
+ local segments = split_into_segments(translation, SEGMENT_SIZE - REDUCE_SEGMENT_SIZE)
+ -- Write each segment as a separate paragraph
+ for _, segment in ipairs(segments) do
+ file:write("" .. sanitize_translation(segment) .. "
\n")
+ end
+ else
+ -- Write the translation as a single paragraph
+ file:write("" .. sanitize_translation(translation) .. "
\n")
+ end
+ end
+ file:write("\n")
+ file:close()
+end
+
+-- Function to create a branch in the trie structure
+local function create_branch(strings, stringIndex)
+ local branch = {}
+ -- Process each string in the input array
+ for i = 1, #strings do
+ local string = strings[i]
+ local cleanedString = string.gsub(string, "%s", "")
+ -- Remove all numbers from the string
+ -- cleanedString = string.gsub(cleanedString, "%d+", "")
+ cleanedString = string.gsub(cleanedString, "%p", "")
+ cleanedString = string.gsub(cleanedString, "%c", "")
+
+ -- Get the character at the current index
+ -- local char = string.sub(string.lower(cleanedString), stringIndex, stringIndex)
+ local char = string.sub(cleanedString, stringIndex, stringIndex)
+
+ if char == "" then
+ error(string.format("%s: %d out of range, increase MAX_TRANSLATIONS_PER_FILE", string, stringIndex))
+ end
+
+ -- Create a new branch for the character if it doesn't exist
+ if not branch[char] then
+ branch[char] = {}
+ end
+ table.insert(branch[char], string)
+ end
+
+ -- Recursively create branches for child nodes if needed
+ for char, child in pairs(branch) do
+ if #child > MAX_TRANSLATIONS_PER_FILE then
+ branch[char] = create_branch(child, stringIndex + 1)
+ end
+ end
+
+ return branch
+end
+
+-- Function to create trie folders and write translations to HTML files
+local function create_trie_folders(trie, current_path)
+ print("Current path: " .. current_path)
+ for key, value in pairs(trie) do
+ local new_path = current_path .. "/" .. key
+ if type(value) == "table" then
+ -- If the number of translations is greater than the maximum or there are no translations
+ if #value > MAX_TRANSLATIONS_PER_FILE or #value == 0 then
+ mkdir(new_path)
+ -- print("Continuing recursion for: " .. new_path)
+ create_trie_folders(value, new_path)
+ else
+ print("Writing HTML file for: " .. new_path)
+ write_html_file(new_path, value)
+ end
+ end
+ end
+end
+
+-- Main function to compile translations to HTML
+function Compile_translations_to_html(strings)
+ -- Initialize the trie
+ local success, trie
+ repeat
+ success, trie = pcall(create_branch, strings, 1)
+ if not success then
+ MAX_TRANSLATIONS_PER_FILE = MAX_TRANSLATIONS_PER_FILE + 1
+ print(trie)
+ print("Shortest word does not fit! - increasing MAX_TRANSLATIONS_PER_FILE to: " .. MAX_TRANSLATIONS_PER_FILE)
+ end
+ until success
+
+ -- Create the root folder
+ local root_folder = "translations"
+ mkdir(root_folder)
+
+ -- Create trie folders and write translations
+ create_trie_folders(trie, root_folder)
+
+ -- DevTools_Dump(trie)
+ -- Dump the trie to a lua file
+ -- local lines = {}
+ local function dump_trie(trie, indent)
+ local lines = {}
+ local indent_str = string.rep(" ", indent)
+ if type(trie) == "table" then
+ for char, value in pairs(trie) do
+ if type(value) == "table" then
+ table.insert(lines, indent_str .. "['" .. char .. "'] = {")
+ table.insert(lines, dump_trie(value, indent + 1))
+ table.insert(lines, indent_str .. "},")
+ else
+ table.insert(lines, indent_str .. "\"" .. value .. "\",")
+ end
+ end
+ else
+ table.insert(lines, indent_str .. "[\"" .. trie .. "\"]")
+ end
+ return table.concat(lines, "\n")
+ end
+
+ local lua_file = io.open(root_folder .. "/trie.lua", "w")
+ if lua_file ~= nil then
+ local dump_str = dump_trie(trie, 1)
+ lua_file:write("local trie = {\n" .. dump_str .. "\n}")
+ lua_file:close()
+ end
+end
diff --git a/.generate_database_lua/generate_translation_trie_root.lua b/.generate_database_lua/generate_translation_trie_root.lua
new file mode 100644
index 00000000..93d78ae3
--- /dev/null
+++ b/.generate_database_lua/generate_translation_trie_root.lua
@@ -0,0 +1,359 @@
+require("cli.dump")
+
+---@alias trie table
+---@alias filepath string
+
+-- The root folder
+local root_folder = "Translations"
+local data_folder = "_data"
+local full_path = root_folder .. "/" .. data_folder
+
+-- Variable to disable the writing of html
+local write_html = true
+
+-- Double Dagger
+local splitCharacter = "‡"
+
+-- Define the maximum number of translations per file
+local MAX_TRANSLATIONS_PER_FILE = 50
+local SEGMENT_SIZE = 4000
+local REDUCE_SEGMENT_SIZE = math.max(math.min(SEGMENT_SIZE * 0.05, 100), 10)
+
+local f = string.format
+
+-- From here
+-- https://github.com/Questie/Questie/blob/2e2c44dc42dd66fb144be8ba0115287da5b7cd8e/Localization/l10n.lua#L22
+local localeOrder = {
+ 'enUS',
+ 'esES',
+ 'esMX',
+ 'ptBR',
+ 'frFR',
+ 'deDE',
+ 'ruRU',
+ 'zhCN',
+ 'zhTW',
+ 'koKR',
+}
+
+print("Max translations per file: " .. MAX_TRANSLATIONS_PER_FILE)
+print("Segment size: " .. SEGMENT_SIZE, "Reduced segment size: " .. SEGMENT_SIZE - REDUCE_SEGMENT_SIZE)
+
+local tConcat = table.concat
+local tInsert = table.insert
+
+--- Function to sanitize translation strings by replacing special characters with HTML entities
+---@param str string
+---@return string
+local function sanitize_translation(str)
+ str = string.gsub(str, "&", "&")
+ str = string.gsub(str, "<", "<")
+ str = string.gsub(str, ">", ">")
+ -- str = string.gsub(str, "\n", "
")
+ -- str = string.gsub(str, '"', '\\"')
+ return str
+end
+
+--- Function to create directories recursively using os.execute
+---@param path filepath
+local function mkdir(path)
+ os.execute("mkdir -p " .. path)
+end
+
+--- Function to split a table of strings into segments based on maximum characters per segment
+---@param tbl string[] The table of strings to split
+---@param max_chars number The maximum number of characters per segment
+---@return string[] A table of segments, each being a table of strings
+local function split_into_segments(tbl, max_chars)
+ local segments = {}
+ local current_segment = {}
+
+ -- Is the total length too long?
+ if #tConcat(tbl, splitCharacter) > max_chars then
+ local concat_current_segments = tConcat(current_segment, splitCharacter)
+ for _, segment in ipairs(tbl) do
+ -- Will the current length + the next one be over max_chars?
+ if #concat_current_segments + #segment > max_chars then
+ -- Insert a segment and start a new one
+ tInsert(segments, concat_current_segments)
+ current_segment = { segment, }
+ else
+ -- We are below the max_chars limit, so we can add the next string to the current segment
+ tInsert(current_segment, segment)
+ end
+ end
+ -- Insert the last segment
+ tInsert(segments, concat_current_segments)
+ end
+
+ -- Iterate over each segment and write it as a separate paragraph
+ local ret_segments = {}
+ for idx, segment in ipairs(segments) do
+ -- If it's the first segment, prepend the total segments count
+ if idx == 1 and #segments > 0 then
+ segment = tostring(#segments) .. segment
+ elseif idx > 1 then
+ segment = tostring(idx) .. segment
+ end
+ tInsert(ret_segments, segment)
+ end
+
+ return ret_segments
+end
+
+--- Function to write translations to an HTML file with segmentation
+---@param key_path string The path for the translation e.g. translations/u/s/e/t/h/e
+---@param translations string[]
+---@param translation_func fun(string):string[], string A function that takes the english string and returns all translations
+local function write_html_file(key_path, translations, translation_func)
+ -- ? This stops the function from writing in subfolders but instead writes in the root folder
+ -- ? Remember to activate the folder creation function in the create_trie_folders function if disabled
+ -- "%-" Always escape
+ local replaceChar = ""
+ key_path = key_path:gsub("/", replaceChar)
+ key_path = key_path:gsub(full_path:gsub("/", replaceChar) .. replaceChar, full_path .. "/", 1) -- We also removed the root folder from the key_path, add back the /
+
+ -- Define the file path
+ local filename = key_path .. ".html"
+
+ -- Open the file for writing
+ if write_html then
+ local file, err = io.open(filename, "w")
+ if not file then
+ print("Error opening file " .. filename .. ": " .. err)
+ return
+ end
+
+ -- Write the HTML structure
+ file:write("\n")
+ for _, translation in ipairs(translations) do
+ local fullTranslationTable, enUS = translation_func(translation)
+ file:write("\n")
+
+ if #tConcat(fullTranslationTable, splitCharacter) > SEGMENT_SIZE - REDUCE_SEGMENT_SIZE then
+ print("Splitting translation into segments", translation, filename)
+ -- Split the translation into segments
+ local segments = split_into_segments(fullTranslationTable, SEGMENT_SIZE - REDUCE_SEGMENT_SIZE)
+ for _, segment in ipairs(segments) do
+ file:write("" .. sanitize_translation(segment) .. "
\n")
+ end
+ else
+ -- Write the translation as a single paragraph
+ local concatenated = tConcat(fullTranslationTable, splitCharacter)
+ file:write("" .. sanitize_translation(concatenated) .. "
\n")
+ end
+ end
+ file:write("\n")
+ file:close()
+ end
+end
+
+-- Function to create trie folders and write translations to HTML files
+---comment
+---@param trie trie
+---@param current_path filepath
+---@param translation_func fun(translation:string):string[], string A function that takes the english string and returns all translations
+local function write_trie_structure(trie, current_path, translation_func)
+ -- print("Current path: " .. current_path)
+ for trieKey, translations in pairs(trie) do
+ local new_path = current_path .. "/" .. trieKey
+ if type(translations) == "table" then
+ -- If the number of translations is greater than the maximum or there are no translations
+ if #translations > MAX_TRANSLATIONS_PER_FILE or #translations == 0 then
+ -- ? This line activates the function to create folders for the current path, disabled due to easier to just have it flat.
+ -- * mkdir(new_path)
+
+ -- print("Continuing recursion for: " .. new_path)
+ write_trie_structure(translations, new_path, translation_func)
+ else
+ -- print("Writing HTML file for: " .. new_path)
+ write_html_file(new_path, translations, translation_func)
+ end
+ end
+ end
+end
+
+--- Function to create a branch in the trie structure
+---@param strings string[]
+---@param stringIndex number @ The index of the current character in the string
+---@return trie
+local function create_trie(strings, stringIndex)
+ local branch = {}
+ -- Process each string in the input array
+ for i = 1, #strings do
+ local string = strings[i]
+ -- Remove all whitespaces from the string
+ local cleanedString = string.gsub(string, "%s", "")
+ -- Remove all numbers from the string (Not in use as some strings are very short and contains numbers)
+ -- cleanedString = string.gsub(cleanedString, "%d+", "")
+ -- Remove all punctuation from the string
+ cleanedString = string.gsub(cleanedString, "%p", "")
+ -- Remove all control characters from the string
+ cleanedString = string.gsub(cleanedString, "%c", "")
+
+ -- Get the character at the current index
+ -- local char = string.sub(string.lower(cleanedString), stringIndex, stringIndex)
+ local char = string.sub(cleanedString, stringIndex, stringIndex)
+
+ if char == "" then
+ -- error(f("%s: %d out of range, increase MAX_TRANSLATIONS_PER_FILE", string, stringIndex))
+ print(f("%s: %d out of range", string, stringIndex))
+ char = "."
+ -- tInsert(parent[char], string)
+ -- else
+ end
+ -- Create a new branch for the character if it doesn't exist
+ if not branch[char] then
+ branch[char] = {}
+ end
+ tInsert(branch[char], string)
+ end
+
+ -- Recursively create branches for child nodes if needed
+ for char, child in pairs(branch) do
+ if #child > MAX_TRANSLATIONS_PER_FILE then
+ branch[char] = create_trie(child, stringIndex + 1)
+ end
+ end
+
+ return branch
+end
+
+-- Function to check if a table is an array of strings
+local function isStringArray(t)
+ if type(t) ~= "table" then
+ return false
+ end
+ -- Check if the table is a sequence (array) with consecutive integer keys starting at 1
+ local index = 1
+ for k, v in pairs(t) do
+ if type(k) ~= "number" or k ~= index then
+ return false
+ end
+ if type(v) ~= "string" then
+ return false
+ end
+ index = index + 1
+ end
+ return true
+end
+
+-- Recursive function to traverse and modify the trie table
+local function replaceArrays(t, filepath)
+ for key, value in pairs(t) do
+ if type(value) == "table" then
+ if isStringArray(value) then
+ -- Replace the array of strings with the filepath
+ t[key] = filepath .. key .. ".html"
+ else
+ -- Recursively process nested tables
+ replaceArrays(value, filepath .. key)
+ end
+ end
+ end
+end
+
+-- Main function to compile translations to HTML
+---@param strings string[]
+---@param addonName string The folder name for the addon, it is for the path in the XML files
+---@param translation_func fun(string):table A function that takes the english string and returns all translations
+function Compile_translations_to_html(strings, addonName, translation_func)
+ -- Initialize the trie
+ local success, trie
+ repeat
+ success, trie = pcall(create_trie, strings, 1)
+ if not success then
+ MAX_TRANSLATIONS_PER_FILE = MAX_TRANSLATIONS_PER_FILE + 1
+ print(trie)
+ print("Shortest word does not fit! - increasing MAX_TRANSLATIONS_PER_FILE to: " .. MAX_TRANSLATIONS_PER_FILE)
+ end
+ until success
+
+ mkdir(root_folder)
+ mkdir(root_folder .. "/" .. data_folder)
+
+ ---@param enUStext string
+ ---@return string[]?
+ ---@return string enUS translation
+ -- -@return error?
+ local function getTranslation(enUStext)
+ local allTranslations, err = translation_func(enUStext)
+ if err then
+ return nil, err
+ end
+
+ local enUSTranslation = enUStext -- "enUS" .. "[" .. enUStext .. "]"
+ local combinedTranslations = {}
+ for _, locale in ipairs(localeOrder) do
+ local text = allTranslations[locale]
+ if type(text) == "string" and locale ~= "enUS" then
+ tInsert(combinedTranslations, locale .. "[" .. text .. "]")
+ elseif locale ~= "enUS" then
+ tInsert(combinedTranslations, "")
+ end
+ end
+ return combinedTranslations, enUSTranslation --tConcat(combinedTranslations, "‡")
+ end
+
+ -- Create trie folders and write translations
+ write_trie_structure(trie, full_path, getTranslation)
+
+ -- Replace the actual string arrays with the template xml name
+ replaceArrays(trie, "")
+
+ local allHTMLFiles = {}
+ -- Function to print the table for verification (optional)
+ -- Function to print the table for verification and collect HTML files
+ ---@param t table The table to print
+ ---@param indent string? The indentation string
+ ---@return string The formatted table string
+ local function printTable(t, indent)
+ -- Reset the HTML files if we are on the first step.
+ if indent == "" then
+ allHTMLFiles = {}
+ end
+ local lines = {}
+ indent = indent or ""
+ for k, v in pairs(t) do
+ if type(v) == "table" then
+ tInsert(lines, indent .. "[\"" .. tostring(k) .. "\"] = {")
+ tInsert(lines, printTable(v, indent .. " "))
+ tInsert(lines, indent .. "},")
+ else
+ -- if html in v use " otherwise it will be wrapped in [[]]
+ if string.find(v, ".html") then
+ tInsert(lines, indent .. "[\"" .. tostring(k) .. "\"] = \"" .. tostring(v) .. "\",")
+ tInsert(allHTMLFiles, v)
+ else
+ tInsert(lines, indent .. "[\"" .. tostring(k) .. "\"] = [[" .. tostring(v) .. "]],")
+ end
+ end
+ end
+ return tConcat(lines, "\n")
+ end
+
+ -- ? Generate the lookup file loaded in Lua
+ local lua_file = io.open(root_folder .. "/TranslationsLookup_gen.lua", "w")
+ if lua_file then
+ local dump_str = printTable(trie, "") --dump_trie(point_to_html(trie), 1)
+ lua_file:write("-- ! File generated by generate_translation_trie_root.lua --\n")
+ lua_file:write("-- ! DO NOT EDIT --\n")
+ lua_file:write("---@class LibQuestieDB\n")
+ lua_file:write("---@field translationsLookup table|string> Contains lookup for HTML files for translations\n")
+ lua_file:write("local LibQuestieDB = select(2, ...)\n")
+ lua_file:write("\n")
+ lua_file:write(f("LibQuestieDB.translationsLookup = {\n%s\n}", dump_str))
+ lua_file:close()
+ end
+
+ -- ? Generate the XML file that creates the virtual SimpleHTML objects
+ local fileString = '\n'
+ lua_file = io.open(root_folder .. "/TranslationsFiles_gen.xml", "w")
+ if lua_file then
+ lua_file:write('\n')
+ for _, htmlfile in pairs(allHTMLFiles) do
+ lua_file:write(f(fileString, htmlfile, addonName, data_folder, htmlfile))
+ end
+ lua_file:write('')
+ end
+end
diff --git a/.generate_database_lua/helpers.lua b/.generate_database_lua/helpers.lua
new file mode 100644
index 00000000..e2859716
--- /dev/null
+++ b/.generate_database_lua/helpers.lua
@@ -0,0 +1,222 @@
+-- helpers.lua
+local lfs = require("lfs")
+
+-- Require the necessary LuaSocket modules
+local http = require("socket.http")
+local https = require("ssl.https")
+local ltn12 = require("ltn12")
+
+--- Get the script directory.
+---@return string The directory containing the script.
+local function get_script_dir()
+ local script_path = debug.getinfo(1, "S").source:sub(2)
+ return script_path:match("(.*/)")
+end
+
+--- Get the project directory path.
+---@return string The project directory path.
+local function get_project_dir_path()
+ return get_script_dir() .. ".."
+end
+
+--- Get the data directory path for a given entity type and expansion.
+---@param entity_type string The type of entity (e.g., "Quest", "Item").
+---@param expansion string The expansion name (e.g., "Era", "Tbc", "Wotlk").
+---@return string The data directory path.
+local function get_data_dir_path(entity_type, expansion)
+ local path = get_project_dir_path() .. "/Database/" .. entity_type .. "/" .. expansion
+ -- Does path not exist, l10n has this issue...
+ if not lfs.attributes(path, "mode") then
+ print("Path " .. path .. " does not exist, trying lowercase")
+ path = get_project_dir_path() .. "/Database/" .. entity_type:lower() .. "/" .. expansion
+ end
+ return path
+end
+
+--- Find the addon name.
+---@return string The addon name.
+local function find_addon_name()
+ local current_dir = lfs.currentdir()
+ local previous_dir = nil
+ local addon_dir = nil
+ local max_level = 20
+ local level = 0
+
+ while level < max_level do
+ local dir_name = current_dir:match("([^/]+)$")
+ local parent_dir = current_dir:match("^(.*)/[^/]+$")
+ if type(dir_name) == "nil" then
+ addon_dir = previous_dir
+ break
+ elseif dir_name:lower() == "addons" and parent_dir:match("([^/]+)$"):lower() == "interface" then
+ addon_dir = previous_dir
+ break
+ else
+ previous_dir = current_dir
+ current_dir = parent_dir
+ level = level + 1
+ end
+ end
+
+ if not addon_dir then
+ print("Could not find the Addons folder, defaulting to 'QuestieDB'.")
+ return "QuestieDB"
+ else
+ print("Found Addons folder: " .. addon_dir)
+ end
+
+ -- Remove / or \ characters
+ addon_dir = addon_dir:gsub("[/\\]", "")
+
+ return addon_dir
+end
+
+--- Read expansion data from a Lua file.
+---@param expansion string The expansion name (e.g., "Era", "Tbc", "Wotlk").
+---@param entity_type string The type of entity (e.g., "Quest", "Item").
+---@return string|nil The content of the Lua file, or nil if not found.
+local function read_expansion_data(expansion, entity_type)
+ local path = get_data_dir_path(entity_type, expansion)
+ print("Reading " .. expansion .. " lua " .. entity_type:lower() .. " data from " .. path)
+ local file_path = path .. "/" .. entity_type:sub(1, 1):upper() .. entity_type:sub(2):lower() .. "Data.lua-table"
+
+ if not lfs.attributes(file_path, "mode") then
+ file_path = path .. "/" .. entity_type:lower() .. "Data.lua-table"
+ if not lfs.attributes(file_path, "mode") then
+ print("File not found: " .. file_path)
+ return nil
+ end
+ end
+
+ local file, err = io.open(file_path, "r")
+ if not file then
+ print("File not found: " .. path)
+ return nil
+ end
+
+ local data = file:read("*all")
+ file:close()
+
+ -- Perform any necessary replacements on the data string
+ data = data:gsub("&", "and"):gsub("<", "|"):gsub(">", "|")
+ return data
+end
+
+--- Download a raw text file from a URL and return its contents as a string.
+-- @param url string The URL of the text file to download.
+-- @return string|nil The contents of the text file, or nil if the download fails.
+local function download_text_file(url)
+ local response_body = {}
+ local res, code, response_headers = https.request {
+ url = url,
+ sink = ltn12.sink.table(response_body),
+ }
+
+ if res == 1 and code == 200 then
+ -- Concatenate the table into a single string
+ return table.concat(response_body)
+ else
+ print("Failed to download file: HTTP response code " .. tostring(code))
+ return nil
+ end
+end
+
+---Dumps the data for Item, Quest, Npc, Object into a string
+---@param tbl table> @ Table that will be dumped, Item, Quest, Npc, Object
+---@param dataKeys ItemDBKeys|QuestDBKeys|NpcDBKeys|ObjectDBKeys @ Contains name of data as keys and their index as value
+---@param dumpFunctions table @ Contains the functions that will be used to dump the data
+---@param combineFunc function? @ Function that will be used to combine the data, if nil the data will not be combined
+---@return string,table[] @ Returns the dumped data as a string and a table of the dumped data
+local function dumpData(tbl, dataKeys, dumpFunctions, combineFunc)
+ -- sort tbl by key
+ local sortedKeys = {}
+ for key in pairs(tbl) do
+ sortedKeys[#sortedKeys + 1] = key
+ end
+ table.sort(sortedKeys)
+
+ local nrDataKeys = 0
+ local reversedKeys = {}
+ for key, id in pairs(dataKeys) do
+ reversedKeys[id] = key
+ nrDataKeys = nrDataKeys + 1
+ end
+
+ local allResultsTbl = {}
+ local allResults = { "{\n", }
+ for sortKeyIndex = 1, #sortedKeys do
+ local sortKey = sortedKeys[sortKeyIndex]
+
+ -- print(sortKey)
+ local value = tbl[sortKey]
+
+ local resulttable = {}
+ for i = 1, nrDataKeys do
+ resulttable[i] = "nil"
+ end
+
+ for key = 1, nrDataKeys do
+ -- The name of the key e.g. "objectDrops"
+ local dataName = dataKeys[key] == nil and reversedKeys[key] or key
+ -- The id of the key e.g. "3"-(objectDrops)
+ local dataKey = type(key) == "number" and key or dataKeys[key]
+
+ -- print(key, dataName, dataKey)
+
+ -- Get the data from the table
+ local data = value[key]
+
+ -- Because we build it with nil we have to check for nil here, if the value is nil we just print nil
+ if data ~= "nil" and data ~= nil then
+ local dumpFunction = dumpFunctions[dataName]
+ if dumpFunction then
+ local dumpedData = dumpFunction(data)
+ resulttable[dataKey] = dumpedData
+ else
+ error("No dump function for key: " .. "dataName" .. " (" .. tostring(dataName) .. ")" .. " dataKey: " .. tostring(dataKey))
+ end
+ else
+ resulttable[dataKey] = "nil"
+ end
+ end
+ -- DevTools_Dump({resulttable})
+
+ -- If a combine funnction exist we run it here
+ assert(#resulttable == nrDataKeys, "resulttable length is not equal to dataKeys length, combine will fail")
+ if combineFunc then
+ combineFunc(resulttable)
+ end
+ -- DevTools_Dump({resulttable})
+
+ -- Concat the data into a string
+ local data = table.concat(resulttable, ",")
+
+ allResultsTbl[sortKey] = data
+
+ -- Remove trailing nil
+ repeat
+ local count = 0
+ data, count = string.gsub(data, ",nil$", "")
+ until count == 0
+
+ -- Add the data to the result
+ allResults[#allResults + 1] = " [" .. sortKey .. "] = {"
+ allResults[#allResults + 1] = data
+ allResults[#allResults + 1] = ",},\n"
+ end
+ allResults[#allResults + 1] = "}"
+ -- return result .. "}"
+ return table.concat(allResults), allResultsTbl
+end
+
+---@class helpers
+local return_table = {
+ get_project_dir_path = get_project_dir_path,
+ get_data_dir_path = get_data_dir_path,
+ find_addon_name = find_addon_name,
+ read_expansion_data = read_expansion_data,
+ dumpData = dumpData,
+ download_text_file = download_text_file,
+}
+-- Expose functions
+return return_table
diff --git a/.generate_database_lua/main.lua b/.generate_database_lua/main.lua
new file mode 100644
index 00000000..7e3d07d2
--- /dev/null
+++ b/.generate_database_lua/main.lua
@@ -0,0 +1,116 @@
+-- main.lua
+-- Prepend your script's directory to the package.path
+local script_path = debug.getinfo(1, "S").source:sub(2)
+local script_dir = script_path:match("(.*/)")
+package.path = script_dir .. "?.lua;" .. package.path
+
+local helpers = require("helpers")
+require("cli.CLI_Helpers")
+
+-- Main function to demonstrate the usage of helper functions
+local function main()
+ -- Get the project directory path
+ local project_dir = helpers.get_project_dir_path()
+ print("Project Directory Path: " .. project_dir)
+
+ -- Get the data directory path for a specific entity type and expansion
+ local entity_type = "Quest"
+ local expansion = "Wotlk"
+ local data_dir = helpers.get_data_dir_path(entity_type, expansion)
+ print("Data Directory Path for " .. entity_type .. " in " .. expansion .. ": " .. data_dir)
+
+ -- Find the addon name
+ local addon_name = helpers.find_addon_name()
+ print("Addon Name: " .. addon_name)
+
+ -- Read expansion data
+ local expansion_data = helpers.read_expansion_data(expansion, entity_type)
+ if expansion_data then
+ local lines = 4
+ local count = 0
+ for line in expansion_data:gmatch(" [^\n]+") do
+ print(line)
+ count = count + 1
+ if count >= lines then
+ break
+ end
+ end
+ else
+ print("No data found for " .. entity_type .. " in " .. expansion)
+ end
+
+ -- Download a file from a URL
+ local url = "https://raw.githubusercontent.com/Questie/Questie/master/Database/Classic/classicItemDB.lua"
+ local data = helpers.download_text_file(url)
+ if data then
+ print("Downloaded data from URL: ")
+ local lines = 4
+ local count = 0
+ for line in data:gmatch(" [^\n]+") do
+ print(line)
+ count = count + 1
+ if count >= lines then
+ break
+ end
+ end
+ else
+ print("Failed to download data from URL: " .. url)
+ end
+
+ -- Write data to a file
+ -- Write data to a file
+ local output_dir = ".generate_database/_data"
+ local output_file = output_dir .. "/eraItemDB.lua"
+
+ -- Write the data to the file
+ local file, err = io.open(output_file, "w")
+ if not file then
+ print("Error opening file for writing: " .. err)
+ else
+ file:write(data)
+ file:close()
+ print("Data written to file: " .. output_file)
+ end
+end
+
+
+require("cli.Addon_Meta")
+local translations = {}
+QuestieLoader = {
+ ImportModule = function()
+ return { translations = translations, }
+ end,
+}
+
+CLI_Helpers.loadTOC(".generate_database_lua/translations.toc")
+
+
+local single_translation = {}
+for key, value in pairs(translations) do
+ -- local translation = string.gsub(key, "\n", "
")
+ -- translation = string.gsub(translation, '"', '\\"')
+ table.insert(single_translation, key)
+end
+
+
+---comment
+---@param enUStext string
+---@return table?
+---@return error?
+local function getTranslation(enUStext)
+ if translations[enUStext] then
+ return translations[enUStext], nil
+ else
+ return nil, "Translation not found for: " .. enUStext
+ end
+end
+
+
+require("generate_translation_trie_root")
+-- Find the addon name
+local addon_name = helpers.find_addon_name()
+print("Addon Name: " .. addon_name)
+Compile_translations_to_html(single_translation, addon_name, getTranslation)
+
+-- Run the main function
+-- main()
diff --git a/.generate_database_lua/run.sh b/.generate_database_lua/run.sh
new file mode 100644
index 00000000..743969e3
--- /dev/null
+++ b/.generate_database_lua/run.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+echo "Current directory: $(pwd)"
+echo "$(which $1) is the lua executable"
+
+# First argument points to lua executable set "lua" if not set
+if [ -z "$1" ]; then
+ LUA=lua
+else
+ LUA=$1
+fi
+
+# Needed for the docker container but not action but it doesn't hurt the run if it fails
+cd /QuestieDB
+
+
+
+$LUA ./.generate_database_lua/main.lua
+# $LUA ./.generate_database_lua/generate_translation_trie.lua
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 991aacd7..a5868770 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,10 @@ Database/*/**/*.html
Database/*/*/*Data*.xml
Database/*/*/*Data*.html
+Translations/*_gen.xml
+Translations/*_gen.lua
+Translations/_data/*.html
+
.shit
.translator
@@ -54,4 +58,5 @@ Perfy
.build
-.llm-output
\ No newline at end of file
+.llm-output
+..wiki-information
\ No newline at end of file
diff --git a/.luarc.json b/.luarc.json
new file mode 100644
index 00000000..b5695012
--- /dev/null
+++ b/.luarc.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
+ // "workspace.library": ["path/to/library/directory"],
+ "workspace.ignoreDir":[
+ ".generate_database",
+ ".vscode",
+ ".translator",
+ ".wowhead",
+ "**/.git",
+ "**/.cache",
+ "**/_data/**",
+ ".llm-output",
+ ".build",
+ "Perfy",
+ ],
+ // "runtime.version": "Lua 5.3",
+ // "hint.enable": false
+}
+
diff --git a/.stylua.toml b/.stylua.toml
new file mode 100644
index 00000000..e9636bd0
--- /dev/null
+++ b/.stylua.toml
@@ -0,0 +1,6 @@
+column_width = 160
+line_endings = "Unix"
+indent_type = "Spaces"
+indent_width = 2
+quote_style = "AutoPreferDouble"
+call_parentheses = "None"
diff --git a/.vscode/launch.json b/.vscode/launch.json
index eade0158..4e15717c 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -12,7 +12,9 @@
"console": "internalConsole",
"justMyCode": true,
"cwd": "${workspaceFolder}/.translator",
- "args": []
+ "args": [
+ "Era"
+ ]
}
]
}
\ No newline at end of file
diff --git a/Corrections/Corrections.lua b/Corrections/Corrections.lua
index f00b8c1a..ff5d5888 100644
--- a/Corrections/Corrections.lua
+++ b/Corrections/Corrections.lua
@@ -169,7 +169,9 @@ do
dynamicCorrections[i] = correctionObject
end
end
-
+ -- TODO: How do i remove this and keep the possiblity to load new corrections?
+ -- Corrections[capitalizedTypeStatic] = nil
+ -- Corrections[capitalizedTypeDynamic] = nil
return {
dynamic = dynamicCorrections,
static = staticCorrections,
diff --git a/Corrections/DumpFunctions.lua b/Corrections/DumpFunctions.lua
index 2fe5d4d9..8c395a2b 100644
--- a/Corrections/DumpFunctions.lua
+++ b/Corrections/DumpFunctions.lua
@@ -29,6 +29,9 @@ end
local tblMaxIndex = DumpFunctions.tblMaxIndex
+---Counts the number of key-value pairs in the given table.
+---@param tbl table The table to count the number of pairs in.
+---@return number count The number of key-value pairs in the table.
function DumpFunctions.tblCount(tbl)
local count = 0
for _ in pairs(tbl) do
diff --git a/Corrections/Sod/base/sodBaseItems.lua b/Corrections/Sod/base/sodBaseItems.lua
index ebd891b3..f3218222 100644
--- a/Corrections/Sod/base/sodBaseItems.lua
+++ b/Corrections/Sod/base/sodBaseItems.lua
@@ -8,8 +8,8 @@ local ItemMeta = Corrections.ItemMeta
---@class ItemBaseSod
local ItemBase = {}
---? This is the "static" database for SOD, it always have to load first of all SOD fixes.
---? Sod should however always load last.
+--? This is the "static" database for SOD, out of ALL SoD fixes it should always load first.
+--? SoD as "expansion" should ALWAYS load last.
C_Timer.After(0, function()
if LibQuestieDB.IsSoD then
Corrections.RegisterCorrectionDynamic("item",
diff --git a/Database/Base/Base.lua b/Database/Base/Base.lua
index c829e432..d16576bc 100644
--- a/Database/Base/Base.lua
+++ b/Database/Base/Base.lua
@@ -53,8 +53,10 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType
---@type table>
local override = {}
- ---- Contains the id strings ----
- local AllIdStrings = {}
+ ---- Contains the id string ----
+ ---- Use GetAllIds function to get the ids
+ ---@type string
+ local AllIdStrings = ""
---- Add entity type to the database ----
Database.entityTypes[captializedType] = true
@@ -197,10 +199,15 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType
-- If there are new IDs, concatenate them into a string and add to `AllIdStrings` for tracking.
if #newIds ~= 0 then
- tInsert(AllIdStrings, tConcat(newIds, ","))
+ -- To get the right length we just print this message before adding the old string
if Database.debugPrintEnabled then
LibQuestieDB.ColorizePrint("lightBlue", f(" # New %s IDs", captializedType), #newIds)
end
+ -- Add the old ID string to the new list of ids
+ -- e.g { "1", "2", "3", "4,5,6" } -> "1,2,3,4,5,6"
+ ---@diagnostic disable-next-line: assign-type-mismatch
+ newIds[#newIds + 1] = AllIdStrings
+ AllIdStrings = tConcat(newIds, ",")
end
-- Apply the override data to the database and return the number of overrides applied.
@@ -212,10 +219,11 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType
-- It will however still initialize during the CLI testing phase.
if Is_Create_Static then return end
- wipe(AllIdStrings)
+ -- Reset the `AllIdStrings` variable to an empty string.
+ AllIdStrings = ""
local func, idString = Database.GetAllEntityIdsFunction(captializedType)
-- TODO: Maybe we should sort this list?
- tInsert(AllIdStrings, idString)
+ AllIdStrings = idString
if Database.debugPrintEnabled then
assert(#func() == #DB.GetAllIds(), f("%s ids are not the same", captializedType))
end
@@ -241,12 +249,12 @@ function LibQuestieDB.CreateDatabaseInTable(refTable, databaseType, databaseType
function DB.GetAllIds(hashmap)
if hashmap == true then
-- Substitute all numbers in the concatenated ID strings with Lua table format [number]=true
- local dat = gsub(tConcat(AllIdStrings, ","), "(%d+)", "[%1]=true")
+ local dat = gsub(AllIdStrings, "(%d+)", "[%1]=true")
-- Execute the string as Lua code to create and return the hashmap
return loadstring(f("return {%s}", dat))()
else
-- If hashmap is not requested, simply return a list of IDs
- return loadstring(f("return {%s}", tConcat(AllIdStrings, ",")))()
+ return loadstring(f("return {%s}", AllIdStrings))()
end
end
diff --git a/Database/Database.lua b/Database/Database.lua
index 8c280c7f..662aef97 100644
--- a/Database/Database.lua
+++ b/Database/Database.lua
@@ -46,21 +46,24 @@ local _nil = Database._nil
---- Local Functions ----
--* For performance reasons we check Is_CLI here, Database.CreateFrame supports both CLI and WOW
-local CreateFrame = Is_CLI and Database.CreateFrame or CreateFrame
-local frameType = "SimpleHTML"
+local CreateFrame = Is_CLI and Database.CreateFrame or CreateFrame
+local frameType = "SimpleHTML"
local strsplittable = strsplittable
-local tConcat = table.concat
-
-local tonumber = tonumber
-local loadstring = loadstring
-local gMatch = string.gmatch
-local tInsert = table.insert
-local sFind = string.find
-local format = string.format
-
-local type = type
-local pairs = pairs
-local assert = assert
+local tConcat = table.concat
+local wipe = wipe
+
+local tonumber = tonumber
+local tostring = tostring
+local loadstring = loadstring
+local gMatch = string.gmatch
+local tInsert = table.insert
+local sFind = string.find
+local format = string.format
+local f = string.format
+
+local type = type
+local pairs = pairs
+local assert = assert
function Database.Init()
local startTotal = 0
@@ -214,30 +217,40 @@ function Database.Override(overrideData, overrideTable, keys)
end
---Used to add new Ids into the master list of ids for that type
----@param AllIdStrings string[] @A list of strings containing the ids in the database, will be concatinated into one string
----@param dataOverride table @The data to check for new ids
----@return QuestId[]|NpcId[]|ObjectId[]|ItemId[] @Returns a list of new ids
-function Database.GetNewIds(AllIdStrings, dataOverride)
- -- We add , to the start and end of the string so we can search for ,id, in the string
- local allIds = "," .. tConcat(AllIdStrings, ",") .. ","
-
- -- Table to store the new ids
- local newIds = {}
-
- -- Add all the ids to the allIds table
- for id in pairs(dataOverride) do
- -- Search in the idString if ,id, is found
- -- local found, e, d = allIds:find("(,*" .. id .. ",*)")
- local found = sFind(allIds, "," .. id .. ",")
- if not found then
- -- Print what we found
- if not Database.debugLoadStaticEnabled and Database.debugPrintEnabled and Database.debugEnabled then
- LibQuestieDB.ColorizePrint("reputationBlue", " Adding new ID", id)
+do
+ -- Table used to store the ids for the current call
+ local allIdsSet = {}
+ ---Used to add new Ids into the master list of ids for that type
+ ---@param AllIdStrings string @A list of strings containing the ids in the database, will be concatinated into one string
+ ---@param dataOverride table @The data to check for new ids
+ ---@return QuestId[]|NpcId[]|ObjectId[]|ItemId[] @Returns a list of new ids
+ function Database.GetNewIds(AllIdStrings, dataOverride)
+ -- Split the string into a table of strings
+ local allIds = strsplittable(",", AllIdStrings)
+
+ -- Create a hash set for all existing IDs
+ for i = 1, #allIds do
+ allIdsSet[allIds[i]] = true
+ end
+
+ -- Table to store the new ids
+ local newIds = {}
+ -- Add all the ids to the allIds table
+ for id in pairs(dataOverride) do
+ if not allIdsSet[tostring(id)] then
+ -- Print what we found
+ -- if not Database.debugLoadStaticEnabled and Database.debugPrintEnabled and Database.debugEnabled then
+ -- LibQuestieDB.ColorizePrint("reputationBlue", " Adding new ID", id)
+ -- end
+ tInsert(newIds, id)
end
- tInsert(newIds, id)
end
+
+ -- Wipe it for the next call
+ -- Do it at the end so when it is called the last time it is wiped.
+ wipe(allIdsSet)
+ return newIds
end
- return newIds
end
--*-------------------------------------------
diff --git a/Database/l10n/l10n.lua b/Database/l10n/l10n.lua
index d50ee3dc..badcb5ae 100644
--- a/Database/l10n/l10n.lua
+++ b/Database/l10n/l10n.lua
@@ -9,9 +9,7 @@ local l10n = LibQuestieDB.CreateDatabaseInTable(LibQuestieDB.l10n, "l10n", {})
l10n.currentLocale = GetLocale()
-- Set this to nil to use the locale of the client
-- Override locale
-l10n.currentLocale = "ptBR"
-
-GLOBl10n = l10n
+-- l10n.currentLocale = "ptBR"
-- Order Item, Npc, Object, Quest
-- "enUS": "", # English (US) # Yes EN is empty
@@ -45,20 +43,28 @@ local indexToLocale = {
local localeToIndex = {}
local localeToPattern = {}
do
+ -- Populate the localeToIndex table with locale as key and its index as value
+ -- This helps in quickly finding the index of a given locale
for k, v in pairs(indexToLocale) do
localeToIndex[v] = k
end
+ -- Function to create a pattern string for a given locale
+ -- This pattern is used to extract the localized string from a concatenated string of all locales
local function createPatternForLocale(locale)
- local patternString = "^"
- local repeatPattern = ".-"
- local capturePattern = "(.-)"
+ local patternString = "^" -- Start of the string
+ local repeatPattern = ".-" -- Non-greedy match for any character sequence
+ local capturePattern = "(.-)" -- Non-greedy match for any character sequence, to be captured
for i = 1, localeToIndex[locale] do
- patternString = patternString .. (i == localeToIndex[locale] and capturePattern or repeatPattern) .. (i == #indexToLocale and "$" or specialChar)
+ -- Append the appropriate pattern based on the current index
+ patternString = patternString .. (i == localeToIndex[locale] and capturePattern or repeatPattern)
+ patternString = patternString .. (i == #indexToLocale and "$" or specialChar)
end
return patternString
end
+ -- Populate the localeToPattern table with locale as key and its pattern as value
+ -- This pattern is used to extract the localized string for the given locale
for i = 1, #indexToLocale do
localeToPattern[indexToLocale[i]] = createPatternForLocale(indexToLocale[i])
end
diff --git a/Helpers/ThreadLib.lua b/Helpers/ThreadLib.lua
index ff4c9192..52013a2b 100644
--- a/Helpers/ThreadLib.lua
+++ b/Helpers/ThreadLib.lua
@@ -35,13 +35,13 @@ function ThreadLib.Thread(threadFunction, delay, errorMessage, callbackFunction)
local timer
timer = newTicker(delay or 0, function()
- if (coStatus(thread) == "suspended") then --It's faster not to lookup the value but instead have it here
+ if (coStatus(thread) == "suspended") then --It's faster not to lookup the value but instead have it here
local success = coResume(thread)
-- Something in the coroutine went wrong, print the error and stop the timer
if not success then
timer:Cancel();
end
- elseif (coStatus(thread) == "dead") then --It's faster not to lookup the value but instead have it here
+ elseif (coStatus(thread) == "dead") then --It's faster not to lookup the value but instead have it here
timer:Cancel();
if (callbackFunction) then
callbackFunction()
@@ -78,4 +78,6 @@ end
---@param delay integer @Anything below 0.05 is each frame
function ThreadLib.ThreadSimple(threadFunction, delay)
return ThreadLib.Thread(threadFunction, delay)
-end
\ No newline at end of file
+end
+
+return ThreadLib
diff --git a/LuaLS-annotations.md b/LuaLS-annotations.md
new file mode 100644
index 00000000..82b23c54
--- /dev/null
+++ b/LuaLS-annotations.md
@@ -0,0 +1,964 @@
+LUA Type Annotations
+
+Add additional context to your code and type checking.
+
+Annotations are prefixed with `---`, like a Lua comment with one extra dash.
+
+Annotation Formatting
+
+Annotations support most of the markdown syntax. More specifically, you can use:
+
+- headings
+- bold text
+- italic text
+- struckthrough text
+- ordered list
+- unordered list
+- blockquote
+- code
+- code block
+- horizontal rule
+- link
+- image
+
+There are many ways to add newlines to your annotations. The most bulletproof way is to simply add an extra line of just `---`, although this functions like a paragraph break, not a newline.
+
+The below methods can be added to the end of a line:
+
+- HTML `
` tag (recommended)
+- `\n` newline escape character
+- Two trailing spaces (may be removed by formatting tools)
+- Markdown backslash `\` (not recommended)
+
+Referring to symbols
+
+As of v3.9.2, you can refer to symbols from your workspace in markdown descriptions using markdown links. Hovering the described value will show a hyperlink that, when clicked, will take you to where the symbol is defined.
+
+```lua
+---@alias MyCustomType integer
+
+---Calculate a value using [my custom type](lua://MyCustomType)
+function calculate(x) end
+```
+
+Tips
+
+- If you type `---` one line above a function, you will receive a suggested snippet that includes `@param` and `@return` annotations for each parameter and return value found in the function.
+
+Documenting Types
+
+Properly documenting types with the language server is very important and where a lot of the features and advantages are. Below is a list of all recognized Lua types:
+
+- `nil`
+- `any`
+- `boolean`
+- `string`
+- `number`
+- `integer`
+- `function`
+- `table`
+- `thread`
+- `userdata`
+- `lightuserdata`
+
+You can also simulate classes and fields and even create your own types.
+
+Adding a question mark (`?`) after a type like `boolean?` or `number?` is the same as saying `boolean|nil` or `number|nil`. This can be used to specify that something is either a specified type or `nil`. This can be very useful for function returns where a value or `nil` can be returned.
+
+Below is a list of how you can document more advanced types:
+
+Type | Document As:
+--- | ---
+Union Type | `TYPE_1 | TYPE_2`
+Array | `VALUE_TYPE[]`
+Tuple | `[VALUE_TYPE, VALUE_TYPE]`
+Dictionary | `{ [string]: VALUE_TYPE }`
+Key-Value Table | `table`
+Table literal | `{ key1: VALUE_TYPE, key2: VALUE_TYPE }`
+Function | `fun(PARAM: TYPE): RETURN_TYPE`
+
+Unions may need to be placed in parentheses in certain situations, such as when defining an array that contains multiple value types:
+
+```Lua
+---@type (string | integer)[]
+local myArray = {}
+```
+
+Understanding This Page
+
+To get an understanding of how to use the annotations described on this page, you'll need to know how to read the Syntax sections of each annotation.
+
+Symbol | Meaning
+--- | ---
+`` | A required value that you provide
+`[value_name]` | Everything inside is optional
+`[value_name...]` | This value is repeatable
+`value_name | value_name` | The left or right side are valid
+
+Any other symbols are syntactically required and should be copied verbatim.
+
+Annotations List
+
+Below is a list of all annotations recognized by the language server:
+
+@alias
+
+An alias can be used to create your own type. You can also use it to create an enum that does not exist at runtime.
+
+Syntax
+
+`---@alias `
+
+Examples
+
+Simple alias
+
+```Lua
+---@alias userID integer The ID of a user
+```
+
+Custom Type
+
+```Lua
+---@alias modes "r" | "w"
+```
+
+Custom Type with Descriptions
+
+```Lua
+---@alias DeviceSide
+---| '"left"' # The left side of the device
+---| '"right"' # The right side of the device
+---| '"top"' # The top side of the device
+---| '"bottom"' # The bottom side of the device
+---| '"front"' # The front side of the device
+---| '"back"' # The back side of the device
+
+---@param side DeviceSide
+local function checkSide(side) end
+```
+
+Literal Custom Type
+
+```Lua
+local A = "Hello"
+local B = "World"
+
+---@alias myLiteralAlias `A` | `B`
+
+---@param x myLiteralAlias
+function foo(x) end
+```
+
+@as
+
+Force a type onto an expression.
+
+Warning: This annotation cannot be added using `---@as ` it must be done like `--[[@as ]]`.
+
+Syntax
+
+`--[[@as ]]`
+
+Examples
+
+Override Type
+
+```Lua
+---@param key string Must be a string
+local function doSomething(key) end
+
+local x = nil
+
+doSomething(x --[[@as string]])
+```
+
+@async
+
+Mark a function as being asynchronous. When hint.await is true, functions marked with @async will have an await hint displayed next to them when they are called.
+
+Syntax
+
+`---@async`
+
+Examples
+
+Mark Function Async
+
+```Lua
+---@async
+---Perform an asynchronous HTTP GET request
+function http.get(url) end
+```
+
+@cast
+
+Cast a variable to a different type or types.
+
+Syntax
+
+`---@cast [+|-][, [+|-]...]`
+
+Examples
+
+Simple Cast
+
+```Lua
+---@type integer | string
+local x
+
+---@cast x string
+print(x) --> x: string
+```
+
+Add Type
+
+```Lua
+---@type integer
+local x
+
+---@cast x +boolean
+print(x) --> x: integer | boolean
+```
+
+Remove Type
+
+```Lua
+---@type integer|string
+local x
+
+---@cast x -integer
+print(x) --> x: string
+```
+
+Cast Multiple Types
+
+```Lua
+---@type string
+local x --> x: string
+
+---@cast x +boolean, +number
+print(x) --> x:string | boolean | number
+```
+
+Cast Possibly `nil`
+
+```Lua
+---@type string
+local x
+
+---@cast x +?
+print(x) --> x:string?
+```
+
+@class
+
+Define a class. Can be used with @field to define a table structure. Once a class is defined, it can be used as a type for parameters, returns, and more. A class can also inherit one or more parent classes. Marking the class as (exact) means fields cannot be injected after the definition.
+
+Syntax
+
+`---@class [(exact)] [: [, ...]]`
+
+Examples
+
+Define a Class
+
+```Lua
+---@class Car
+local Car = {}
+```
+
+Class Inheritance
+
+```Lua
+---@class Vehicle
+local Vehicle = {}
+
+---@class Plane: Vehicle
+local Plane = {}
+```
+
+Create exact class
+
+```Lua
+---@class (exact) Point
+---@field x number
+---@field y number
+local Point = {}
+Point.x = 1 -- OK
+Point.y = 2 -- OK
+Point.z = 3 -- Warning
+```
+
+How the table Class is Implemented
+
+```Lua
+---@class table: { [K]: V }
+```
+
+@deprecated
+
+Mark a function as deprecated. This will trigger the deprecated diagnostic, displaying it as struck through.
+
+Syntax
+
+`---@deprecated`
+
+Examples
+
+Mark a Function as Deprecated
+
+```Lua
+---@deprecated
+function outdated() end
+```
+
+@enum
+
+Mark a Lua table as an enum, giving it similar functionality to @alias, only the table is still usable at runtime.
+
+Syntax
+
+`---@enum [(key)] `
+
+Examples
+
+Define Table as Enum
+
+```Lua
+---@enum colors
+local COLORS = {
+ black = 0,
+ red = 2,
+ green = 4,
+ yellow = 8,
+ blue = 16,
+ white = 32
+}
+
+---@param color colors
+local function setColor(color) end
+
+setColor(COLORS.green)
+```
+
+Define Table Keys as Enum
+
+```Lua
+---@enum (key) Direction
+local direction = {
+ LEFT = 1,
+ RIGHT = 2,
+}
+
+---@param dir Direction
+local function move(dir)
+ assert(dir == "LEFT" or dir == "RIGHT")
+
+ assert(direction[dir] == 1 or direction[dir] == 2)
+ assert(direction[dir] == direction.LEFT or direction[dir] == direction.RIGHT)
+end
+
+move("LEFT")
+```
+
+@field
+
+Define a field within a table. Should be immediately following a @class. As of v3.6, you can mark a field as private, protected, public, or package.
+
+Syntax
+
+`---@field [scope] [description]`
+
+It is also possible to allow any key of a certain type to be added using the below:
+
+`---@field [scope] [] [description]`
+
+Examples
+
+Simple Documentation of a Class
+
+```Lua
+---@class Person
+---@field height number The height of this person in cm
+---@field weight number The weight of this person in kg
+---@field firstName string The first name of this person
+---@field lastName? string The last name of this person
+---@field age integer The age of this person
+
+---@param person Person
+local function hire(person) end
+```
+
+Mark Field as Private
+
+```Lua
+---@class Animal
+---@field private legs integer
+---@field eyes integer
+
+---@class Dog:Animal
+local myDog = {}
+
+---Child class Dog CANNOT use private field legs
+function myDog:legCount()
+ return self.legs
+end
+```
+
+Mark Field as Protected
+
+```Lua
+---@class Animal
+---@field protected legs integer
+---@field eyes integer
+
+---@class Dog:Animal
+local myDog = {}
+
+---Child class Dog can use protected field legs
+function myDog:legCount()
+ return self.legs
+end
+```
+
+Typed Field
+
+```Lua
+---@class Numbers
+---@field named string
+---@field [string] integer
+local Numbers = {}
+```
+
+@generic
+
+Generics allow code to be reused and serve as a sort of "placeholder" for a type.
+
+Syntax
+
+`---@generic [:parent_type] [, [:parent_type]]`
+
+Examples
+
+Generic Function
+
+```Lua
+---@generic T : integer
+---@param p1 T
+---@return T, T[]
+function Generic(p1) end
+
+-- v1: string
+-- v2: string[]
+local v1, v2 = Generic("String")
+
+-- v3: integer
+-- v4: integer[]
+local v3, v4 = Generic(10)
+```
+
+Capture with Backticks
+
+```Lua
+---@class Vehicle
+local Vehicle = {}
+function Vehicle:drive() end
+
+---@generic T
+---@param class `T` # the type is captured using `T`
+---@return T # generic type is returned
+local function new(class) end
+
+-- obj: Vehicle
+local obj = new("Vehicle")
+```
+
+Array Class Using Generics
+
+```Lua
+---@class Array: { [integer]: T }
+
+---@type Array
+local arr = {}
+
+-- Warns that I am assigning a boolean to a string
+arr[1] = false
+
+arr[3] = "Correct"
+```
+
+Dictionary Class Using Generics
+
+```Lua
+---@class Dictionary: { [string]: T }
+
+---@type Dictionary
+local dict = {}
+
+-- no warning despite assigning a string
+dict["foo"] = "bar?"
+
+dict["correct"] = true
+```
+
+@meta
+
+Marks a file as "meta", meaning it is used for definitions and not for its functional Lua code. It is used internally by the language server for defining the built-in Lua libraries. If you are writing your own definition files, you will probably want to include this annotation in them.
+
+Syntax
+
+`---@meta [name]`
+
+Examples
+
+Mark Meta File
+
+```Lua
+---@meta [name]
+```
+
+@module
+
+Simulates require-ing a file.
+
+Syntax
+
+`---@module ''`
+
+Examples
+
+"Require" a File
+
+```Lua
+---@module 'http'
+
+--The above provides the same as
+require 'http'
+--within the language server
+```
+
+"Require" a File and Assign to a Variable
+
+```Lua
+---@module 'http'
+local http
+
+--The above provides the same as
+local http = require 'http'
+--within the language server
+```
+
+@nodiscard
+
+Mark a function as having return values that cannot be ignored/discarded. This can help users understand how to use the function as if they do not capture the returns, a warning will be raised.
+
+Syntax
+
+`---@nodiscard`
+
+Examples
+
+Prevent Ignoring a Function's Returns
+
+```Lua
+---@return string username
+---@nodiscard
+function getUsername() end
+```
+
+@operator
+
+Provides type declarations for an operator metamethod.
+
+Syntax
+
+`---@operator [(param_type)]:`
+
+Examples
+
+Declare `__add` Metamethod
+
+```Lua
+---@class Vector
+---@operator add(Vector): Vector
+
+---@type Vector
+local v1
+---@type Vector
+local v2
+
+--> v3: Vector
+local v3 = v1 + v2
+```
+
+Declare Unary Minus Metamethod
+
+```Lua
+---@class Passcode
+---@operator unm:integer
+
+---@type Passcode
+local pA
+
+local pB = -pA
+--> integer
+```
+
+Declare `__call` Metamethod
+
+```Lua
+---@class URL
+---@operator call:string
+local URL = {}
+```
+
+@overload
+
+Define an additional signature for a function.
+
+Syntax
+
+`---@overload fun([param: type[, param: type...]]): [return_value[,return_value]]`
+
+Examples
+
+Define Function Overload
+
+```Lua
+---@param objectID integer The id of the object to remove
+---@param whenOutOfView boolean Only remove the object when it is not visible
+---@return boolean success If the object was successfully removed
+---@overload fun(objectID: integer): boolean
+local function removeObject(objectID, whenOutOfView) end
+```
+
+Define Class Call Signature
+
+```Lua
+---@overload fun(a: string): boolean
+local foo = setmetatable({}, {
+ __call = function(a)
+ print(a)
+ return true
+ end,
+})
+
+local bool = foo("myString")
+```
+
+@package
+
+Mark a function as private to the file it is defined in. A packaged function cannot be accessed from another file.
+
+Syntax
+
+`---@package`
+
+Examples
+
+Mark a Function as Private to a Package
+
+```Lua
+---@class Animal
+---@field private eyes integer
+local Animal = {}
+
+---@package
+---This cannot be accessed in another file
+function Animal:eyesCount()
+ return self.eyes
+end
+```
+
+@param
+
+Define a parameter/argument for a function. This tells the language server what types are expected and can help enforce types and provide completion. Putting a question mark (`?`) after the parameter name will mark it as optional, meaning `nil` is an accepted type.
+
+Syntax
+
+`---@param [description]`
+
+Examples
+
+Simple Function Parameter
+
+```Lua
+---@param username string The name to set for this user
+function setUsername(username) end
+```
+
+Parameter Union Type
+
+```Lua
+---@param setting string The name of the setting
+---@param value string|number|boolean The value of the setting
+local function settings.set(setting, value) end
+```
+
+Optional Parameter
+
+```Lua
+---@param role string The name of the role
+---@param isActive? boolean If the role is currently active
+---@return Role
+function Role.new(role, isActive) end
+```
+
+Variable Number of Parameters
+
+```Lua
+---@param index integer
+---@param ... string Tags to add to this entry
+local function addTags(index, ...) end
+```
+
+Generic Function Parameter
+
+```Lua
+---@class Box
+
+---@generic T
+---@param objectID integer The ID of the object to set the type of
+---@param type `T` The type of object to set
+---@return `T` object The object as a Lua object
+local function setObjectType(objectID, type) end
+
+--> boxObject: Box
+local boxObject = setObjectType(1, "Box")
+```
+
+Custom Type Parameter
+
+```Lua
+---@param mode string
+---|"'immediate'" # comment 1
+---|"'async'" # comment 2
+function bar(mode) end
+```
+
+Literal Custom Type Parameter
+
+```Lua
+local A = 0
+local B = 1
+
+---@param active integer
+---| `A` # Has a value of 0
+---| `B` # Has a value of 1
+function set(active) end
+```
+
+@private
+
+Mark a function as private to a class. Private functions can be accessed only from within their class and are not accessible from child classes.
+
+Syntax
+
+`---@private`
+
+Examples
+
+Mark a function as private
+
+```Lua
+---@class Animal
+---@field private eyes integer
+local Animal = {}
+
+---@private
+function Animal:eyesCount()
+ return self.eyes
+end
+
+---@class Dog:Animal
+local myDog = {}
+
+---NOT PERMITTED!
+myDog:eyesCount();
+```
+
+@protected
+
+Mark a function as protected within a class. Protected functions can be accessed only from within their class or from child classes.
+
+Syntax
+
+`---@protected`
+
+Examples
+
+Mark a function as protected
+
+```Lua
+---@class Animal
+---@field private eyes integer
+local Animal = {}
+
+---@protected
+function Animal:eyesCount()
+ return self.eyes
+end
+
+---@class Dog:Animal
+local myDog = {}
+
+---Permitted because function is protected, not private.
+myDog:eyesCount();
+```
+
+@return
+
+Define a return value for a function. This tells the language server what types are expected and can help enforce types and provide completion.
+
+Syntax
+
+`---@return [ [comment] | [name] #]`
+
+Examples
+
+Simple Function Return
+
+```Lua
+---@return boolean
+local function isEnabled() end
+```
+
+Named Function Return
+
+```Lua
+---@return boolean enabled
+local function isEnabled() end
+```
+
+Named, Described Function Return
+
+```Lua
+---@return boolean enabled If the item is enabled
+local function isEnabled() end
+```
+
+Described Function Return
+
+```Lua
+---@return boolean # If the item is enabled
+local function isEnabled() end
+```
+
+Optional Function Return
+
+```Lua
+---@return boolean|nil error
+local function makeRequest() end
+```
+
+Variable Function Returns
+
+```Lua
+---@return integer count Number of nicknames found
+---@return string ...
+local function getNicknames() end
+```
+
+@type
+
+Mark a variable as being of a certain type. Union types are separated with a pipe character `|`.
+
+Syntax
+
+`---@type `
+
+Examples
+
+Basic Type Definition
+
+```Lua
+---@type boolean
+local x
+```
+
+Array Type Definition
+
+```Lua
+---@type string[]
+local names
+```
+
+Dictionary Type Definition
+
+```Lua
+---@type { [string]: boolean }
+local statuses
+```
+
+Table Type Definition
+
+```Lua
+---@type table
+local players
+```
+
+Union Type Definition
+
+```Lua
+---@type boolean|number|"yes"|"no"
+local x
+```
+
+Function Type Definition
+
+```Lua
+---@type fun(name: string, value: any): boolean
+local x
+```
+
+@vararg
+
+Mark a function as having variable arguments. For variable returns, see @return.
+
+This annotation has been deprecated and is purely for legacy support for EmmyLua annotations. Use @param instead.
+
+Syntax
+
+`---@vararg `
+
+Examples
+
+Basic Variable Function Arguments
+
+```Lua
+---@vararg string
+function concat(...) end
+```
+
+@version
+
+Mark the required Lua version for a function or @class.
+
+Syntax
+
+`---@version [<|>] [, [<|>]version...]`
+
+Possible version values:
+
+- `5.1`
+- `5.2`
+- `5.3`
+- `5.4`
+- `JIT`
+
+Examples
+
+Declare Function Version
+
+```Lua
+---@version >5.2, JIT
+function hello() end
+```
+
+Declare Class Version
+
+```Lua
+---@version 5.4
+---@class Entry
+```
\ No newline at end of file
diff --git a/QuestieDB-BCC.toc b/QuestieDB-BCC.toc
index 478b0f71..59cba44b 100644
--- a/QuestieDB-BCC.toc
+++ b/QuestieDB-BCC.toc
@@ -29,6 +29,8 @@ Database\Object\Tbc\ObjectDataFiles.xml
Database\Quest\Tbc\QuestDataFiles.xml
## l10n
Database\l10n\Tbc\L10nDataFiles.xml
+## Translations
+Translations\Translations.xml
##* Corrections Meta
Corrections\MetaCorrections.xml
diff --git a/QuestieDB-Classic.toc b/QuestieDB-Classic.toc
index 0d974609..7118a5db 100644
--- a/QuestieDB-Classic.toc
+++ b/QuestieDB-Classic.toc
@@ -29,6 +29,8 @@ Database\Object\Era\ObjectDataFiles.xml
Database\Quest\Era\QuestDataFiles.xml
## l10n
Database\l10n\Era\L10nDataFiles.xml
+## Translations
+Translations\Translations.xml
##* Corrections Meta
Corrections\MetaCorrections.xml
diff --git a/QuestieDB-WOTLKC.toc b/QuestieDB-WOTLKC.toc
index a3b5a67e..1bb85dcd 100644
--- a/QuestieDB-WOTLKC.toc
+++ b/QuestieDB-WOTLKC.toc
@@ -29,6 +29,8 @@ Database\Object\Wotlk\ObjectDataFiles.xml
Database\Quest\Wotlk\QuestDataFiles.xml
## l10n
Database\l10n\Wotlk\L10nDataFiles.xml
+## Translations
+Translations\Translations.xml
##* Corrections Meta
Corrections\MetaCorrections.xml
diff --git a/QuestieDB.lua b/QuestieDB.lua
index 8420e0b4..5cb1c7bb 100644
--- a/QuestieDB.lua
+++ b/QuestieDB.lua
@@ -102,6 +102,8 @@ SlashCmdList["QuestieDB"] = function(args)
print("Localized math.floor", time1, "ms")
print("Modulus to floor", time2, "ms")
end
+ collectgarbage()
end
+
SLASH_QuestieDB1 = "/QuestieDB"
SLASH_QuestieDB2 = "/qdb"
diff --git a/Translations/_data/.gitkeep b/Translations/_data/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/Translations/translations.xml b/Translations/translations.xml
new file mode 100644
index 00000000..907213e9
--- /dev/null
+++ b/Translations/translations.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/cli/CLI_Helpers.lua b/cli/CLI_Helpers.lua
index 874fb5d9..567fc247 100644
--- a/cli/CLI_Helpers.lua
+++ b/cli/CLI_Helpers.lua
@@ -68,8 +68,10 @@ function CLI_Helpers.loadTOC(file)
local xmlFilePath = line:match("^(.*)/.-%.xml$") .. "/"
-- print(xmlFilePath)
for xmlFile in string.gmatch(filetext, "