Skip to content

LuaUI CustomText

wezzzyrek1 edited this page Sep 2, 2026 · 2 revisions

Lua UI - Custom text (CustomText.xml / .bmd)

Window scripts should not hard-code the text a player reads. They pull it from the client's language file, CustomText.bmd, so the same window shows the right language and you can edit or translate every string in one place without touching the Lua.

This page covers the whole round trip: reading a string from a script, adding your own section, and encoding the file the client ships with.

Reading text from a script

lbl.text = Lang.GetText(Lang.TYPE.ItemBank, 16)
  • Lang.TYPE is built at runtime from the sections CustomText.bmd actually contains, keyed by their XML node name: a <ItemBank> section becomes Lang.TYPE.ItemBank. Add a section to the file and it appears here on its own - there is no list in Lua to keep in step.
  • Lang.GetText(textType, id) takes one of those Lang.TYPE values and the entry's id within that section, and returns the string.
  • A missing entry never returns nil - it comes back as the placeholder "Missing Text: <id>". Guard against it when a string may not be present yet, and fall back to an in-script default:
local function T(id, fallback)
    local s = Lang.GetText(Lang.TYPE.ItemBank, id)
    if s == nil or s == "" or s:match("^Missing Text: %d+$") then
        return fallback
    end
    return s
end

lbl.text = T(16, "Zen")   -- localised, English when the file has no entry 16

The same thing, already written

ui.LangText(section, id, fallback) is that guard packaged, so you rarely need to write it out. It names the section by its XML node name rather than through Lang.TYPE, which means it also copes with the section being absent altogether:

local ui = require("UI")

lbl.text = ui.LangText("ItemBank", 16, "Zen")

It returns the fallback when the section is missing, when the id is missing, and when the placeholder comes back. Reach for Lang.GetText directly only when you want the placeholder to show, which is useful while filling the file in.

Use either for text a player reads and that you want to keep in the language file. A fixed in-script string is fine for anything else - debug labels, internal names.

Adding your own section

The strings live in Data\Local\CustomText.xml, one section per window under the <Language> root:

<Language Password="xxxxxxxx">
    ...
    <CoinExchange>
        <Msg ID="0" Text="Coin Exchange" />
        <Msg ID="1" Text="Convert" />
        <Msg ID="2" Text="Not enough WCoin." />
    </CoinExchange>
</Language>
  • The node name is the Lang.TYPE key - <CoinExchange> becomes Lang.TYPE.CoinExchange. Nothing in Lua or the DLL has to be told the section exists; it appears the moment the file carries it.
  • Each <Msg ID="n" Text="..." /> is one entry. ID is the number you pass as the second argument to Lang.GetText; Text is what comes back.
  • Keep the IDs stable once your window ships - the script refers to them by number, so renumbering an existing section changes what every label reads.

Encoding the .bmd

The client reads CustomText.bmd, not the .xml. The .xml is your editable source; the .bmd is the packed file the client actually loads.

After editing the .xml, re-encode it with the ServerInfo (ToolKit) XML editor. It packs the .xml into the .bmd using the Password attribute on the <Language> root (max 8 characters). Ship the resulting .bmd, and keep the .xml as your source - there is no tool that turns a .bmd back into .xml.

Each time you change a string:

  1. edit Data\Local\CustomText.xml;
  2. re-encode to CustomText.bmd in the ServerInfo (ToolKit) editor;
  3. ship the .bmd with the client.

Why not hard-code it?

Keeping the strings in CustomText.bmd instead of in the script means one place to edit or translate them, and the same window renders in whatever language a client runs - with no script change and no DLL rebuild.

See Also

  • Controls - the UI.* globals and where Lang.GetText fits
  • File map - client file layout, .usc vs .lua
  • Lua UI - dev mode and getting started

The UrlConfirm section

The link confirmation that ui.ConfirmUrl and a button's url option put on screen reads its wording from a section of its own, so it can be translated without touching any script:

<UrlConfirm>
	<Msg ID="0" Text="Leaving the game" />
	<Msg ID="1" Text="This link opens in your web browser:" />
	<Msg ID="2" Text="Continue" />
	<Msg ID="3" Text="Cancel" />
</UrlConfirm>

The address itself is not part of the prompt. It is drawn on its own line below it, so it stays readable however long it is, and so the player cannot miss where the button is about to take them.

Every id has an English fallback compiled in, so a client whose file has no such section still shows a complete box. Reach any section the same way from your own scripts with ui.LangText(section, id, fallback).

Clone this wiki locally