539 lines
16 KiB
Lua
539 lines
16 KiB
Lua
--[[
|
|
Name: AceLocale-2.2
|
|
Revision: $Rev: 17638 $
|
|
Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
|
|
Inspired By: Ace 1.x by Turan (turan@gryphon.com)
|
|
Website: http://www.wowace.com/
|
|
Documentation: http://www.wowace.com/index.php/AceLocale-2.2
|
|
SVN: http://svn.wowace.com/root/trunk/Ace2/AceLocale-2.2
|
|
Description: Localization library for addons to use to handle proper
|
|
localization and internationalization.
|
|
Dependencies: AceLibrary
|
|
]]
|
|
|
|
local MAJOR_VERSION = "AceLocale-2.2"
|
|
local MINOR_VERSION = "$Revision: 17639 $"
|
|
|
|
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
|
|
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
|
|
|
|
if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check
|
|
local AceLocale = {}
|
|
|
|
local DEFAULT_LOCALE = "enUS"
|
|
local _G = getfenv(0)
|
|
|
|
local BASE_TRANSLATIONS, DEBUGGING, TRANSLATIONS, BASE_LOCALE, TRANSLATION_TABLES, REVERSE_TRANSLATIONS, STRICTNESS, DYNAMIC_LOCALES, CURRENT_LOCALE, NAME
|
|
|
|
local rawget = rawget
|
|
local rawset = rawset
|
|
local type = type
|
|
|
|
local newRegistries = {}
|
|
local scheduleClear
|
|
|
|
local lastSelf
|
|
local __index = function(self, key)
|
|
lastSelf = self
|
|
local value = (rawget(self, TRANSLATIONS) or AceLocale.prototype)[key]
|
|
rawset(self, key, value)
|
|
return value
|
|
end
|
|
|
|
local __newindex = function(self, k, v)
|
|
if type(v) ~= "function" and type(k) ~= "table" then
|
|
AceLocale.error(self, "Cannot change the values of an AceLocale instance.")
|
|
end
|
|
rawset(self, k, v)
|
|
end
|
|
|
|
local __tostring = function(self)
|
|
if type(rawget(self, 'GetLibraryVersion')) == "function" then
|
|
return self:GetLibraryVersion()
|
|
else
|
|
return "AceLocale(" .. self[NAME] .. ")"
|
|
end
|
|
end
|
|
|
|
local function clearCache(self)
|
|
if not rawget(self, BASE_TRANSLATIONS) then
|
|
return
|
|
end
|
|
|
|
local cache = self[BASE_TRANSLATIONS]
|
|
rawset(self, REVERSE_TRANSLATIONS, nil)
|
|
|
|
for k in pairs(self) do
|
|
if rawget(cache, k) ~= nil then
|
|
self[k] = nil
|
|
end
|
|
end
|
|
rawset(self, 'tmp', true)
|
|
self.tmp = nil
|
|
end
|
|
|
|
local function refixInstance(instance)
|
|
if getmetatable(instance) then
|
|
setmetatable(instance, nil)
|
|
end
|
|
local translations = instance[TRANSLATIONS]
|
|
if translations then
|
|
if getmetatable(translations) then
|
|
setmetatable(translations, nil)
|
|
end
|
|
local baseTranslations = instance[BASE_TRANSLATIONS]
|
|
if getmetatable(baseTranslations) then
|
|
setmetatable(baseTranslations, nil)
|
|
end
|
|
if translations == baseTranslations or instance[STRICTNESS] then
|
|
setmetatable(instance, {
|
|
__index = __index,
|
|
__newindex = __newindex,
|
|
__tostring = __tostring
|
|
})
|
|
|
|
setmetatable(translations, {
|
|
__index = AceLocale.prototype
|
|
})
|
|
else
|
|
setmetatable(instance, {
|
|
__index = __index,
|
|
__newindex = __newindex,
|
|
__tostring = __tostring
|
|
})
|
|
|
|
setmetatable(translations, {
|
|
__index = baseTranslations,
|
|
})
|
|
|
|
setmetatable(baseTranslations, {
|
|
__index = AceLocale.prototype,
|
|
})
|
|
end
|
|
else
|
|
setmetatable(instance, {
|
|
__index = __index,
|
|
__newindex = __newindex,
|
|
__tostring = __tostring,
|
|
})
|
|
end
|
|
clearCache(instance)
|
|
newRegistries[instance] = true
|
|
scheduleClear()
|
|
return instance
|
|
end
|
|
|
|
function AceLocale:new(name)
|
|
self:argCheck(name, 2, "string")
|
|
|
|
if self.registry[name] and type(rawget(self.registry[name], 'GetLibraryVersion')) ~= "function" then
|
|
return self.registry[name]
|
|
end
|
|
|
|
AceLocale.registry[name] = refixInstance({
|
|
[STRICTNESS] = false,
|
|
[NAME] = name,
|
|
})
|
|
newRegistries[AceLocale.registry[name]] = true
|
|
return AceLocale.registry[name]
|
|
end
|
|
|
|
AceLocale.prototype = { class = AceLocale }
|
|
|
|
function AceLocale.prototype:EnableDebugging()
|
|
if rawget(self, BASE_TRANSLATIONS) then
|
|
AceLocale.error(self, "Cannot enable debugging after a translation has been registered.")
|
|
end
|
|
rawset(self, DEBUGGING, true)
|
|
end
|
|
|
|
function AceLocale.prototype:EnableDynamicLocales(override)
|
|
AceLocale.argCheck(self, override, 2, "boolean", "nil")
|
|
if not override and rawget(self, BASE_TRANSLATIONS) then
|
|
AceLocale.error(self, "Cannot enable dynamic locales after a translation has been registered.")
|
|
end
|
|
if not rawget(self, DYNAMIC_LOCALES) then
|
|
rawset(self, DYNAMIC_LOCALES, true)
|
|
if rawget(self, BASE_LOCALE) then
|
|
if not rawget(self, TRANSLATION_TABLES) then
|
|
rawset(self, TRANSLATION_TABLES, {})
|
|
end
|
|
self[TRANSLATION_TABLES][self[BASE_LOCALE]] = self[BASE_TRANSLATIONS]
|
|
self[TRANSLATION_TABLES][self[CURRENT_LOCALE]] = self[TRANSLATIONS]
|
|
end
|
|
end
|
|
end
|
|
|
|
function AceLocale.prototype:RegisterTranslations(locale, func)
|
|
AceLocale.argCheck(self, locale, 2, "string")
|
|
AceLocale.argCheck(self, func, 3, "function")
|
|
|
|
if locale == rawget(self, BASE_LOCALE) then
|
|
AceLocale.error(self, "Cannot provide the same locale more than once. %q provided twice.", locale)
|
|
end
|
|
|
|
if rawget(self, BASE_TRANSLATIONS) and GetLocale() ~= locale then
|
|
if rawget(self, DEBUGGING) or rawget(self, DYNAMIC_LOCALES) then
|
|
if not rawget(self, TRANSLATION_TABLES) then
|
|
rawset(self, TRANSLATION_TABLES, {})
|
|
end
|
|
if self[TRANSLATION_TABLES][locale] then
|
|
AceLocale.error(self, "Cannot provide the same locale more than once. %q provided twice.", locale)
|
|
end
|
|
local t = func()
|
|
func = nil
|
|
if type(t) ~= "table" then
|
|
AceLocale.error(self, "Bad argument #3 to `RegisterTranslations'. function did not return a table. (expected table, got %s)", type(t))
|
|
end
|
|
self[TRANSLATION_TABLES][locale] = t
|
|
t = nil
|
|
end
|
|
func = nil
|
|
return
|
|
end
|
|
local t = func()
|
|
func = nil
|
|
if type(t) ~= "table" then
|
|
AceLocale.error(self, "Bad argument #3 to `RegisterTranslations'. function did not return a table. (expected table, got %s)", type(t))
|
|
end
|
|
|
|
rawset(self, TRANSLATIONS, t)
|
|
if not rawget(self, BASE_TRANSLATIONS) then
|
|
rawset(self, BASE_TRANSLATIONS, t)
|
|
rawset(self, BASE_LOCALE, locale)
|
|
for key,value in pairs(t) do
|
|
if value == true then
|
|
t[key] = key
|
|
end
|
|
end
|
|
else
|
|
for key, value in pairs(self[TRANSLATIONS]) do
|
|
if not rawget(self[BASE_TRANSLATIONS], key) then
|
|
AceLocale.error(self, "Improper translation exists. %q is likely misspelled for locale %s.", key, locale)
|
|
end
|
|
if value == true then
|
|
AceLocale.error(self, "Can only accept true as a value on the base locale. %q is the base locale, %q is not.", rawget(self, BASE_LOCALE), locale)
|
|
end
|
|
end
|
|
end
|
|
rawset(self, CURRENT_LOCALE, locale)
|
|
refixInstance(self)
|
|
if rawget(self, DEBUGGING) or rawget(self, DYNAMIC_LOCALES) then
|
|
if not rawget(self, TRANSLATION_TABLES) then
|
|
rawset(self, TRANSLATION_TABLES, {})
|
|
end
|
|
self[TRANSLATION_TABLES][locale] = t
|
|
end
|
|
t = nil
|
|
end
|
|
|
|
function AceLocale.prototype:SetLocale(locale)
|
|
AceLocale.argCheck(self, locale, 2, "string", "boolean")
|
|
if not rawget(self, DYNAMIC_LOCALES) then
|
|
AceLocale.error(self, "Cannot call `SetLocale' without first calling `EnableDynamicLocales'.")
|
|
end
|
|
if not rawget(self, TRANSLATION_TABLES) then
|
|
AceLocale.error(self, "Cannot call `SetLocale' without first calling `RegisterTranslations'.")
|
|
end
|
|
if locale == true then
|
|
locale = GetLocale()
|
|
if not self[TRANSLATION_TABLES][locale] then
|
|
locale = self[BASE_LOCALE]
|
|
end
|
|
end
|
|
|
|
if self[CURRENT_LOCALE] == locale then
|
|
return
|
|
end
|
|
|
|
if not self[TRANSLATION_TABLES][locale] then
|
|
AceLocale.error(self, "Locale %q not registered.", locale)
|
|
end
|
|
|
|
self[TRANSLATIONS] = self[TRANSLATION_TABLES][locale]
|
|
self[CURRENT_LOCALE] = locale
|
|
refixInstance(self)
|
|
end
|
|
|
|
function AceLocale.prototype:GetLocale()
|
|
if not rawget(self, TRANSLATION_TABLES) then
|
|
AceLocale.error(self, "Cannot call `GetLocale' without first calling `RegisterTranslations'.")
|
|
end
|
|
return self[CURRENT_LOCALE]
|
|
end
|
|
|
|
local function iter(t, position)
|
|
return (next(t, position))
|
|
end
|
|
|
|
function AceLocale.prototype:IterateAvailableLocales()
|
|
if not rawget(self, DYNAMIC_LOCALES) then
|
|
AceLocale.error(self, "Cannot call `IterateAvailableLocales' without first calling `EnableDynamicLocales'.")
|
|
end
|
|
if not rawget(self, TRANSLATION_TABLES) then
|
|
AceLocale.error(self, "Cannot call `IterateAvailableLocales' without first calling `RegisterTranslations'.")
|
|
end
|
|
return iter, self[TRANSLATION_TABLES], nil
|
|
end
|
|
|
|
function AceLocale.prototype:HasLocale(locale)
|
|
if not rawget(self, DYNAMIC_LOCALES) then
|
|
AceLocale.error(self, "Cannot call `HasLocale' without first calling `EnableDynamicLocales'.")
|
|
end
|
|
AceLocale.argCheck(self, locale, 2, "string")
|
|
return rawget(self, TRANSLATION_TABLES) and self[TRANSLATION_TABLES][locale] ~= nil
|
|
end
|
|
|
|
function AceLocale.prototype:SetStrictness(strict)
|
|
AceLocale.argCheck(self, strict, 2, "boolean")
|
|
local mt = getmetatable(self)
|
|
if not mt then
|
|
AceLocale.error(self, "Cannot call `SetStrictness' without a metatable.")
|
|
end
|
|
if not rawget(self, TRANSLATIONS) then
|
|
AceLocale.error(self, "No translations registered.")
|
|
end
|
|
rawset(self, STRICTNESS, strict)
|
|
refixInstance(self)
|
|
end
|
|
|
|
local function initReverse(self)
|
|
rawset(self, REVERSE_TRANSLATIONS, {})
|
|
local alpha = self[TRANSLATIONS]
|
|
local bravo = self[REVERSE_TRANSLATIONS]
|
|
for base, localized in pairs(alpha) do
|
|
bravo[localized] = base
|
|
end
|
|
end
|
|
|
|
function AceLocale.prototype:GetTranslation(text)
|
|
AceLocale.argCheck(self, text, 1, "string", "number")
|
|
if not rawget(self, TRANSLATIONS) then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
return self[text]
|
|
end
|
|
|
|
function AceLocale.prototype:GetStrictTranslation(text)
|
|
AceLocale.argCheck(self, text, 1, "string", "number")
|
|
local x = rawget(self, TRANSLATIONS)
|
|
if not x then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
local value = rawget(x, text)
|
|
if value == nil then
|
|
AceLocale.error(self, "Translation %q does not exist for locale %s", text, self[CURRENT_LOCALE])
|
|
end
|
|
return value
|
|
end
|
|
|
|
function AceLocale.prototype:GetReverseTranslation(text)
|
|
local x = rawget(self, REVERSE_TRANSLATIONS)
|
|
if not x then
|
|
if not rawget(self, TRANSLATIONS) then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
initReverse(self)
|
|
x = self[REVERSE_TRANSLATIONS]
|
|
end
|
|
local translation = x[text]
|
|
if not translation then
|
|
AceLocale.error(self, "Reverse translation for %q does not exist", text)
|
|
end
|
|
return translation
|
|
end
|
|
|
|
function AceLocale.prototype:GetIterator()
|
|
local x = rawget(self, TRANSLATIONS)
|
|
if not x then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
return next, x, nil
|
|
end
|
|
|
|
function AceLocale.prototype:GetReverseIterator()
|
|
local x = rawget(self, REVERSE_TRANSLATIONS)
|
|
if not x then
|
|
if not rawget(self, TRANSLATIONS) then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
initReverse(self)
|
|
x = self[REVERSE_TRANSLATIONS]
|
|
end
|
|
return next, x, nil
|
|
end
|
|
|
|
function AceLocale.prototype:HasTranslation(text)
|
|
AceLocale.argCheck(self, text, 1, "string", "number")
|
|
local x = rawget(self, TRANSLATIONS)
|
|
if not x then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
return rawget(x, text) and true
|
|
end
|
|
|
|
function AceLocale.prototype:HasReverseTranslation(text)
|
|
local x = rawget(self, REVERSE_TRANSLATIONS)
|
|
if not x then
|
|
if not rawget(self, TRANSLATIONS) then
|
|
AceLocale.error(self, "No translations registered")
|
|
end
|
|
initReverse(self)
|
|
x = self[REVERSE_TRANSLATIONS]
|
|
end
|
|
return x[text] and true
|
|
end
|
|
|
|
function AceLocale.prototype:Debug()
|
|
if not rawget(self, DEBUGGING) then
|
|
return
|
|
end
|
|
local words = {}
|
|
local locales = {"enUS", "ruRU", "deDE", "frFR", "koKR", "zhCN", "zhTW", "esES"}
|
|
local localizations = {}
|
|
DEFAULT_CHAT_FRAME:AddMessage("--- AceLocale Debug ---")
|
|
for _,locale in ipairs(locales) do
|
|
if not self[TRANSLATION_TABLES][locale] then
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q not found", locale))
|
|
else
|
|
localizations[locale] = self[TRANSLATION_TABLES][locale]
|
|
end
|
|
end
|
|
local localeDebug = {}
|
|
for locale, localization in pairs(localizations) do
|
|
localeDebug[locale] = {}
|
|
for word in pairs(localization) do
|
|
if type(localization[word]) == "table" then
|
|
if type(words[word]) ~= "table" then
|
|
words[word] = {}
|
|
end
|
|
for bit in pairs(localization[word]) do
|
|
if type(localization[word][bit]) == "string" then
|
|
words[word][bit] = true
|
|
end
|
|
end
|
|
elseif type(localization[word]) == "string" then
|
|
words[word] = true
|
|
end
|
|
end
|
|
end
|
|
for word in pairs(words) do
|
|
if type(words[word]) == "table" then
|
|
for bit in pairs(words[word]) do
|
|
for locale, localization in pairs(localizations) do
|
|
if not rawget(localization, word) or not localization[word][bit] then
|
|
localeDebug[locale][word .. "::" .. bit] = true
|
|
end
|
|
end
|
|
end
|
|
else
|
|
for locale, localization in pairs(localizations) do
|
|
if not rawget(localization, word) then
|
|
localeDebug[locale][word] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for locale, t in pairs(localeDebug) do
|
|
if not next(t) then
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q complete", locale))
|
|
else
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q missing:", locale))
|
|
for word in pairs(t) do
|
|
DEFAULT_CHAT_FRAME:AddMessage(string.format(" %q", word))
|
|
end
|
|
end
|
|
end
|
|
DEFAULT_CHAT_FRAME:AddMessage("--- End AceLocale Debug ---")
|
|
end
|
|
|
|
setmetatable(AceLocale.prototype, {
|
|
__index = function(self, k)
|
|
if type(k) ~= "table" and k ~= 0 and k ~= "GetLibraryVersion" and k ~= "error" and k ~= "assert" and k ~= "argCheck" and k ~= "pcall" then -- HACK: remove "GetLibraryVersion" and such later.
|
|
AceLocale.error(lastSelf or self, "Translation %q does not exist.", k)
|
|
end
|
|
return nil
|
|
end
|
|
})
|
|
|
|
local function activate(self, oldLib, oldDeactivate)
|
|
AceLocale = self
|
|
|
|
self.frame = oldLib and oldLib.frame or CreateFrame("Frame")
|
|
self.registry = oldLib and oldLib.registry or {}
|
|
self.BASE_TRANSLATIONS = oldLib and oldLib.BASE_TRANSLATIONS or {}
|
|
self.DEBUGGING = oldLib and oldLib.DEBUGGING or {}
|
|
self.TRANSLATIONS = oldLib and oldLib.TRANSLATIONS or {}
|
|
self.BASE_LOCALE = oldLib and oldLib.BASE_LOCALE or {}
|
|
self.TRANSLATION_TABLES = oldLib and oldLib.TRANSLATION_TABLES or {}
|
|
self.REVERSE_TRANSLATIONS = oldLib and oldLib.REVERSE_TRANSLATIONS or {}
|
|
self.STRICTNESS = oldLib and oldLib.STRICTNESS or {}
|
|
self.NAME = oldLib and oldLib.NAME or {}
|
|
self.DYNAMIC_LOCALES = oldLib and oldLib.DYNAMIC_LOCALES or {}
|
|
self.CURRENT_LOCALE = oldLib and oldLib.CURRENT_LOCALE or {}
|
|
|
|
BASE_TRANSLATIONS = self.BASE_TRANSLATIONS
|
|
DEBUGGING = self.DEBUGGING
|
|
TRANSLATIONS = self.TRANSLATIONS
|
|
BASE_LOCALE = self.BASE_LOCALE
|
|
TRANSLATION_TABLES = self.TRANSLATION_TABLES
|
|
REVERSE_TRANSLATIONS = self.REVERSE_TRANSLATIONS
|
|
STRICTNESS = self.STRICTNESS
|
|
NAME = self.NAME
|
|
DYNAMIC_LOCALES = self.DYNAMIC_LOCALES
|
|
CURRENT_LOCALE = self.CURRENT_LOCALE
|
|
|
|
|
|
local GetTime = GetTime
|
|
local timeUntilClear = GetTime() + 5
|
|
scheduleClear = function()
|
|
if next(newRegistries) then
|
|
self.frame:Show()
|
|
timeUntilClear = GetTime() + 5
|
|
end
|
|
end
|
|
|
|
if not self.registry then
|
|
self.registry = {}
|
|
else
|
|
for name, instance in pairs(self.registry) do
|
|
local name = name
|
|
local mt = getmetatable(instance)
|
|
setmetatable(instance, nil)
|
|
instance[NAME] = name
|
|
local strict
|
|
if instance[STRICTNESS] ~= nil then
|
|
strict = instance[STRICTNESS]
|
|
elseif instance[TRANSLATIONS] ~= instance[BASE_TRANSLATIONS] then
|
|
if getmetatable(instance[TRANSLATIONS]).__index == oldLib.prototype then
|
|
strict = true
|
|
end
|
|
end
|
|
instance[STRICTNESS] = strict and true or false
|
|
refixInstance(instance)
|
|
end
|
|
end
|
|
|
|
self.frame:SetScript("OnEvent", scheduleClear)
|
|
self.frame:SetScript("OnUpdate", function() -- (this, elapsed)
|
|
if timeUntilClear - GetTime() <= 0 then
|
|
self.frame:Hide()
|
|
for k in pairs(newRegistries) do
|
|
clearCache(k)
|
|
newRegistries[k] = nil
|
|
k = nil
|
|
end
|
|
end
|
|
end)
|
|
self.frame:UnregisterAllEvents()
|
|
self.frame:RegisterEvent("ADDON_LOADED")
|
|
self.frame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
self.frame:Show()
|
|
|
|
if oldDeactivate then
|
|
oldDeactivate(oldLib)
|
|
end
|
|
end
|
|
|
|
AceLibrary:Register(AceLocale, MAJOR_VERSION, MINOR_VERSION, activate)
|