Localization
The following contains in-depth technical documentation on how to use TS!UNDERSWAP's localization systems.
If you have any further questions or concerns, don't hesitate to join our Discord server to talk to us!
Also, this page will update in the future. This current information is up-to-date as of Demo 2.0.7. To see the old documentation for Demo 1.0, go here.
Debug Mode
DISCLAIMER: Using debug mode can seriously corrupt your game saves and files. Use it at your own risk. Savedata produced in debug mode may cause errors and crashes in later releases of the game, as well.
Additionally, no official support is provided for using debug mode, even if you break your game using it. These features are subject for change and removal at any given time.
How to enable on Windows
- Option 1: Launch game from command line by navigating to the game folder and running
"TS!Underswap.exe" -debugmode
- Option 2: Create a shortcut to the game (on Windows), open Properties on the shortcut. Where it says "Target", add a space and
-debugmode
to the end of the box. - Option 3: Navigate to
%localappdata%\TS_Underswap
, then openconfig.ini
. After the line that says[General]
, add this line:LaunchDebug="1.000000"
, and save the file.
Relevant Commands
Listed here is a subset of debug mode commands that are useful for localization purposes in demo 2.0. More commands exist, but are not documented here.
- F5: Restarts game
- F6: On intro screen, loads current save slot
- Hold in combination with a number key from 0-9 to load that specific save slot. Note that these numbers start from 0, while the game shows them 1 higher.
- F2: Most other commands (in combination with other keys)
- All of the following keybinds can be accessed by holding F2, then pressing the corresponding key.
- Q/W: Go to previous/next room
- R: Reloads the current room, along with language data. Extremely useful to test changes quickly.
- H: Toggles invincibility.
- F: Set FPS, or if a textbox is open, select a new face portrait.
- G: Go to any room by typing out its name.
- V: On intro screen, toggles game being muted. Elsewhere, toggles all objects being visible.
- On intro screen, you can hold Shift to only toggle music volume.
- O: Change basic stats about the player like name, HP, LV, gold, etc.
- I: Get any item by name, if you have room.
- Y: Save game where you are right now. (NOTE: This is very dangerous and can break the game across versions.)
- You can supply a "save variant" ID, usually from 0 to 9, being the save slot you're saving to.
- Z: Trigger any "hangout/date" (by technical, not actual terms) sequence, by name/ID.
- ~ (or 2): Open a debug menu with many helpful options.
- Outside of battle, you may use these keybinds:
- A: Trigger any battle by name/ID.
- N: Toggle noclip. (Lets you walk through walls and objects.)
- M: Allows you to move when you're normally not allowed to.
- E: Will cause your next step to trigger a battle, if there is going to be one.
- T: Warps you to the start of Starlight Isles, useful if you are on a fresh save.
- Inside battle, you may use these keybinds:
- S: Make every enemy spareable in the battle.
- A: In some battles, will make every attack end instantly for the duration of the battle. In others, will instantly end the current attack.
- Press the ~ key on its own to toggle the game being very sped up.
- Holding Ctrl will cause the game to slow down instead.
- Note: Several cutscenes and events will have animations that do not play well with the game being sped up or slowed down.
- Text and Battle testing
- On intro screen, you can press F3 to load into the battle testing room. Here, you can type out a battle to test.
- Your current save slot will be used to play the battle.
- Alternatively, you can press F6. This will switch you into the text testing room, where you can type out a scene/function/definition (Diannex terminology) to test.
- These names correspond directly to the names in the localization files.
- Branches in scenes/functions will be entirely skipped.
- There are a number of scenes that do not play well with this, especially certain battles and shops. This is most useful for overworld interactions and scenes.
- On intro screen, you can press F3 to load into the battle testing room. Here, you can type out a battle to test.
How to Make a New Language
Getting Started
Step 1: In the game's data
folder, create a lang
folder if one does not already exist. Here, you can put localization files for any number of languages installed.
Step 2: Copy the game's default.json
file from the data
folder into the lang
folder. Rename it to something unique for your language, and customize it in a text editor of choice!
Step 3: You will want to make a new folder for your language's remaining data files (including text, sprites, sounds).
This will be what you use for the directory
in the JSON file you made a copy of.
What's all of this JSON stuff?
See the below section that goes into detail. JSON is a common file format used to represent data.
Distribution
To distribute unofficial language patches, you can ZIP up your files (including the copied JSON) and share it with others.
On older versions of the game, people may be required to create a lang
folder to extract to, but once official localization patches exist, it should be there automatically.
If you would like to make an unofficial localization patch official, we are open to manually reviewing the patches. However, we will need proper verification that it is of good quality. We're open to talk on our Discord server!
Localization File Structure
JSON Config File
- Basic properties
name
: Name of the language.ascii_name
: ASCII name of language; only use characters supported in native fontlanguage_code
: The ISO639 two-letter language code of the language.region_code
: The ISO3116-1 two or three-letter region code of the language.directory
: Folder name used for all other files for this language. Should be set to something unique for this language.diannex
: Name of translation file used for text, for this language.
- Fonts
- Each font listed in the file can be replaced, with a number of modifiable attributes for how they are displayed.
filename
: The TTF or PNG filename used for the font glyphs/data.- As of demo 2.0.7, to use "extended" built-in fonts like
ext1
, just addext1_
to the font name, and use that here. As an example,fnt_main
becomesfnt_ext1_main
.
- As of demo 2.0.7, to use "extended" built-in fonts like
antialias
: Whether the font should have "antialiasing," used for non-pixelated fonts.size
: Size of font to use.bold
: Whether to use bold version of font, if applicable.italic
: Whether to use italic version of font, if applicable.scale
: How much to upscale the font when drawing.scale_small
: How much to upscale the font when drawing at half the scale, if applicable.gamepad_icon_x
: How much to offset gamepad icons on the X axis.gamepad_icon_y
: How much to offset gamepad icons on the Y axis.gamepad_icon_width_pre
: How much extra space to add before gamepad icons, on the X axis (affects later characters).gamepad_icon_width_post
: How much extra space to add after gamepad icons, on the X axis (affects later characters).characters
: A bunch of properties specific to each character in the fontwidths
: If present, you can define the additional width to add to specific characters. Can be positive or negative.- Formatted like
["a", 4, "b", -2]
- Important caveat: As of writing (2.0.7), this does not account for font scaling. You may wish to use
offsets_x
instead.
- Formatted like
offsets_x
: If present, you can define X offsets to add to specific characters in certain situations.- Order is [character, offset after character, offset before character, offset after character IF the next character is the same]
- Example is
"offsets_x": ["A", 1, 1, 0, "L", -1, 0, 0]
- As of Demo 2.0.6, you can specify ranges of characters by putting two characters next to each other.
- Example of a range is
["AZ", 1, 1, 0]
, which applies to all letters between A-Z.
offsets_x_default
: As of Demo 2.0.6, if this is present, you can specify the default X offsets for all characters in the font.- Same order as above, but without the start character.
- Example is
"offsets_x_default": [16, 0, 16]
- There are also a few extra properties that aren't used by default, but you may add them to a font if you need them:
ext_range_start
: Start of character range used in font (character code number; defaults to32
). Used for TTF fonts.ext_range_end
: End of character range used in font (character code number; defaults to128
). Used for TTF fonts.ext_proportional
: In PNG fonts, defines whether the font is "proportional," or not monospace. True by default.ext_separation
: In PNG fonts, defines how much space to put between each character, if proportional. 2 by default.ext_string_map
: In PNG fonts, defines the order of characters in the supplied image. Supply this as a string, such as"abcdefgABCDEFG"
.ext_sprite_subimages
: In PNG fonts, defines how many "frames" there are in the image sprite. 1 by default.ext_sprite_smooth
: In PNG fonts, enables smoothing on edges when loading the image. False by default.ext_sprite_first_char
: In PNG fonts, when no string map is supplied, this is the first character that is used. Supply as a string."!"
by default.
syntax
asterisk
: Represents the character used to start most characters' lines of text, normally"*"
.punctuation
: Represents punctuation characters used to generate pauses.period
: Represents period character used in text.
offsets
- Various offsets for text and icons used in text.
characters
- Has default character widths used at different font sizes used by the game.
widths
: Same property as thewidths
used for fonts, but globally, for all fonts.
sprites
- List of localizable sprites, by internal name.
""
means no replacement; uses normal sprite from the game."$"
means to use a filename with the same name, ending in.png
.- In the name, you can additionally specify some sprite properties, as used by GameMaker!
- These will get automatically processed and removed.
- To change sprite origin: add something like
[origin:<x>,<y>]
, for example[origin:-1,2]
, to the name. - To change sprite bounding box: add something like
[bbox:<left>,<top>,<right>,<bottom>]
, for example[bbox:2,2,10,10]
, to the name.
- Otherwise, you can enter any PNG filename, as located in your folder with localization files.
- To get the source sprites (as seen in English in the game), you can run the game from command line with
-exportloc
, which will output the sprites to the game's save directory.- We have a pre-exported version of this from Demo 2.0.7 that you can download here!
- (Old sprites from demo 2.0.6 can be found here.)
- List of localizable sprites, by internal name.
sounds
- List of localizable sounds, by internal name.
""
means no replacement; uses normal sound from the game."$"
means to use a filename with the same name, ending in.ogg
.- Otherwise, you can enter any OGG filename, as located in your folder with localization files.
- List of localizable sounds, by internal name.
misc
- This section is for legacy localizations that rely on changing default fonts automatically. This may be removed in a future version, so do not rely on it.
Translation File Types
There are multiple formats that can be used to represent the text data of a language. Here's a list:
- "Private" file (contains comments)
- Filename must end with
.priv.dxt
- Why use this format?
- Contains comments and names of scenes/functions.
- Useful for localization development!
- Why not use this format?
- Takes extra storage space, and marginally more time to load.
- What does all of this formatting mean?
- Some lines are comments. This means they contain no text, and are just there to help you understand what's going on.
Example:
# The intro sequence of the game
Comments always start with a#
. - Some lines denote the beginning of a scene (and similar). These are also just to let you know the context, and to separate things out.
Example:
@intro
These lines always start with a@
. - The rest of the lines are all for text and dialogue! At the end of the line is a "string ID," which uniquely identifies the line across game versions.
Example:
"* \"Never fret, for the leaves shall catch your fall.\""&000005f5
Here, the string ID is&000005f5
.
- Some lines are comments. This means they contain no text, and are just there to help you understand what's going on.
- Filename must end with
- "Public" file (without comments)
- Filename must end with
.dxt
- Why use this format?
- Smaller filesize, marginally faster to load.
- Why not use this format?
- Harder to translate without some context!
- Filename must end with
- Binary public file (without comments, not text)
- Filename must end with
.dxl
- Why use this format?
- Smallest filesize, fastest to load.
- Good for when finalized and sharing with others.
- Will be used by final releases of official localizations.
- Why not use this format?
- Not easy to modify, since it's not a text file!
- Filename must end with
You can get the base English translation files used in Demo 2.0.7 here! You can choose either version you prefer to work with, and convert between as you see fit.
Note: Old translation files from Demo 2.0.6 can be found here, for comparison/upgrade purposes.
When used as part of a language (by updating diannex
in the config JSON file), these files will override all of the existing in-game text.
Converting Between Translation File Types and Versions
Translation files are built on a system called Diannex, which has a command-line tool used to convert between types. It currently supports 64-bit Windows officially, but can be manually built for other platforms.
To use the Diannex tool, you can download the version used by TS!UNDERSWAP from GitHub.
Note: If your browser or antivirus believes Diannex is malware, try to allow the file to download and run anyway. It's a false positive due to the EXE not being commonly downloaded.
You'll want to put diannex.exe
into a folder with your all of your translation files. Make sure to back them up before running anything. When there, open a command line or terminal in that folder (or navigate to that folder using the cd <path>
command). Then, you have multiple options for commands to run:
- Upgrade Demo 1.0 public file -> new private file (for Demo 2.0.x)
- Note: Upgrading from a public file to a new private file can only be done along the transition from Demo 1.0 to Demo 2.0.x.
-
diannex --upgrade --in_public "<name_of_old_public_file>" --in_newer "<name_of_provided_new_private_file>" --out "<name_of_output_upgraded_file>"
Sidenote: When upgrading translation files to a newer version, lines that have been newly-added (and likely need to be translated) are marked with
[new]
at the end of the line. You can modify and/or remove these as you see fit! - Upgrade old private file -> new private file (for a newer game versions)
-
diannex --upgrade --in_private "<name_of_old_private_file>" --in_newer "<name_of_provided_new_private_file>" --out "<name_of_output_upgraded_file>"
-
- Convert private file -> public file (for same game version)
-
diannex --convert --in_private "<name_of_private_file>" --out "<name_of_output_public_file>"
-
- Convert public file -> private file (for same game version)
-
diannex --convert --in_public "<name_of_public_file>" --in_match "<name_of_matching_private_file>" --out "<name_of_output_private_file>"
-
- Convert private file -> binary file (for same game version)
-
diannex --to_binary --in_private "<name_of_private_file>" --out "<name_of_output_binary_file>"
-
- Convert public file -> binary file (for same game version)
-
diannex --to_binary --in_public "<name_of_public_file>" --out "<name_of_output_binary_file>"
-
Text Formatting
TS!UNDERSWAP has custom text formatting syntax used for dialogue, as well as some menus and UI. This section will describe some of it.
Mid-Text Commands
Everything enclosed with two `
characters is not displayed on screen.
They signify a "mid-text command," and can change properties of the text.
The first character after the opening `
tells the type of command.
Following that is any parameters, such as a specific text color/effect.
After the parameters is the closing `
character.
Here's a list of useful commands to know:
-
`i`
- Appends a newline and two spaces to the next line of dialogue. This is typically added automatically.
- Example in dialogue:
"* Once upon a time, `i`this part is indented..."
-
`p1`
- When dialogue is typing out, this will cause the text to pause.
- The
1
can be substituted for a number between 0 through 9. The higher the number, the longer the pause. - This is automatically generated based on the punctuation defined in the language's config JSON.
- To avoid certain punctuation from generating pauses, you can escape it like this:
"* This text\\, despite its commas\\, has no pauses."
- To avoid certain punctuation from generating pauses, you can escape it like this:
- Example in dialogue:
"* This `p0`line `p1`pauses `p2`between `p2`every `p0`word."
-
`cR`
- This sets the color used by the text after it. The
R
can be substituted for any supported color code. - List of supported colors (mind the letter cases!):
W
: WhiteY
: YellowR
: RedB
: Blue (!)b
: Black (!)G
: Green (!)g
: Gray (!)h
: Light grayp
: PinkA
: AquaL
: Lime green$
: Reset color (base color; usually black or white)
- Example in dialogue:
"* The last word in this line should be `cR`red`c$`."
- This sets the color used by the text after it. The
-
`e1`
- This sets the effect used by the text after it. The
1
can be substituted for any supported effect ID. - Here's a list of the effect IDs:
0
: Normal/default1
: Shaking2
: Circular/wavy movement4
: Random characters (all letters become random ASCII values from 33 to 126)5
: Slight twitch6
: Stronger twitch7
: Double size8
: Slight shake
- Example in dialogue:
"* The last word in this line should be `e1`shaking`e0`."
- This sets the effect used by the text after it. The
-
`s8`
- This inserts the specified number of space characters to the text, at its location. The
8
(representing the number of spaces) can be substituted for any number. - Generally, this is used by the choice text generator for 2-way choices. You may use this to pad 2-way choices according to your needs, as well.
- Example in dialogue:
"* I `s8`want a bunch of spaces, but no delay between the words."
- This inserts the specified number of space characters to the text, at its location. The
-
`*X`
- This causes a gamepad icon of the given input to be drawn at the current location. Icon is determined by the connected controller.
X
can also be substituted withZ
,C
, orU
/D
/L
/R
, representing the keyboard buttons and arrow keys.- Example in dialogue:
"* Press `*X` to finish."
-
`#`
- Not to be confused with a standalone
#
character (which, by the way, creates a new line). - This disables the newline generator from doing its job, for cases where it produces undesired results.
- Example in dialogue:
"`#`* I purposefully want this line to go off the edge of the box."
- Example line from the game:
"`#`* Being the sage of `i`RUINED HOME, and knowing `i`so many monsters..."
- Not to be confused with a standalone
-
`@`
- This disables the pause generator from doing its job, for cases where it produces undesired results, instead of using
\
to escape punctuation. - If already disabled, this will re-enable the pause generator in the middle of a line.
- Example in dialogue:
"`@`"STAY DETERMINED,"`p1` EVERYBODY!!!"
- This disables the pause generator from doing its job, for cases where it produces undesired results, instead of using
-
`$0`
- This adds a "formatting" string to the text at this command's position.
- The
0
can be replaced with any number from 0 through 9, representing the ID of the text to use.Generally, you should never edit this number. Doing so can break the game.
- This is usually used for inserting names and numbers that can change dynamically.
- Example in dialogue:
"`$0`! #Stay determined!"
If the string at index 0 was
"test"
, the result would look like this:"test! #Stay determined!"
- Note: In the text testing room, the strings are always
BEPIS
, as it has no knowledge of the context.
`!event`
:- This runs a specific event in the code when the dialogue reaches this command.
event
is substituted with the specific code name for the event.- You can move this around to affect timing of the event, but do not change its contents.
- Example line from the game:
"AND... `!papcut`CUT!!!"
`~`
:- Represents monster status in the JOURNAL specifically.
- This gets replaced by the game with the monster's status.
Additional Formatting
You can insert new lines of dialogue to the game!
- If you put
`%%%`
in a line of dialogue, it will often be able to split the dialogue into a new textbox. This can be done multiple times. - Example in dialogue:
"* Line 1.`%%%`* Line 2.`%%%`* Line 3."
You can remove lines of dialogue from the game! (To an extent.)
- If you replace an entire line of dialogue with exactly
`%%%`
, it will usually also be able to remove that line of dialogue from occurring. - Example in dialogue:
"`%%%`"
You can change and add face portraits/characters to lines of dialogue!
- At the start of a line of dialogue, you can put
`char:[X]`
where[X]
is the character/portrait name to use for that line. This overrides the default portraits that the game provides. As an example:-
"`char:asg_normal`* This line now uses Asgore's normal expression, voice, etc."
-
- This can also be used with newly-added lines (as seen above), like so:
-
"`char:asg_normal`* Line 1.`%%%``char:asg_left`* Now I'm looking left."
-
- To figure out what portraits are available, you can use the game's debug mode.
When a character is speaking with their portrait, press
F2+F
to show all of their available portraits, and you can click one of them to preview it, as well as copy the name to your clipboard.Do be aware this also can affect game/cutscene logic, as it will actually change the character that's speaking directly.
- This can be used in the middle of a line if necessary. This isn't a very common practice, but it is used by the game in some places.
- Finally, there's a variant,
`char%:[X]`
, which queues up a character change for when the line is progressed past. This may not always work.