-
Notifications
You must be signed in to change notification settings - Fork 18
Guide Translations
A translation is an ordinary content mod. It needs no engine changes and no special permissions: it registers a glyph page, a charmap, and a pile of strings, exactly like any other mod registers a species or a song.
Start by generating one:
python3 tools/modkit.py translation francais --language "Francais" --base importedThat writes mods/francais/ with every translatable string in the game as
an empty catalog, plus mods/francais-worksheet/ holding the English to
translate from. Read the generated TRANSLATING.md first; this page is the
background it assumes.
The game's text comes from two places, and they are reached differently.
| Where it lives | Registry | Key | |
|---|---|---|---|
| Script text |
Data.text, extracted from the ROM |
text |
the pokered label, _PalletTownText1
|
| Engine text | Literals in src/, wrapped in Strings(...)
|
strings |
the English source string |
Script text is the dialogue: signs, NPCs, dex entries. Engine text is
everything the engine says on its own behalf: But, it failed!, SAVE,
TEXT SPEED, the link-play screens.
The split exists because only one of the two is ROM-derived. That matters for what you are allowed to ship (see below).
Text is drawn from pages: an image of 8x8 cells plus a charmap mapping
byte sequences to glyph codes. The vanilla pages sit at $60 and $80.
Anything from 0x100 up is free, so a new alphabet is added rather than
swapping one out:
mod.content.font:register("kana", {
image = "assets/font/kana.png",
base = 0x100,
glyphsPerRow = 16,
-- advance = 8, -- only if your glyphs are not 8px wide
})
mod.content.font:register("charmap:a", { seq = "あ", code = 0x100 })Codes run left to right, top to bottom from base. Sequences are matched
longest first, so a multi-byte character and a multi-character ligature
both work and neither shadows the other: "ch" can be one glyph even
though "c" is also mapped, and that is the same mechanism the vanilla
font already uses for <PK>, <MN> and 'd.
advance is per page. A proportional font needs one page per width.
The dialogue box holds 18 glyphs. A 3-byte character costs one column, and the engine will never cut a character in half, so a kana line wraps at 18 and not at 6.
Your own \n breaks are honoured exactly as written. Break lines where
they read best rather than where English happened to fit.
To change how much fits, restyle the box:
mod.content.field:patch("theme", { textBox = { maxCols = 16 } })The budget is measured in pixels behind the scenes (maxCols * 8), so a
page with a non-default advance re-measures rather than overflowing.
Some engine sources carry %s or %d:
mod.content.strings:override("Wild %s\nappeared!", "Un %s\nsauvage apparait !")Keep every directive, in a matching count. Word order is yours - the
engine substitutes in the order the directives appear, so if your language
wants the name last, write the sentence with the %s last.
A translation whose directive count does not match the source is refused at runtime: the English is drawn instead and the mismatch is logged once. It will not crash a battle, but it will not show your text either.
Species, move, item and trainer-class names are fields on their records, so they are patched rather than overridden:
mod.content.pokemon:patch("BULBASAUR", { name = "BULBIZARRE" })Status labels (PSN, BRN) sit in the battle HUD and have very little
room; the vanilla ones are three glyphs.
The naming screen's letter grid is a hook, so a translation can replace the alphabet without replacing the screen:
mod.hooks:on("ui.naming.grid", function(base, ctx)
return ctx.lower and MY_LOWER or MY_UPPER
end)Each cell is whatever sequence your charmap maps, so a multi-byte character
is one cell and counts as one character toward the name length. The row
holding a single "lower case" / "UPPER CASE" cell is the case switch,
and the cell spelled "ED" is the confirm.
Extracted script text and the vanilla names are ROM content. Your
translation of them is your own work and ships freely; the English does
not. This is why the generator writes the English to a -worksheet/
directory beside the mod rather than inside it: modkit pack zips
everything under the mod directory, so a worksheet kept inside would land
in your release no matter what a .gitignore said.
lang/strings.lua is the exception. Those sources are the engine's own
Lua, not ROM output, so the key is allowed to be the English.
modkit lint enforces this. Run it before you publish:
python3 tools/modkit.py lint francais
python3 tools/modkit.py pack francaisEvery catalog starts empty and the generated main.lua skips empty values,
so an untranslated string falls through to English. A half-finished
translation is always playable. Ship early.
When the engine adds new strings, re-harvest:
python3 tools/modkit.py translation francais --refresh--refresh keeps every translation you have written, adds the new keys,
and parks anything whose key no longer exists in an ORPHANED comment
block at the end of the catalog rather than deleting your work.
- Guide-Art-Pipeline for how glyph pages sit alongside the other art
-
Reference-Registries for
strings,text,font -
Reference-Hooks for
ui.naming.grid
Engine 1.0.0 · mod API 2 · repository · Discord
This project ships no ROM data. All game content is decoded on the player's machine from their own verified Pokemon Red ROM; mods ship recipes and original assets, never extracted content.
Start here
Concepts
Tutorials
- The ladder
- 01 Sprite and Text Tweak
- 02 Balance Patch
- 03 New Species
- 04 New Item and Ball
- 05 New Move
- 06 NPC and Dialogue
- 07 New Map
- 08 Quest
- 09 Custom Music
- 10 New Mechanic
- 11 Custom UI Screen
- 12 Mini Total Conversion
Reference
Guides
- Art Pipeline
- Audio Authoring
- Translations
- Total Conversions
- Link Compatibility
- Publishing a Mod
- Docs Style Guide
Playing