模块:GameText

来自Terraria Wiki
跳转到导航 跳转到搜索
亦可参看英文模块:Module:GameText。那边可能有更完整或更正确的信息。

This module is used to display text used in Terraria, exactly as in-game. It provides the functionality of the {{gameText}} template and can also be used from within other modules.

Usage

Wikitext

The module can be called from wikitext with the functions listed below.

get

{{#invoke:GameText| get | lang = <language> }}

Displays the string associated with the given key. If the given key refers to a group of strings (e.g. WorldGeneration), then it displays all strings in that group separated by the character .

This function is generally only intended to be called from {{gameText}}. The parameters passed in the transclusion of that template are recognized and handled by this function. See the template documentation for details about the parameters.

getRaw

{{#invoke:GameText| getRaw | <key> | prefix = <output prefix> | postfix = <output postfix> | <placeholder replacements> | lang = <language> }}

Displays the string exactly as it is defined in the source language JSON file. The get function wraps its output in <span class="gameText"> and replaces linebreaks (\n) with HTML linebreaks (<br/>) for a more intuitive output. This function does not do that.

getSection

{{#invoke:GameText| getSection | <key> | lang = <language> }}

Returns all keys and their strings that are in the group of the given key (e.g. WorldGeneration) as an array from Extension:ArrayFunctions. This function is generally only intended to be called from {{gameText/section}}.

Examples
Code Result
{{#af_print:{{#invoke:GameText|getSection|WorldGeneration}}}}
  • OceanSand: Generating ocean sand
  • WavyCaves: Generating wavy caves

getSectionList

{{#invoke:GameText| getSectionList }}

Returns all top-level keys from the database as an array from Extension:ArrayFunctions. This function is generally only intended to be called from {{gameText/sectionList}}.

Examples
Code Result
{{#af_print:{{#invoke:GameText|getSectionList}}}}
  • 1: LucyTheAxe_ChoppedCactus
  • 2: CreditsRollCategory_Sound
  • 3: BestiaryGirlSpecialText
  • 4: BunnyNames_Lop
  • 5: WorkshopTags
  • 6: GolferQuestsChatterApprentice
  • 7: CatNames_Black
  • 8: ItemTooltip
  • 9: BunnyNames_White
  • 10: StylistChatter
  • 11: MechanicSpecialText
  • 12: RandomWorldName_Location
  • 13: CreditsRollCategory_SpecialThanksto
  • 14: ItemVariant
  • 15: TownNPCMood_DD2Bartender
  • 16: DogNames_Corgi
  • 17: SantaSpecialText
  • 18: CLI
  • 19: ItemVariantCondition
  • 20: CatNames_Siamese
  • 21: PainterNames
  • 22: Social
  • 23: BunnyChatter
  • 24: AnglerSpecialText
  • 25: EmojiCommand
  • 26: TownNPCMood_Wizard
  • 27: SlimeNames_Blue
  • 28: CreditsRollCategory_Marketing
  • 29: DungeonDefenders2
  • 30: TownNPCMood_BestiaryGirlTransformed
  • 31: RandomWorldName_Composition
  • 32: TownNPCMood_BestiaryGirl
  • 33: GameUI
  • 34: TownNPCMood_Painter
  • 35: TownNPCMood_Truffle
  • 36: TownNPCMood_Golfer
  • 37: CatNames_RussianBlue
  • 38: SlimeRainbowChatter
  • 39: MapObject
  • 40: ClothierNames
  • 41: UI
  • 42: DryadSpecialText
  • 43: CreditsRollCategory_Creator
  • 44: TownNPCMood_Steampunker
  • 45: BartenderHelpText
  • 46: TownNPCMood_Pirate
  • 47: TownNPCMood_Guide
  • 48: TownNPCMood_Mechanic
  • 49: SpecialWorldName
  • 50: LoadingTips_GamePad
  • 51: SlimeCopperChatter
  • 52: TownNPCMood_Princess
  • 53: LegacyTooltip
  • 54: ArmsDealerNames
  • 55: DryadNames
  • 56: GuideNames
  • 57: LucyTheAxe_Storage
  • 58: ChatCommandDescription
  • 59: Achievements
  • 60: TownNPCMood_Dryad
  • 61: Prefix
  • 62: StardewTalk
  • 63: SlimeRedChatter
  • 64: CreativePowers
  • 65: LucyTheAxe_ChoppedTree
  • 66: TownNPCMood_Demolitionist
  • 67: Controls
  • 68: ProjectileName
  • 69: LegacyMenu
  • 70: BartenderSpecialText
  • 71: TownNPCMood_Stylist
  • 72: DogChatter
  • 73: TownNPCMood_PartyGirl
  • 74: AnglerNames
  • 75: SlimeNames_Yellow
  • 76: LegacyWorldGen
  • 77: TownNPCMood_Nurse
  • 78: TravellingMerchantSpecialText
  • 79: BuffDescription
  • 80: TownNPCMood_TaxCollector
  • 81: GolferQuestsChatterMaster
  • 82: GuideChatter
  • 83: PirateChatter
  • 84: NurseNames
  • 85: Currency
  • 86: GolferQuestsChatterBeginner
  • 87: StylistSpecialText
  • 88: GolferQuestsChatterJourneyman
  • 89: SkeletonMerchantSpecialText
  • 90: Enemies
  • 91: RichPresence
  • 92: SlimePurpleChatter
  • 93: DogNames_Beagle
  • 94: DeathTextGeneric
  • 95: CatNames_OrangeTabby
  • 96: LegacyInterface
  • 97: Language
  • 98: TownNPCMood
  • 99: LucyTheAxe_GemTree
  • 100: Bestiary_Events
  • 101: TownNPCMood_Merchant
  • 102: DogNames_Dalmation
  • 103: Net
  • 104: PrincessSpecialText
  • 105: ChatCommand
  • 106: GolferSpecialText
  • 107: Bestiary_Biomes
  • 108: Friends
  • 109: TownNPCHousingFailureReasons
  • 110: BestiaryGirlNames
  • 111: WitchDoctorSpecialText
  • 112: DeathSource
  • 113: RandomWorldName_Adjective
  • 114: PainterSpecialText
  • 115: CreditsRollCategory_EndingNotes
  • 116: Key
  • 117: BuffName
  • 118: PartyGirlSpecialText
  • 119: MerchantNames
  • 120: TaxCollectorNames
  • 121: DogNames_PitBull
  • 122: CreditsRollCategory_BusinessDevelopment
  • 123: Bestiary_BiomeText
  • 124: CreditsRollCategory_PublicRelations
  • 125: CreditsRollCategory_Programming
  • 126: AnglerQuestChatter
  • 127: LucyTheAxe_Idle
  • 128: LegacyChestType2
  • 129: BartenderNames
  • 130: CatNames_Silver
  • 131: SteampunkerSpecialText
  • 132: TruffleSpecialText
  • 133: NPCName
  • 134: DemolitionistSpecialText
  • 135: SlimeBlueChatter
  • 136: WizardSpecialText
  • 137: GoblinTinkererSpecialText
  • 138: ClothierSpecialText
  • 139: CreditsRollCategory_Webmaster
  • 140: SlimeNames_Rainbow
  • 141: MerchantSpecialText
  • 142: TownNPCMood_Cyborg
  • 143: SlimeYellowChatter
  • 144: SkeletonMerchantNames
  • 145: MechanicChatter
  • 146: NurseSpecialText
  • 147: GuideSpecialText
  • 148: GoblinTinkererChatter
  • 149: AnglerChatter
  • 150: CyborgChatter
  • 151: CreditsRollCategory_Designer
  • 152: TownNPCMood_Angler
  • 153: SteampunkerNames
  • 154: BestiaryInfo
  • 155: WizardNames
  • 156: AssetRejections
  • 157: GuideHelpText
  • 158: CommonItemTooltip
  • 159: SlimeNames_Purple
  • 160: LegacyMultiplayer
  • 161: TownNPCMood_Clothier
  • 162: TownNPCMood_DyeTrader
  • 163: SlimeGreenChatter
  • 164: RandomWorldName_Noun
  • 165: NurseChatter
  • 166: LegacyChestType
  • 167: CreditsRollCategory_Graphics
  • 168: PrincessChatter
  • 169: BestiaryGirlLycantropeChatter
  • 170: CaptureBiomeChoice
  • 171: BestiaryGirlChatter
  • 172: EmojiName
  • 173: BartenderChatter
  • 174: DyeTraderSpecialText
  • 175: LucyTheAxe_PickedUp
  • 176: Game
  • 177: SlimeNames_Copper
  • 178: SlimeNames_Red
  • 179: GuideHelpTextSpecific
  • 180: SlimeNames_Old
  • 181: CreditsRollCategory_Playtesting
  • 182: PartyGirlNames
  • 183: SlimeNames_Green
  • 184: GameTitle
  • 185: BunnyNames_Flemish
  • 186: TownNPCMood_WitchDoctor
  • 187: BunnyNames_Silver
  • 188: CreditsRollCategory_Dialog
  • 189: GolferChatter
  • 190: BunnyNames_Angora
  • 191: DogNames_Husky
  • 192: DogNames_Labrador
  • 193: CatNames_White
  • 194: BunnyNames_Dutch
  • 195: ItemName
  • 196: Workshop
  • 197: PirateNames
  • 198: LoadingTips_Default
  • 199: SlimeOldChatter
  • 200: Bestiary_ItemDropConditions
  • 201: TownNPCMoodBiomes
  • 202: CommonBestiaryFlavor
  • 203: PrincessNames
  • 204: DeathText
  • 205: PirateSpecialText
  • 206: GolferNames
  • 207: LegacyDialog
  • 208: RandomWorldName_Legacy
  • 209: Bestiary_FlavorText
  • 210: LegacyDresserType
  • 211: ArmsDealerSpecialText
  • 212: Misc
  • 213: TaxCollectorSpecialText
  • 214: LucyTheAxe_ThrownAway
  • 215: LegacyMisc
  • 216: TravelingMerchantNames
  • 217: LoadingTips_Keyboard
  • 218: StylistNames
  • 219: WitchDoctorNames
  • 220: CyborgNames
  • 221: CreditsRollCategory_QualityAssurance
  • 222: DyeTraderNames
  • 223: AnglerQuestText
  • 224: CyborgSpecialText
  • 225: TruffleNames
  • 226: CreditsRollCategory_Music
  • 227: Bestiary_Invasions
  • 228: Announcement
  • 229: MechanicNames
  • 230: DemolitionistNames
  • 231: TownNPCMood_ArmsDealer
  • 232: TownNPCMood_GoblinTinkerer
  • 233: CatChatter
  • 234: PaintingArtist
  • 235: Bestiary_Times
  • 236: TitleLinks
  • 237: CreditsRollCategory_ExecutiveProducer
  • 238: ArmorSetBonus
  • 239: GoblinTinkererNames
  • 240: TownNPCMood_SantaClaus
  • 241: Error
  • 242: WorldGeneration

listAll

{{#invoke:GameText| listAll | <key> | lang = <language> }}

Displays all keys and their strings that are in the group of the given key (e.g. WorldGeneration), separated by the character ¦. The separator between each key and string is . If no key is provided, then merely lists all keys in the entire database.

Examples
Code Result
{{#invoke:GameText|listAll|WorldGeneration}} OceanSand₪Generating ocean sand¦WavyCaves₪Generating wavy caves

listKeys

{{#invoke:GameText| listKeys | <key> | lang = <language> }}

Similar to the listAll function, except that only the keys are displayed in any case, never the strings.

Examples
Code Result
{{#invoke:GameText|listKeys|WorldGeneration}} OceanSand¦WavyCaves
{{#invoke:GameText|listKeys}} LucyTheAxe_ChoppedCactus¦CreditsRollCategory_Sound¦BestiaryGirlSpecialText¦BunnyNames_Lop¦WorkshopTags¦GolferQuestsChatterApprentice¦CatNames_Black¦ItemTooltip¦BunnyNames_White¦StylistChatter¦MechanicSpecialText¦RandomWorldName_Location¦CreditsRollCategory_SpecialThanksto¦ItemVariant¦TownNPCMood_DD2Bartender¦DogNames_Corgi¦SantaSpecialText¦CLI¦ItemVariantCondition¦CatNames_Siamese¦PainterNames¦Social¦BunnyChatter¦AnglerSpecialText¦EmojiCommand¦TownNPCMood_Wizard¦SlimeNames_Blue¦CreditsRollCategory_Marketing¦DungeonDefenders2¦TownNPCMood_BestiaryGirlTransformed¦RandomWorldName_Composition¦TownNPCMood_BestiaryGirl¦GameUI¦TownNPCMood_Painter¦TownNPCMood_Truffle¦TownNPCMood_Golfer¦CatNames_RussianBlue¦SlimeRainbowChatter¦MapObject¦ClothierNames¦UI¦DryadSpecialText¦CreditsRollCategory_Creator¦TownNPCMood_Steampunker¦BartenderHelpText¦TownNPCMood_Pirate¦TownNPCMood_Guide¦TownNPCMood_Mechanic¦SpecialWorldName¦LoadingTips_GamePad¦SlimeCopperChatter¦TownNPCMood_Princess¦LegacyTooltip¦ArmsDealerNames¦DryadNames¦GuideNames¦LucyTheAxe_Storage¦ChatCommandDescription¦Achievements¦TownNPCMood_Dryad¦Prefix¦StardewTalk¦SlimeRedChatter¦CreativePowers¦LucyTheAxe_ChoppedTree¦TownNPCMood_Demolitionist¦Controls¦ProjectileName¦LegacyMenu¦BartenderSpecialText¦TownNPCMood_Stylist¦DogChatter¦TownNPCMood_PartyGirl¦AnglerNames¦SlimeNames_Yellow¦LegacyWorldGen¦TownNPCMood_Nurse¦TravellingMerchantSpecialText¦BuffDescription¦TownNPCMood_TaxCollector¦GolferQuestsChatterMaster¦GuideChatter¦PirateChatter¦NurseNames¦Currency¦GolferQuestsChatterBeginner¦StylistSpecialText¦GolferQuestsChatterJourneyman¦SkeletonMerchantSpecialText¦Enemies¦RichPresence¦SlimePurpleChatter¦DogNames_Beagle¦DeathTextGeneric¦CatNames_OrangeTabby¦LegacyInterface¦Language¦TownNPCMood¦LucyTheAxe_GemTree¦Bestiary_Events¦TownNPCMood_Merchant¦DogNames_Dalmation¦Net¦PrincessSpecialText¦ChatCommand¦GolferSpecialText¦Bestiary_Biomes¦Friends¦TownNPCHousingFailureReasons¦BestiaryGirlNames¦WitchDoctorSpecialText¦DeathSource¦RandomWorldName_Adjective¦PainterSpecialText¦CreditsRollCategory_EndingNotes¦Key¦BuffName¦PartyGirlSpecialText¦MerchantNames¦TaxCollectorNames¦DogNames_PitBull¦CreditsRollCategory_BusinessDevelopment¦Bestiary_BiomeText¦CreditsRollCategory_PublicRelations¦CreditsRollCategory_Programming¦AnglerQuestChatter¦LucyTheAxe_Idle¦LegacyChestType2¦BartenderNames¦CatNames_Silver¦SteampunkerSpecialText¦TruffleSpecialText¦NPCName¦DemolitionistSpecialText¦SlimeBlueChatter¦WizardSpecialText¦GoblinTinkererSpecialText¦ClothierSpecialText¦CreditsRollCategory_Webmaster¦SlimeNames_Rainbow¦MerchantSpecialText¦TownNPCMood_Cyborg¦SlimeYellowChatter¦SkeletonMerchantNames¦MechanicChatter¦NurseSpecialText¦GuideSpecialText¦GoblinTinkererChatter¦AnglerChatter¦CyborgChatter¦CreditsRollCategory_Designer¦TownNPCMood_Angler¦SteampunkerNames¦BestiaryInfo¦WizardNames¦AssetRejections¦GuideHelpText¦CommonItemTooltip¦SlimeNames_Purple¦LegacyMultiplayer¦TownNPCMood_Clothier¦TownNPCMood_DyeTrader¦SlimeGreenChatter¦RandomWorldName_Noun¦NurseChatter¦LegacyChestType¦CreditsRollCategory_Graphics¦PrincessChatter¦BestiaryGirlLycantropeChatter¦CaptureBiomeChoice¦BestiaryGirlChatter¦EmojiName¦BartenderChatter¦DyeTraderSpecialText¦LucyTheAxe_PickedUp¦Game¦SlimeNames_Copper¦SlimeNames_Red¦GuideHelpTextSpecific¦SlimeNames_Old¦CreditsRollCategory_Playtesting¦PartyGirlNames¦SlimeNames_Green¦GameTitle¦BunnyNames_Flemish¦TownNPCMood_WitchDoctor¦BunnyNames_Silver¦CreditsRollCategory_Dialog¦GolferChatter¦BunnyNames_Angora¦DogNames_Husky¦DogNames_Labrador¦CatNames_White¦BunnyNames_Dutch¦ItemName¦Workshop¦PirateNames¦LoadingTips_Default¦SlimeOldChatter¦Bestiary_ItemDropConditions¦TownNPCMoodBiomes¦CommonBestiaryFlavor¦PrincessNames¦DeathText¦PirateSpecialText¦GolferNames¦LegacyDialog¦RandomWorldName_Legacy¦Bestiary_FlavorText¦LegacyDresserType¦ArmsDealerSpecialText¦Misc¦TaxCollectorSpecialText¦LucyTheAxe_ThrownAway¦LegacyMisc¦TravelingMerchantNames¦LoadingTips_Keyboard¦StylistNames¦WitchDoctorNames¦CyborgNames¦CreditsRollCategory_QualityAssurance¦DyeTraderNames¦AnglerQuestText¦CyborgSpecialText¦TruffleNames¦CreditsRollCategory_Music¦Bestiary_Invasions¦Announcement¦MechanicNames¦DemolitionistNames¦TownNPCMood_ArmsDealer¦TownNPCMood_GoblinTinkerer¦CatChatter¦PaintingArtist¦Bestiary_Times¦TitleLinks¦CreditsRollCategory_ExecutiveProducer¦ArmorSetBonus¦GoblinTinkererNames¦TownNPCMood_SantaClaus¦Error¦WorldGeneration

printTable

{{#invoke:GameText| printTable | lang = <language> }}

Displays a tabular visualization of the entire database, where the left column is the key and the right column is the string. The keys are ordered alphabetically.

purge

{{#invoke:GameText| purge | lang = <language> }}

Purges the cache of the game text database for the given language. This is necessary after making a change to the database. See Module:GameText/loaddata for more details.

Other modules

The module can be called from another module with the functions listed below.

getText

require('Module:GameText').getText('<key>', '<language>', {<placeholder replacements>})

Returns the string associated with the given key. The placeholder replacements should be in a literal translation table, i.e. {['<placeholder>']='<replacement>'}.

Examples
Code Variable result
local gameText = require('Module:GameText')
local result = gameText.getText('BuffDescription.ObsidianSkin')
'Immune to lava'
local gameText = require('Module:GameText')
local result = gameText.getText('LegacyDialog.19')
"{PlayerName} is it? I've heard good things, friend!"
local gameText = require('Module:GameText')
local result = gameText.getText('LegacyDialog.19', 'en', { ['{PlayerName}'] = 'myName' })
"myName is it? I've heard good things, friend!"
local gameText = require('Module:GameText')
local result = gameText.getText('Misc.ResolutionChanged')
'Resolution changed to: {0}x{1}.'
local gameText = require('Module:GameText')
local result = gameText.getText('Misc.ResolutionChanged', 'en', { ['{0}'] = '1920', ['{1}'] = '1080' })
'Resolution changed to: 1920x1080.'

getData

require('Module:GameText').getData('<key>', '<language>')

Returns the string or table associated with the given key. Placeholders are never replaced and condition marks like {?Homeless} are kept in the strings.

Examples
Code Variable result
local gameText = require('Module:GameText')
local result = gameText.getData('BuffDescription.ObsidianSkin')
'Immune to lava'
local gameText = require('Module:GameText')
local result = gameText.getData('WorldGeneration')
{
    ["OceanSand"] = "Generating ocean sand",
    ["WavyCaves"] = "Generating wavy caves"
}
local gameText = require('Module:GameText')
local result = gameText.getData('BartenderChatter')
{
    ["Chatter_1"] = "I've got the cure for what ails ya! Get it? Ale? No?",
    ["Chatter_2"] = "They say you're strong, well, I know strong. Let's see if you measure up.",
    ["Chatter_3"] = "Back where I'm from, we only serve Root Beer...",
    ["Chatter_4"] = "This is a big upgrade from wiping that table all day.",
    ["Chatter_5"] = "Life's a challenge when you're just naturally better than everyone else.",
    ["Chatter_6"] = "What am I doing here...",
    ["Chatter_7"] = "A lot of tenacity and a little bit of luck can go a long way...",
    ["Chatter_8"] = "Have you seen any Meburs around here?",
    ["Chatter_9"] = "{Dryad} seems nice. I should bring her back with me.",
    ["Chatter_10"] = "Do you think {Steampunker} has an extra of that gun? I know a witch that may want one.",
    ["Chatter_11"] = "No wonder {Demolitionist} has so many accidents. You can't imagine how much ale he buys from me.",
    ["Chatter_12"] = "Normally, I'm not much of a fan of Goblins, but {GoblinTinkerer} seems to be an alright sort.",
    ["Chatter_13"] = "{?Day}Anyone see where the Dryad went?",
    ["Chatter_14"] = "{?!Day}Really quiet out there. A little too quiet...",
    ["Chatter_15"] = "{?!Day}Check in with me, and do your job.",
    ["Chatter_16"] = "{?BloodMoon}You know, where I'm from, a Blood Moon is actually just an excuse to get some fresh air.",
    ["Chatter_17"] = "{?MoonLordDefeated}Moon Lord, don't you mean Abyss Lord?",
    ["Chatter_18"] = "{?HardMode}I know a Lavamancer that would really like that hellstone down in the underworld.",
    ["Chatter_19"] = "{?Homeless}Know any good places to set up shop? Would love to open up a bar here."
}

getSections

require('Module:GameText').getSections()

Returns all top-level keys from the database.

Examples
Code Variable result
local gameText = require('Module:GameText')
local result = gameText.getSections()
{
    "Achievements",
    "AnglerChatter",
    "AnglerNames",
    "AnglerQuestChatter",
    "AnglerQuestText",
    "AnglerSpecialText",
    "Announcement",
    "ArmorSetBonus",
    "ArmsDealerNames",
    "ArmsDealerSpecialText",
	-- ...
	-- (rest omitted for brevity)
}

purge

require('Module:GameText').purge('<language>')

Purges the cache of the game text database for the given language. This is necessary after making a change to the database. See Module:GameText/loaddata for more details.


--------------------------------------------------------------------------------
--
-- =============================================================================
--
-- Module:GameText
--
-- Fetching text from Terraria's localization files
--
-- =============================================================================
--
-- Code annotations:
-- This module is documented according to LuaCATS (Lua Comment and Type System).
-- LuaCATS comments are prefixed with three dashes (---) and use Markdown syntax.
-- For a full list of annotations, see the following link:
-- https://luals.github.io/wiki/annotations/
--
--------------------------------------------------------------------------------


local trim = mw.text.trim

---Holds the arguments from the template call.
---@type table<string, string>
local args_table

---Full database of all strings in Terraria's localization files for the
---different languages. Is filled by the `loadDatabase` function.
---Keys are language codes (e.g. 'de', 'en', 'fr', ...) and values are the
---respective localization tables from [[Module:GameText/db-de]],
---[[Module:GameText/db-en]], [[Module:GameText/db-fr]], ...
---@type table<string, table>
local db = {}

---Return a trimmed version of the value of the template parameter with the specified `key`.
---Return `nil` if the parameter is empty or unset.
---@param key string|integer
---@return string?
local function getArg(key)
	local value = args_table[key]
	if not value then
		return nil
	end
	value = trim(value)
	if value == '' then
		return nil
	end
	return value
end

---Concatenate the values of a table as a string. Works like `table.concat()`,
---except it also accepts associative arrays, i.e. tables that don't have
---purely numerical indices, like `{['foo']='bar'}`.
---@param tbl table
---@param sep string Separator for concatenation
---@return string
local function concatTable(tbl, sep)
	local arr = {}
	for _, v in pairs(tbl) do
		arr[#arr+1] = v
	end
	return table.concat(arr, sep)
end

---Fill the database of strings for a given language and return it.
---The database is cached; see [[Module:GameText/loaddata]] for details about
---what this means.
---@param lang string Language code
---@return table
local function loadDatabase(lang)
	if not db[lang] then
		local success, result = xpcall(
			-- try to load the database for the given language
			function() return mw.loadData('Module:GameText/loaddata-' .. lang) end,
			-- if it doesn't exist, fall back to the English database
			function() return mw.loadData('Module:GameText/loaddata-en') end
		)
		db[lang] = result or {}
	end
	return db[lang]
end

---Process the template parameter input for placeholder replacements.
---Terraria's localization strings contain placeholders like '{0}' or '<right>',
---which the `getText` function of this module replaces by the custom strings
---provided by template parameters. The function uses `string.gsub()` for that
---replacement and it requires a table of replacements for it, so the template
---parameters must be converted to a replacement table for `string.gsub()`.
---That conversion is what this function does. Example:
---Template parameter input: `|x_0=Angler|y_right=Right click`.
---Replacement table: `{['{0}']='Angler', ['<right>']='Right click'}`.
---
---There are two types of placeholders: '{foo}' and '<foo>'. The first type is
---prefixed with 'x_' in the template parameters, the second with 'y_'.
---@param templateParams table<string, string> Template parameter input
---@return table? repl Replacement table suitable for `string.gsub()`
local function replacementArgs(templateParams)
	local replTable = {}
	---Whether the `replTable` is empty.
	local anyReplacements = false

	-- iterate over the template parameters and extract the ones starting with
	-- 'x_' or 'y_'
	for paramKey, paramValue in pairs(templateParams) do
		-- first type of placeholder: 'x_foo' => '{foo}'
		string.gsub(paramKey, '^x_(.+)', function(s)
			replTable['{' .. s .. '}'] = paramValue
			anyReplacements = true
		end)
		-- second type of placeholder: 'y_foo' => '<foo>'
		string.gsub(paramKey, '^y_(.+)', function(s)
			replTable['<' .. s .. '>'] = paramValue
			anyReplacements = true
		end)
	end
	-- return `nil` if the `replTable` is empty, i.e. if there are no relevant
	-- parameters in the input
	if anyReplacements then
		return replTable
	end
end

---Return the localization string or table of strings corresponding to the given
---key. Keys can be nested using dots, e.g. `ItemName.IronPickaxe`.
---@param key string Identifier of the localization string/table
---@param lang? string Language code
---@return string|table|nil
local function get(key, lang)
	key = trim(key)

	-- The `key` is a dot-separated string that identifies a string or object in
	-- the localization data. The JSON localization files contain nested objects
	-- like so (fictional example):
	-- {
	-- 	"Buffs": {
	-- 		"Names": {
	-- 			"StardustMinion": "Stardust Cell"
	-- 		}
	-- 	}
	-- }
	-- Usually there are only 2 levels of nesting but we must assume that there
	-- can be any number of levels.
	-- The JSON data is `db[lang]` here, with JSON objects being Lua tables.

	-- We "descend" into the data by splitting the `key` on dots into subkeys.

	---Index of the next "subkey" within the `key`.
	local nextSubkeyStartPosition = 1

	-- For the key splitting we use a for-loop in the "generic form", which
	-- requires a "function-in-function":

	---Return a function that returns the index of the next dot in the `key`,
	---starting from the current `nextSubkeyStartPosition`.
	local function findNextDot()
		return function()
			-- note: string.find() returns start *and* end index, but those are
			-- always identical because our pattern is only 1 character long
			return string.find(key, '.', nextSubkeyStartPosition, true)
		end
	end

	-- Initially we start with the entire database.

	local data = db[lang] or loadDatabase(lang or 'en')

	-- We locate the first subkey:
	for dotPosition in findNextDot() do
		local subkey = string.sub(key, nextSubkeyStartPosition, dotPosition - 1)

		-- Then we descend into the `data` using this subkey:
		data = data[tonumber(subkey) or subkey]
		-- (`tonumber` is needed because numerical indices are true integers,
		-- not "integer-as-a-string"s)

		if not data then
			-- subkey is invalid
			return nil
		end

		-- Finally we repeat it with the next subkey.
		nextSubkeyStartPosition = dotPosition + 1
	end
	-- The above is the "generic form" of the for-loop. It is repeated until
	-- `dotPosition` becomes `nil`, i.e. until there are no more dots in the
	-- `key`.

	-- The final part of the `key` is the subkey to look for in the `data`.
	if nextSubkeyStartPosition ~= 0 then
		key = string.sub(key, nextSubkeyStartPosition)
	end

	-- Using the example data from above, this is how the process would go:
	-- We would get the first subkey from the `key` 'Buffs.Names.StardustMinion',
	-- i.e. 'Buffs'. We would descend one level now by setting `data`
	-- (previously the entire database) to `data['Buffs']`.
	-- Then we would get the second subkey, 'Names' and descend another level by
	-- setting `data` to `data['Names']`.
	-- There would be no more dots in the `key` now, so the current `data` would
	-- be the table in which to look for the third subkey, 'StardustMinion'.
	-- Since we would have descended two levels, the `data` would be:
	-- `db[lang]['Buffs']['Names']`.

	-- Look for the final subkey:
	local result = data[tonumber(key) or key]

	if result and type(result) == 'string' then
		-- some localization strings contain reference marks, such as
		-- '{$CommonItemTooltip.RightClickToOpen}'; replace those
		result = string.gsub(result, '({%$(.-)})', function(_, referenceKey)
			return get(referenceKey, lang)
		end)
	end
	return result
end

---Return the string corresponding to the given `key`. The value of the `key`
---must be a string, not a table. Placeholders in it like '{0}' or '<right>'
---will be replaced according to the `placeholderReplacements`, which should
---be a table suitable for `string.gsub()`, e.g.:
---```lua
---{
---	['{0}'] = 'Angler',
---	['<right>'] = 'Right click'
---}
---```
---@param key string Identifier of the localization string
---@param lang? string Language code
---@param placeholderReplacements? table<string, string>
---@return string?
local function getText(key, lang, placeholderReplacements)
	if not key then
		return
	end
	local result = get(key, lang) or get(key, 'en')

	-- error handling
	if not result then
		return
	end
	if type(result) ~= 'string' then
		error('The value of "' .. key .. '" is not a string! getText can only be used for strings.')
	end

	-- strip condition marks like '{?Homeless}'
	result = string.gsub(result, '{%?.-}', '')

	-- replace placeholders
	if placeholderReplacements then
		result = string.gsub(result, '%b{}', placeholderReplacements)
		result = string.gsub(result, '%b<>', placeholderReplacements)
	end

	return result
end

---Return all keys of the localization database for one language on the top level.
---@return string[]
local function getSectionList()
	-- the language doesn't matter because all languages should have the same keys
	local data = db['en'] or loadDatabase('en')
	local arr = {}
	for k, v in pairs(data) do
		arr[#arr + 1] = k
	end
	return arr
end

--------------------------------------------------------------------------------
---Main return object
local p = {}

---For `{{gameText}}`: return a string by key.
---@param frame table Interface to the parser (`mw.frame`)
---@return string?
p.get = function(frame)
	args_table = frame:getParent().args  -- global input args cache
	local result = getText(getArg(1), getArg('lang') or frame.args['lang'], replacementArgs(args_table))
	if result then
		return '<span class="gameText">' .. string.gsub(result, '\n', '<br/>') .. '</span>'
	end
end

---For `{{gameText/raw}}`: return a string by key without any processing/formatting.
---@param frame table Interface to the parser (`mw.frame`)
---@return string
p.getRaw = function(frame)
	return
		(frame.args['prefix'] or '')
		.. (getText(frame.args[1], frame.args['lang'] or require('Module:Lang').get(), replacementArgs(frame.args)) or '')
		.. (frame.args['postfix'] or '')
end

---For `{{gameText/section}}`: return one table ("section") from the database as
---an array from Extension:ArrayFunctions.
---@param frame table Interface to the parser (`mw.frame`)
---@return string
p.getSection = function(frame)
	args_table = frame.args  -- global input args cache
	return mw.af.export(get(getArg(1), getArg('lang')))
end

---For `{{gameText/sectionList}}`: return all top-level keys as an array from
---Extension:ArrayFunctions.
---@param frame table Interface to the parser (`mw.frame`)
---@return string
p.getSectionList = function(frame)
	args_table = frame.args  -- global input args cache
	return mw.af.export(getSectionList())
end

---Return all keys and strings of one table from the database, separated by the
---character '¦'. The separator between each key and string is '₪'. If no key is
---provided, then merely return all keys in the entire database.
---@param frame table Interface to the parser (`mw.frame`)
---@return string?
p.listAll = function(frame)
	local lang = frame.args['lang'] or 'en'
	loadDatabase(lang)
	local arr = {}
	if frame.args[1] then
		if not db[lang][frame.args[1]] then
			return
		end
		for k, v in pairs(db[lang][frame.args[1]]) do
			arr[#arr + 1] = k .. '₪' .. v
		end
	else
		-- no key provided
		for k, v in pairs(db[lang]) do
			arr[#arr + 1] = k
		end
	end
	return table.concat(arr, '¦')
end

---Return all keys of one table from the database, separated by the character '¦'.
---If no key is provided, then merely return all keys in the entire database.
---@param frame table Interface to the parser (`mw.frame`)
---@return string
p.listKeys = function(frame)
	local lang = frame.args['lang'] or 'en'
	loadDatabase(lang)
	local data
	if frame.args[1] then
		data = db[lang][frame.args[1]] or {}
	else
		-- no key provided
		data = db[lang]
	end
	local arr = {}
	for k, v in pairs(data) do
		arr[#arr + 1] = k
	end
	return table.concat(arr, '¦')
end

---Return a tabular visualization of the entire database, where the left column
---is the key and the right column is the string.
---@param frame table Interface to the parser (`mw.frame`)
---@return string
p.printTable = function(frame)
	local lang = frame.args['lang'] or 'en'
	local data = loadDatabase(lang)

	local allKeys = {}
	local allValues = {}
	local function recurse(datatable, previousKey)
		for thisSubkey, thisData in pairs(datatable) do
			local nextKey
			if previousKey ~= '' then
				nextKey = previousKey .. '.' .. thisSubkey
			else
				nextKey = thisSubkey
			end
			if type(thisData) ~= 'table' then
				table.insert(allKeys, nextKey)
				allValues[nextKey] = thisData
			else
				recurse(thisData, nextKey)
			end
		end
	end
	recurse(data, '')
	table.sort(allKeys)

	local output = {'<table class="wikitable"><tr><th>Key</th><th>String</th></tr>'}
	for i = 1, #allKeys do
		local key = allKeys[i]
		local keyCell = '<td><code>' .. key .. '</code></td>'
		local valueCell = '<td><span class="gameText">' .. string.gsub(allValues[key], '\n', '<br/>') .. '</span></td>'
		table.insert(output, '<tr>' .. keyCell .. valueCell .. '</tr>')
	end
	table.insert(output, '</table>')
	return table.concat(output)
end

---Purge the cache of the string database for the given language. See
---[[Module:GameText/loaddata]] for details about what this means.
---Invoke from wikitext or from another module.
---@param frame table Interface to the parser (`mw.frame`)
p.purge = function(frame)
	local lang
	if frame == mw.getCurrentFrame() then
		lang = frame.args['lang']
	else
		lang = frame
	end
	lang = lang or 'en'
	require('Module:GameText/loaddata').purge(lang)
end

---For other modules: return a string.
p.getText = getText

---For other modules: return a string or table without processing/formatting.
p.getData = get

---For other modules: return an array of top-level keys.
p.getSections = getSectionList

return p