diff --git a/[core]/es_extended/locale.lua b/[core]/es_extended/locale.lua index 3f059b62..b19cd79f 100644 --- a/[core]/es_extended/locale.lua +++ b/[core]/es_extended/locale.lua @@ -1,20 +1,69 @@ Locales = {} +--- English fallback for missing keys. locale.lua runs in every resource VM, so +--- a resource may opt out through its own Config.LocaleFallback; otherwise the +--- shared "esx:localeFallback" convar decides (enabled by default). +local function localeFallbackEnabled() + if Config.LocaleFallback ~= nil then + return Config.LocaleFallback + end + + return GetConvar("esx:localeFallback", "true") ~= "false" +end + +--- Read and execute a locale file, returning its table or false if unreadable. +local function readLocale(locale) + local success, result = pcall(function() + return assert(load(LoadResourceFile(GetCurrentResourceName(), ("locales/%s.lua"):format(locale))))() + end) + + return success and result or false +end + +--- Copy every key the target locale is missing from English into it once, warn +--- about the gaps, then let the English table go. The locale ends up +--- self-contained, so no per-call fallback and only one table stays in memory. +local function backfillFromEnglish(locale, translations) + local english = readLocale("en") + if not english then + return + end + + local missing = {} + for key, value in pairs(english) do + if translations[key] == nil then + translations[key] = value + missing[#missing + 1] = key + end + end + + if #missing > 0 then + print(("[es_extended] locale [%s] is missing %d translation(s), falling back to English: %s") + :format(locale, #missing, table.concat(missing, ", "))) + end +end + +--- Lazily load and cache a locale file, false if it cannot be read. +local function loadLocale(locale) + if Locales[locale] == nil then + local translations = readLocale(locale) + + if translations and locale ~= "en" and localeFallbackEnabled() then + backfillFromEnglish(locale, translations) + end + + Locales[locale] = translations + end + + return Locales[locale] +end + function Translate(str, ...) -- Translate string if not str then error(("Resource ^5%s^1 You did not specify a parameter for the Translate function or the value is nil!"):format(GetInvokingResource() or GetCurrentResourceName())) end - --- Load the locale file if it hasn't been loaded yet - if Locales[Config.Locale] == nil then - local success, result = pcall(function() - return assert(load(LoadResourceFile(GetCurrentResourceName(), ("locales/%s.lua"):format(Config.Locale))))() - end) - - Locales[Config.Locale] = success and result or false - end - - local translations = Locales[Config.Locale] + local translations = loadLocale(Config.Locale) if not translations then if Config.Locale == "en" then return "Locale [en] does not exist" @@ -38,4 +87,4 @@ end _ = Translate -- luacheck: ignore _U -_U = TranslateCap \ No newline at end of file +_U = TranslateCap diff --git a/[core]/es_extended/shared/config/main.lua b/[core]/es_extended/shared/config/main.lua index 0bb55117..a1194e45 100644 --- a/[core]/es_extended/shared/config/main.lua +++ b/[core]/es_extended/shared/config/main.lua @@ -4,6 +4,11 @@ local txAdminLocale = GetConvar("txAdmin-locale", "en") local esxLocale = GetConvar("esx:locale", "invalid") Config.Locale = (esxLocale ~= "invalid") and esxLocale or (txAdminLocale ~= "custom" and txAdminLocale) or "en" +-- When a key is missing from the active locale, fall back to the English string +-- instead of returning a placeholder. Set convar "esx:localeFallback" to "false" +-- to keep the placeholder (useful when authoring a new translation). +Config.LocaleFallback = GetConvar("esx:localeFallback", "true") ~= "false" + -- For ox inventory, this will automatically be adjusted, do not change! For other inventories, leave as false unless specifically instructed to change. Config.CustomInventory = false