
Localization
The following contains in-depth technical documentation on how to use TS!UNDERSWAP's localization systems.
The bottom of the page contains a list of frequently asked questions about the less-technical aspects.
If you have any further questions or concerns, don't hesitate to join our Discord server to talk to us!
This page will be updated as new changes occur in the localization system. This current information is up-to-date as of Demo 2.0.8. 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.
-
`x8`
- This offsets the following text by a specified amount of pixels. The
8
(representing the number of pixels) can be substituted for any number, including negatives. - Generally, there's better ways to do certain things than manual offsets.
- Example in dialogue:
"* The last word here is `x8`offset."
- This offsets the following text by a specified amount of pixels. The
-
`y8`
- This offsets the following text by a specified amount of pixels on the Y axis. The
8
(representing the number of pixels) can be substituted for any number, including negatives. - Generally, there's better ways to do certain things than manual offsets.
- Example in dialogue:
"* The last word here is `y8`offset."
- This offsets the following text by a specified amount of pixels on the Y axis. The
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.
Frequently Asked Questions
Q: What languages are currently supported?
A: In addition to English, the following languages are currently supported:
- Russian (Русский)
We also plan to support the following languages:
- Spanish (Español)
- Brazilian Portuguese (Português Brasileiro)
- Dutch (Nederlands)
- Italian (Italiano)
These are developed by community localizers in their spare time, and will be released whenever they're ready. This process takes a while, so please be patient!
Q: What types of localizations are there?
A: The game has three different types of languages:
- Default -- The default for the game, written by Team Switched (English only.)
- Verified Community -- Created by community localizers, and added to the game in collaboration with Team Switched.
- Unverified -- Not verified by Team Switched (These are manually added by users.)
Q: How do I get my localization added to the game?
A: We generally evaluate patches on a case-by-case basis, and once substantial progress has been made.
Before anything, you'll want to join our Discord server and voice your intent in the #localization channel. It's important to communicate with us -- asking questions about the writing, how to translate certain material, consulting us about dialogue changes, and sharing your progress.
Most importantly, patches must fulfill the below guidelines to be considered for implementation.
Q: What are the guidelines for verified community localization patches?
A: It must be fully and accurately translated. This includes:
- All text in the game's Diannex file, and gameplay-important sprites, must be localized. (Any sprites and audio files unimportant to gameplay are optional.)
- Writing must be as accurate to the source material as possible. Anything that cannot be directly translated may be rewritten, but please communicate major changes to us.
- The patch must be properly vetted by native speakers to ensure accuracy and quality.
- Everything must be thoroughly tested to ensure there are no major bugs or crashes.
- Absolutely no use of generative AI.
- Nothing inappropriate should be included within the patch.
- The patch must utilize our official localization tools, and not require external game modification.
- All contributors must be fully credited, and consent to it being added in-game.
If a patch is added in-game, but is found to have violated our guidelines after, it may be removed.
Q: The official localization tools do not support something from my language. Can they be updated?
A: We may be willing to integrate certain changes to the base game if necessary (i.e. fonts, text rendering features, fixes, etc.) This will depend on our free time, though.
Q: Will localizations be updated at the same time as major game updates?
Unfortunately not, and we can't give an estimated timeframe on when they would be. Localizations aren't updated at the same time that the game is developed, and are done afterwards by volunteer translators independent of the team.
When the game receives a major update (i.e. Demo v1.0 to v2.0), older patches will not be included until they have been fully updated -- both for new content, and potential changes to existing material in the script.
Q: My localization was added to the game. Am I required to update it for major game updates?
A: Absolutely not. Making a localization patch should be treated as a hobby, and not an obligation. If you would like someone else to update it for you, then we will see if any native speakers in the community are interested.
Q: My localization was added to the game, but I want it to be removed. Can it be?
A: Yes. If you want your localization removed for any reason, you can request it and we'll uphold that.
Q: I want to create a localization patch, but don't want it added to the game. Can I distribute it on my own?
A: Of course! We ask that you distribute only the necessary files (everything that would go in the lang folder, and a readme with instructions and credit.) Do not redistribute our game.
Q: I'm a native speaker and noticed some inaccuracies in one of the game's verified community localizations. Who can I talk to about this?
A: Join our Discord server and reach out in the #localization channel. Each localization is developed by native speakers in the community, and we're willing to make changes based on feedback.