Module:Lang: Difference between revisionsGive feedback
m fixed fallback_str='en' localizing to current lang instead of en |
Gammaton32 (talk | contribs) No edit summary |
||
| (78 intermediate revisions by 9 users not shown) | |||
| Line 2: | Line 2: | ||
local util_module = require("Module:Utilities") | local util_module = require("Module:Utilities") | ||
local lang_codes_set = mw.loadJsonData("Data:LangCodes.json") | local lang_codes_set = mw.loadJsonData("Data:LangCodes.json") | ||
local dictionary_module = require("Module:Dictionary") | |||
-- Overrides applied to searches by key. Designed to handle edge cases where | -- Overrides applied to searches by key. Designed to handle edge cases where | ||
-- 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 16: | 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 success, data = pcall(mw.loadJsonData, file_name) | local success, data = pcall(mw.loadJsonData, file_name) | ||
if success then | if success then | ||
| Line 28: | Line 51: | ||
end | end | ||
-- | local function process_newlines(text) | ||
if not text or type(text) ~= "string" then return text end | |||
-- | -- Replace "n with newline character | ||
-- | 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 | |||
-- | |||
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 = type(key.preprocess) == "function" and key or mw.getCurrentFrame() | ||
key = | |||
lang_code_override = | local args = key.args | ||
fallback_str = | key = args[1] | ||
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 | |||
local lang_code = lang_code_override | |||
if (lang_code == '' or lang_code == nil) then | |||
lang_code = p.get_lang_code() | |||
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] | |||
-- | -- Some labels use key with "_postvalue_label" instead, so check this if previous search failed | ||
if label == nil and key ~= nil and key:sub(-6) == '_label' then | |||
local postvalue_key = key:sub(1, -7) .. '_postvalue_label' | |||
label = data[postvalue_key] | |||
end | end | ||
if (label == nil) then | |||
-- Apply fallback (without HTML processing for fallback) | |||
local fallback_tooltip = frame:expandTemplate{title = "MissingValveTranslationTooltip"} | |||
local fallback | |||
if (fallback_str == 'en') then | |||
fallback = p.get_string(key, 'en', key .. fallback_tooltip, remove_var_index, item_name) | |||
elseif fallback_str == 'dictionary' then | |||
return dictionary_module.translate(key, lang_code_override) | |||
elseif fallback_str ~= nil then | |||
fallback = fallback_str | |||
else | |||
return '' | |||
end | |||
return fallback .. fallback_tooltip | |||
end | |||
-- Always retrieve the last parameter if string contains "|" | |||
if type(label) == "string" and label:find("|") then | |||
local parts = mw.text.split(label, "|") | |||
label = parts[#parts] | |||
end | |||
-- Remove hashtags | |||
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 lang_code_override = frame.args[2] | |||
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 | |||
if (lang_code == '' or lang_code == nil) then | |||
lang_code = p.get_lang_code() | |||
end | |||
-- Load the language files | |||
local data_en = get_lang_file('en') -- English data | |||
local data_lang = get_lang_file(lang_code) -- Target language data | |||
if (data_lang == nil) then | |||
error("Lang code '%s' does not have a json file", lang_code) | |||
end | |||
-- Search for the key in the English data | |||
local key = nil | |||
for k, v in pairs(data_en) do | |||
if v == label then | |||
key = k -- Find the key corresponding to the label | |||
break | |||
end | |||
end | |||
-- Default to input label if localized string is not found | |||
if (key == nil) then | |||
return label | |||
end | |||
if (data_lang[key] == nil) then | |||
return label | |||
end | |||
return data_lang[key] | |||
end | end | ||
function | 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 | |||
return 'en' | |||
end | |||
return lang_code | return lang_code | ||
end | end | ||
return p | return p | ||