Texture localization in Planescape: Torment

Recently, our team finally completed the translation of Planescape: Torment. The Ukrainian localization is set to be officially released later, but for the impatient, a Ukrainian localization mod is already available.

Thank you for reporting issues—we fixed them promptly. However, one thing kept bothering us.

Two and a half years of work, a million words translated… And yet, one of the first things a player sees is the main menu in a foreign language 🙁

The thing is, unlike the rest of the game, the text on these buttons is part of the images. We would normally localize textures as well whenever we can, but here we hit a snag. So, if you’re interested in the technical details that localization teams have to deal with, this article is for you. (Spoiler: we managed to pull it off).

Menu Localization

So, the main menu consists of a dozen textures. The first one, for example, looks like this:

As you can see, for optimization, the developers cut everything into hundreds of pieces and packed it all into a kind of texture atlas. Redrawing the buttons in this form is an unrealistic task.

So I started looking for ways to put it all back together. Modding forums didn’t help much. However, after some time, I found out that information on how to reconstruct normal images from these pieces is stored in the animation file: each separate state of each button is encoded as a separate frame. And I finally managed to see this in Near Infinity—an editor for the game engine used for Planescape: Torment and other games.

Having extracted each frame of this animation separately, Yura began ukrainizing them in a graphics editor. Meanwhile, I started searching for a way to add these separate images back into the game.

My first attempt was to use the tool for creating custom animations from Near Infinity itself, but unfortunately, it turned out to be very problematic. Even unedited textures were sliced differently, leading to results like this:

Obviously, this approach wouldnʼt work for us. And when one can’t handle it with existing tools, the only option left is to create your own one!

The animation file turned out to be a binary format with a fairly simple structure. So creating a parser for it was quite straightforward, especially with such a great helper tool like Kaitai Struct:

As shown in the video, the file contains information about all frames, and each frame consists of a number of blocks, which are also taken from the texture atlases we saw at the beginning.

 

Initially, I decided to write a program that would extract all these blocks and then assemble them back into separate frames—basically exactly what Near Infinity already does. At first glance, this seems redundant, but it’s crucial in order to ensure that everything works correctly. In other words—it’s good to have something to compare with 🙂

My language of choice for this task was Go. First, it’s quite easy and convenient, and second, it’s very easy to get an executable for any system that doesn’t need to be “installed.” I mostly use macOS, and most of my colleagues use Windows, so this was a significant factor.

So… The code is written, the separate frames are saved—everything looks correct. Now it’s time for the reverse process: replace the frames with the localized versions, cut them into pieces, and write them back into the texture atlases.

That’s what I did, and then I ran into a second problem:

As you can see, some “borders” appeared in-between the blocks that were absent in the original. To make sure I didn’t make a mistake, I triple-checked that the images were being cut and assembled correctly:

And yet, in the game itself, these barely noticeable outlines were present, which really bothered us:

Surely, we couldn’t release an update for our language patch in this state. So I started looking for a bug in my code… and didn’t find one! 🤯

If you’re even a little familiar with software development, you know that bugs are always and everywhere. I certainly have some too, but clearly not the kind that could cause this result. So I realized I was probably dealing with a peculiarity of this particular game engine.

After spending a few more hours comparing images pixel by pixel in different variations, I finally concluded that you just need to take the blocks 1 pixel wider on each side 🙂 That is, if you have a square of 32×32 at coordinates (0; 0), you actually need to take a square of 34×34 from coordinates (-1; -1). Of course, there are no negative coordinates in images, so all such pieces just remain transparent.

I made the necessary changes to the program’s code—and voilà! Нове життя!

Notice how beautifully “Нове життя” is curved along the circle, while “New life” in the original is simply written straight. We also tried writing it straight at first, but such a long inscription looked very awkward. So Yura couldn’t leave it like that 😎—thank him for that. And also Yehor for a number of useful suggestions.

Character Creation Screen

Besides the main menu, the names of the attributes on this screen also remained untranslated:

I mean those STR, INT, WIS, DEX, CON, and CHA around the character. They’re also “embedded” in the game’s resources in the same texture atlas (in fact, they even share an atlas with the main menu). And since we now have a new tool for this, it would be a shame to leave this untranslated.

And again, we ran into difficulties! The localized text of some attributes was for some reason rendered cut off in the game:


 
See how the letter “С” is a bit cut off on the left, and the letter “Д” seems to be missing a serif? It turns out that despite the image size, the game engine doesn’t use the entire available image area. We didn’t account for this at first, but now everything is fixed.

All in all, it took us about two days. We made a bunch of attempts and further fixes. And now we’re finally happy with the result!

We hope you’ll like it too. So if you haven’t had a chance to try this wonderful game yet, now is the time.

Your SBT team ♥

Written by: Serhii Olendarenko
With contributions from: Yura Dragon, Yehor Vlasov

July 2, 2025

4.8 16 votes
Article Rating
4.8 16 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Latest news

All news
All news
0
Would love your thoughts, please comment.x
()
x