Module:Lang: Difference between revisions

Sur (talk | contribs)
m bugfix with 'dictionary' fallback option
No edit summary
 
(76 intermediate revisions by 8 users not shown)
Line 7: Line 7:
-- the expected key does not have a localization entry
-- the expected key does not have a localization entry
local KEY_OVERRIDES = {
local KEY_OVERRIDES = {
BurnDurationBase_label = 'AfterburnDurationBase_label',
BurnDurationBase_postfix = 'AfterburnDurationBase_postfix',
     MoveSlowPercent_label = 'MovementSlow_label',
     MoveSlowPercent_label = 'MovementSlow_label',
     BonusHealthRegen_label = 'HealthRegen_label',
     BonusHealthRegen_label = 'HealthRegen_label',
Line 17: Line 19:
     EvasionChance_label = 'EvasionChance_Label',
     EvasionChance_label = 'EvasionChance_Label',
     DelayBetweenShots_label = 'DelayBetweenShots_Label',
     DelayBetweenShots_label = 'DelayBetweenShots_Label',
    MoveSlowDuration_postfix = 'EnemyMoveSlowDuration_postfix',
    StaminaRecoveryDisabledDuration_postfix = 'AbilityDuration_postfix',
    PostInvisBuffDuration_label = 'BuffDuration_label',
    PostInvisBuffDuration_postfix = 'BuffDuration_postfix',
    ProjectileFuse_label = 'BellLifetime_label',
    ProjectileFuse_postfix = 'BellLifetime_postfix',
    SelfBuffDuration_label = 'BuffDuration_label',
    SelfBuffDuration_postfix = 'BuffDuration_postfix',
    LifeStealPercentOnHit_label = 'LifestealPercentOnHit_label',
    LifeStealPercentOnHit_postfix = 'LifestealPercentOnHit_postfix',
    AuraFireRateBonus_label = 'FireRateBonus_label',
    AuraFireRateBonus_postfix = 'FireRateBonus_postfix',
    MoveSlowDuration_label = 'SlowDuration_label',
    MoveSlowDuration_postfix = 'SlowDuration_postfix',
DashRange_label = 'TargetDashRadius_label',
DashRange_postfix = 'TargetDashRadius_postfix',
SpeedOnLandDuration_label = 'BuffDuration_label',
SpeedOnLandDuration_postfix = 'BuffDuration_postfix',
SummonCount_label = 'GhoulCount_label',
TetherDuration_label = 'ImmobilizeTrap_CurseDuration_label'
}
}


function get_lang_file(lang_code)
function get_lang_file(lang_code)
local file_name = string.format("Data:Lang_%s.json", lang_code)
    local file_name = string.format("Data:Lang_%s.json", lang_code)
     local success, data = pcall(mw.loadJsonData, file_name)
     local success, data = pcall(mw.loadJsonData, file_name)
     if success then
     if success then
Line 29: Line 51:
end
end


-- Get a localized string by the raw key
local function process_newlines(text)
p.get_string = function(key, lang_code_override, fallback_str, remove_var_index)
    if not text or type(text) ~= "string" then return text end
-- If called internally (direct Lua call), args will be passed directly.
    -- Replace "n with newline character
     -- If called from wikitext, `key` will be the `frame` object, and we get args from `frame.args`.
    return text:gsub('"n', '<br/>')
end
 
local function process_html_tags(text, frame)
    if not text or type(text) ~= "string" then return text end
   
    local replacements = {
    BonusFireRate = {icon='{{Icon/Brown|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    BonusMoveSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
    BonusSprintSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Sprint speed|20px]]}}', label_color='#ffefd7', link='Sprint speed'},
    BonusSpiritDamage = {icon='{{Icon/NoColor|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    BonusWeaponDamage = {icon='{{Icon/NoColor|[[File:Bullet Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
    bullet_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
    bullet_damage = {icon='{{Icon/NoColor|[[File:Bullet_damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
    BulletResist = {icon='{{Icon/Brown|[[File:Bullet_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#ec981a', link='Damage_Resistance'},
    cast = {icon='{{Icon/White|[[File:Cast.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    CombatBarrier = {icon='{{Icon/White|[[File:Bullet Armor (item).png|link=Damage_Resistance|20px]]}}', label_color='#ffefd7', link='Barrier'},
    cooldown = {icon='{{Icon/White|[[File:AttributeIconTechDuration.png|link=Ability Cooldown|20px]]}}', label_color='#ffefd7', link='Ability Cooldown'},
    damage = {icon='{{Icon/NoColor|[[File:Damage heart.png|link=Damage|20px]]}}', label_color='#ec981a', link='Damage'},
    DamageAmp = {icon='{{Icon/White|[[File:Damage heart.png|link=Damage Amp|20px]]}}', label_color='#ffefd7', link='Damage Amp'},
    FireRate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    fire_rate = {icon='{{Icon/White|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    Heal = {icon='{{Icon/Green|[[File:Healing.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
    Healing = {icon='{{Icon/Green|[[File:Healing.png|link=Healing|20px]]}}', label_color='#13f278', link='Healing'},
    Immobilize = {icon='{{Icon/White|[[File:Immobilize.png|link=Status Effects#Immobilized|20px]]}}', label_color='#ffefd7', link='Status Effects#Immobilized'},
    invisible = {icon='{{Icon/White|[[File:Invisible.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    KnockBack = {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    KnockUp =  {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    MaxHealth = {icon='{{Icon/Green|[[File:Healing.png|link=Health|20px]]}}', label_color='#13f278', link='Health'},
    MeleeDamage = {icon='{{Icon/Brown|[[File:Melee damage.png|link=Melee Damage|20px]]}}', label_color='#ec981a', link='Melee Damage'},
    MoveSpeed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
    move_speed = {icon='{{Icon/Green|[[File:Move_speed.png|link=Move speed|20px]]}}', label_color='#ffefd7', link='Move speed'},
    Pull =      {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    Pulling =  {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    Pulls =    {icon='{{Icon/NoColor|[[File:Displacement.png|link=|20px]]}}', label_color='#ffefd7', link='Status_Effects'},
    PureDamage = {icon='{{Icon/NoColor|[[File:Damage.png|link=Pure Damage|20px]]}}', label_color='#ffefd7', link='Pure Damage'},
    ReducedFireRate = {icon='{{Icon/Brown|[[File:Fire_Rate.png|link=Fire Rate|20px]]}}', label_color='#ffefd7', link='Fire Rate'},
    Regen = {icon='{{Icon/Green|[[File:Healing.png|link=Health_Regen|20px]]}}', label_color='#13f278', link='Health_Regen'},
    revealed = {icon='{{Icon/White|[[File:Revealed.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    silence = {icon='{{Icon/White|[[File:Silence.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    Silence = {icon='{{Icon/Purple|[[File:Silence.png|link=Silence|20px]]}}', label_color='#bc8ee8', link='Silence'},
    Slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
    slow = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow|20px]]}}', label_color='#ffefd7', link='Movement_Slow'},
    SlowResistance = {icon='{{Icon/White|[[File:MoveSlow.png|link=Movement_Slow#Movement_Slow_Resist|20px]]}}', label_color='#ffefd7', link='Movement_Slow#Movement_Slow_Resist'},
    Spirit = {icon='{{Icon/NoColor|[[File:Spirit_icon.png|link=Spirit Power|20px]]}}', label_color='#bc8ee8', link='Spirit Power'},
    SpiritDamage = {icon='{{Icon/NoColor|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    SpiritDPS = {icon='{{Icon/NoColor|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    SpiritIcon = {icon='{{Icon/NoColor|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}', link='Spirit Damage'},
    SpiritResist = {icon='{{Icon/Purple|[[File:Spirit_Armor.png|link=Damage_Resistance|20px]]}}', label_color='#bc8ee8', link='Damage_Resistance'},
    StatDesc_TechPower = {icon='{{Icon/NoColor|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}', label_color='#bc8ee8', link='Spirit Damage'},
    Stun = {icon='{{Icon/White|[[File:Status_Stun.png|link=Stun|20px]]}}', link='Stun'},
    stun = {icon='{{Icon/White|[[File:Stun.png|link=|20px]]}}', label_color='#ffefd7', link=''},
    tech_armor_up = {icon='{{Icon/White|[[File:Barrier.png|link=Barrier|20px]]}}', label_color='#ffefd7', link='Barrier'},
    tech_damage = {icon='{{Icon/NoColor|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}', link='Spirit Damage'},
    WeaponDamage = {icon='{{Icon/NoColor|[[File:Bullet Damage.png|link=Weapon Damage|20px]]}}', label_color='#ec981a', link='Weapon Damage'},
    Heal = {icon='{{Icon/Green|[[File:Healing.png|link=Healing|20px]]}}', label_color='#13f278', link='Healing'},
    Heals = {icon='{{Icon/Green|[[File:Healing.png|link=Healing|20px]]}}', label_color='#13f278', link='Healing'},
}
 
    -- Process HTML spans
    text = text:gsub('<span class="highlight">(.-)</span>', "<span style= font-weight:bold>%1</span>")
    text = text:gsub('<span class="highlight_spirit">(.-)</span>', '<span style="font-weight:bold; color:#bc8ee8">%1</span>')
    text = text:gsub('<span class="highlight_courage">(.-)</span>', '<span style="font-weight:bold; color:#ec981a">%1</span>')
    text = text:gsub('<span class="diminish">(.-)</span>', '<span style="font-style: italic;color:#C0C0C0">%1</span>')
    text = text:gsub('<span class=".-">(.-)</span>', '%1')
    text = text:gsub('<Panel class=\"AbilityPropertyIcon StatDesc_TechPower\">', '{{Icon/Purple|[[File:Spirit damage.png|link=Spirit Damage|20px]]}}')
    text = text:gsub('</Panel>', '')
   
    -- Process panel tags
text = text:gsub('<Panel class=\"AbilityPropertyIcon prop_(.-)\">', function(key)
    local replacement = replacements[key]
    if replacement then
        local label_color = replacement.label_color or 'inherit'
        local icon = replacement.icon or ''
        local link = replacement.link or ''
        -- pre-process string, then check to omit link if the string is empty
        local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
       
        if translated == '' then
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s</span>', label_color, icon)
        else
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s [[%s|%s]]</span>',
                label_color,
                icon,
                link,
                translated)
        end
    else
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing attribute definition - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
    end
end)
   
    -- Process citadel_inline_attribute tags
text = text:gsub("{g:citadel_inline_attribute:'(.-)'}", function(key)
    local replacement = replacements[key]
    if replacement then
        local label_color = replacement.label_color or 'inherit'
        local icon = replacement.icon or ''
        local link = replacement.link or ''
        -- pre-process string, then check to omit link if the string is empty
        local translated = frame:preprocess('{{#invoke:Lang|get_string|InlineAttribute_' .. key .. '}}')
       
        if translated == '' then
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s</span>', label_color, icon)
        else
            return string.format('<span class="no-blue-link" style="text-wrap:nowrap; font-weight:bold; color:%s;">%s [[%s|%s]]</span>',
                label_color,
                icon,
                link,
                translated)
        end
    else
        return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing attribute definition - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
    end
end)
   
    -- List for keybinds with mismatched names. Replace with the correct name under "citadel_keybind_[key]" in Data:Lang
    keybinds = {
        MoveForward = "forward",
        AltCast = "alt_cast",
        }
   
    -- Process citadel_binding/citadel_keybind tags to replace with keybind strings/templates
text = text:gsub("{g:citadel_binding:'(.-)'}", function(key)
  -- convert key to lowercase
        local lower_key = key:lower()
        -- look up key with Module:Lang. check if it has a replacement key first
        local replacement = keybinds[key] or lower_key
        local translated = frame:preprocess('{{#invoke:Lang|get_string|citadel_keybind_'.. replacement ..'}}')
  if translated ~= nil and translated ~= '' then
        return "'''" .. translated .. "'''"
        else
            return '<span style="color:red;font-weight:bold;border-bottom:1px dotted red;" title="Missing keybind - Add \''..key..'\' to local replacements in [Module:Lang]">['..key..']</span>'
        end
end)
     -- Force spaces around keybinds where the source data omits them
    text = text:gsub("'''([a-zA-Z])", "''' %1")
 
    -- Clean up extra spaces between a keybind and following punctuation
    text = text:gsub("'''%s+:", "''':")
 
return frame:preprocess(text)
end


     -- Handle the case where it's called via #invoke (i.e., from wikitext)
 
function p.get_string(key, lang_code_override, fallback_str, remove_var_index, item_name)
     -- Get frame object (either passed directly or via first argument)
    local frame
     if type(key) == "table" and key.args then
     if type(key) == "table" and key.args then
         local frame = key
        frame = type(key.preprocess) == "function" and key or mw.getCurrentFrame()
         key = frame.args[1]
       
         lang_code_override = frame.args["lang_code_override"]
         local args = key.args
         fallback_str = frame.args["fallback_str"]
         key = args[1]
         remove_var_index = frame.args["remove_var_index"]
         lang_code_override = args["lang_code_override"]
         fallback_str = args["fallback_str"]
         remove_var_index = args["remove_var_index"]
        item_name = args["item_name"]
    else
        frame = mw.getCurrentFrame()
     end
     end


-- Determine lang_code if not overridden
    -- Determine lang_code if not overridden
local lang_code = lang_code_override
    local lang_code = lang_code_override
if (lang_code == '' or lang_code == nil) then
    if (lang_code == '' or lang_code == nil) then
    lang_code = get_lang_code()
        lang_code = p.get_lang_code()
end
    end
 
    -- Retrieve lang data
    local data = get_lang_file(lang_code)
    if (data == nil) then
        return string.format("Lang code '%s' does not have a json file", lang_code)   
    end
   
    -- Localize
    local label = data[KEY_OVERRIDES[key] or key]


-- Retrieve lang data
-- Some labels use key with "_postvalue_label" instead, so check this if previous search failed
local data = get_lang_file(lang_code)
if label == nil and key ~= nil and key:sub(-6) == '_label' then
if (data == nil) then
    local postvalue_key = key:sub(1, -7) .. '_postvalue_label'
return string.format("Lang code '%s' does not have a json file", lang_code)
    label = data[postvalue_key]
end
end
-- Localize
    if (label == nil) then
local label = data[KEY_OVERRIDES[key] or key]
        -- Apply fallback (without HTML processing for fallback)
if (label == nil) then
        local fallback_tooltip = frame:expandTemplate{title = "MissingValveTranslationTooltip"}
-- Apply fallback
        local fallback
local fallback_tooltip = mw.getCurrentFrame():expandTemplate{title = "MissingValveTranslationTooltip"}
        if (fallback_str == 'en') then
local fallback
            fallback = p.get_string(key, 'en', key .. fallback_tooltip, remove_var_index, item_name)
if (fallback_str == 'en') then
        elseif fallback_str == 'dictionary' then
fallback = p.get_string(key, 'en', key .. fallback_tooltip, remove_var_index, upper_lower)
            return dictionary_module.translate(key, lang_code_override)
elseif fallback_str == 'dictionary' then
        elseif fallback_str ~= nil then
return dictionary_module.translate(key, lang_code_override)
            fallback = fallback_str
elseif fallback_str ~= nil then
        else
fallback = fallback_str
            return ''
else
        end
return ''
        return fallback .. fallback_tooltip
end
    end
return fallback .. fallback_tooltip
   
end
    -- Always retrieve the last parameter if string contains "|"
    if type(label) == "string" and label:find("|") then
-- Apply remove_var
        local parts = mw.text.split(label, "|")
if (remove_var_index ~= nil) then  
        label = parts[#parts]
label = util_module.remove_var(label, remove_var_index)
    end
end
   
  -- Remove hashtags
return label
  if type(label) == "string" and label:find("#") then
        local parts = mw.text.split(label, "#")
        label = parts[#parts]
    end
 
    -- Apply remove_var
    if (remove_var_index ~= nil) then  
        label = util_module.remove_var(label, remove_var_index)
    end
   
    -- Process variables if item_name is provided
    if item_name and item_name ~= '' then
        local item_data_module = require('Module:ItemData')
        label = label:gsub("{s:([^}]+)}", function(variable_name)
            -- Get the value from ItemData
            local value = item_data_module.get_prop({args = {item_name, variable_name}})
           
            -- If value exists, remove non-number symbols (but keep + and -)
            if value then
                value = value:gsub("[^0-9+-.]", "")
                -- Return empty string if nothing left, otherwise return the filtered value
                return value ~= "" and value or variable_name
            else
                return variable_name -- Fallback to variable name if value not found
            end
        end)
    end
   
    -- Process HTML and return as "raw" HTML
    label = process_newlines(label)
    label = process_html_tags(label, frame)
    -- Create HTML object for safe output
    local html = mw.html.create()
    html:wikitext(label)
    return tostring(html)
end
end


-- Search for a localized string using its English label
-- Search for a localized string using its English label
p.search_string = function(frame)
p.search_string = function(frame)
local label = frame.args[1]
    local label = frame.args[1]
local lang_code_override = frame.args[2]
    local lang_code_override = frame.args[2]


return p._search_string(label, lang_code_override)
    local result = p._search_string(label, lang_code_override)
    result = process_newlines(result)
    return result
end
end


-- search_string, but for internal use by other modules
-- search_string, but for internal use by other modules
p._search_string = function(label, lang_code_override)
p._search_string = function(label, lang_code_override)
lang_code = lang_code_override
    lang_code = lang_code_override
if (lang_code == '' or lang_code == nil) then
    if (lang_code == '' or lang_code == nil) then
lang_code = get_lang_code()
        lang_code = p.get_lang_code()
end
    end


-- Load the language files
    -- Load the language files
local data_en = get_lang_file('en')  -- English data
    local data_en = get_lang_file('en')  -- English data
local data_lang = get_lang_file(lang_code)  -- Target language data
    local data_lang = get_lang_file(lang_code)  -- Target language data


if (data_lang == nil) then
    if (data_lang == nil) then
return string.format("Lang code '%s' does not have a json file", lang_code)
        error("Lang code '%s' does not have a json file", lang_code)  
end
    end
   
-- Search for the key in the English data
    -- Search for the key in the English data
local key = nil
    local key = nil
for k, v in pairs(data_en) do
    for k, v in pairs(data_en) do
if v == label then
        if v == label then
key = k  -- Find the key corresponding to the label
            key = k  -- Find the key corresponding to the label
break
            break
end
        end
end
    end


if (key == nil) then
    -- Default to input label if localized string is not found
return string.format("English label '%s' not found", label)
    if (key == nil) then
end
        return label
    end
if (data_lang[key] == nil) then
    if (data_lang[key] == nil) then
return label
        return label
end
    end


return data_lang[key]
    return data_lang[key]
end
end


function get_lang_code()
p.get_lang_code = function()
     local title = mw.title.getCurrentTitle()
     local title = mw.title.getCurrentTitle()
     local lang_code = title.fullText:match(".*/(.*)$")
     local lang_code = title.fullText:match(".*/(.*)$")
      
      
if lang_code == nil or lang_codes_set[lang_code] == nil then
    if lang_code == nil or lang_codes_set[lang_code] == nil then
return 'en'
        return 'en'  
end
    end
       
     return lang_code
     return lang_code
end
end


return p
return p