logo icon
Huu Thang's blog
wanna or
Connect with me on:

I Added Vietnamese Localization to Balatro


Nov 30, 2025

Introduction

3 Balatro was the indie sensation of 2024, winning Best Independent Game and a Game of The Year nomination at the The Game Awards 2024. I have to admit, I got completely addicted to Balatro then. It is a poker-themed rouge-like where you build insane hand combos with different Joker cards to reach high scores. Watching the game take over the community sparked a question for me: Could I make this experience feel native for Vietnamese players?

The spark to actually mod the game came after I watch this video

It explained how the developer, LocalThunk, used the LÖVE framework to build the entire game in Lua. The video even mentioned that you could access the source code to learn how the game works. That was all I needed to hear.

The Motivation

My inspiration didn't just come from the mechanics of the game, but from my love for the classic Hong Kong "God of Gamblers" era. I'm a huge fan of Stephen Chow (Châu Tinh Trì), especially the legendary films dubbed by Vân Sơn. I grew up on movies like All for the Winner, God of Gamblers II, and God of Gamblers III: Back to Shanghai. Those films are packed with iconic jokes that I realized would fit perfectly within the chaotic world of Balatro.

4

Source Code Investigation

If you unzip the Balatro game files, you basically get the entire source code (𓁹‿𓁹?). Inside the folder there are three key areas that handle how you see the game:

  1. game.lua: The heart of the game where it launches and loads everything.
  2. localization/: The folder where the actual text files live.
  3. resources/fonts/: Where the font files are stored.

How do I know? By opening game.lua, you can search for self.LANGUAGES and self.FONTS to see excactly how the game manages its assets.

The self.FONTS variable is an array of font objects that defines the file path and render options. For example, the default English font looks like this:

{
   file = "resources/fonts/m6x11plus.ttf", 
   render_scale = self.TILESIZE*10, 
   TEXT_HEIGHT_SCALE = 0.83, 
   TEXT_OFFSET = {x=10,y=-20}, 
   FONTSCALE = 0.1, 
   squish = 1, 
   DESCSCALE = 1
}

Meanwhile, self.LANGUAGES is an object where each language has a definition. The English version en-us points to font=1, which is the index of the font in the array self.FONTS:

['en-us'] = {
   font = 1, 
   label = "English", 
   key = 'en-us', ...
}

To add Vietnamese, I realized I needed to inject my own definitions. Since the original font didn't have the characters I needed, I had to create a new font file, m6x11plus_vi.ttf. Once the font was ready, I added it as the 10th element in the self.FONTS array:

{
   file = 'resources/fonts/m6x11plus_vi.ttf', 
   ...
}

Then, I created the new language definition for Vietnamese, pointing it to our new font (index 10):

['vi'] = {
   font = 10, 
   label = 'Tiếng Việt', 
   key = 'vi', 
   beta = true, 
   ...
}

Then what? The game's logic is straightforward. It picks up the language key from its settings and looks for a corresponding file:

self.LANG = self.LANGUAGES[self.SETTINGS.language] or self.LANGUAGES['en-us']
local localization = love.filesystem.getInfo('localization/'..G.SETTINGS.language..'.lua')

With the architecture understood, my next move was clear: create the new font m6x11plus_vi.ttf and the vi.lua and bring the translation to life.

The Glyph Grind

Since I couldn't just swap the font without breaking the game's aesthetic, I had to manually "draw" the language. I spent hours in Font Forge - a font editor, had to calculate the coordinates for every point and line perfectly to maintain the font's aesthetic. Every character, like ớ, the 'o' with a horn and acute accent, had to be precise so it looked identical to the original game's style.

2

Support Tool

Manually editing thousands of lines in a nested Lua table is not just tedious - it's a recipe for game-breaking syntax errors. To truly bring the Vietnamese into Balatro without losing my mind, I needed a more robust way to mange the data. I shifted from being a translator to being an architect, building a dedicated interface to handle the heavy lifting.

The process starts with the game's original en-us.lua file. This file is a massive nested table where every card, UI element, and description is stored. To make this manageable for a web-based editor, I wrote a simple Lua script to read the file and export the entire structure into a .json format.

There are a few key things to understand about this structure:

  • The Hierarchy: Everything is a nested under categories.
  • The Literals: Every path in the object eventually leads to a "literal" - which is either a single string (for names) or an array of strings (for multi-line descriptions).
  • Formatting Tags: The strings are filled with tags like {C:attention} or {C:blue}, which control the game's dynamic text rendering. Theses have to be preserved to keep the game's visual juice intact.

1

My custom tool acts as a bridge between raw JSON data and the final localized experience. Instead of staring at a text editor, I can navigate through specific cards and UI elements using a clean, categorized interface.

The tool features several specialized capabilities:

  • Direct Editing: Each card’s name and every line of its text array can be edited in dedicated fields.
  • Visual Feedback: Modified fields are highlighted, making it easy to track exactly what has been changed during a session.
  • Batch Processing: The built-in Text Replacement Tool supports RegExp, allowing me to find and replace terms across the entire database instantly—vital for maintaining consistent movie slang.
  • Export to Lua: Once the "God of Gamblers" energy is fully injected, the tool exports the data as a .json or directly back into a .lua file for the game to load.

Final Process

You can find the complete repository here: GitHub: balatro-vi-localization

The repository includes:

  • Modification Guide: Step-by-step instructions for manual game file modification.
  • Batch Script: A one-command automation tool that handles unzipping, injecting the vi.lua and the new font, and repacking the game.
  • The tool: The tool for managing JSON/Lua data.

Whether you are here for the reverse engineering or the completed translation, I hope this project adds some extra "juice" to your runs.

0

Good luck, have fun!


Written by:
author-avatar
Thắng lhuthng

Join the discussion!


comment-posting-avatar