October 20265 min readUnder the hood
A new core in Rust, and nothing changed
Aside began as a Python program with a browser window. The first public build is one fast desktop app. Here is how the engine was replaced without changing what it does.
The rule: no new features during the port
A rewrite that also changes behaviour cannot be checked. So the port had one rule: the new core must give exactly the same answers as the old one. Bugs found on the way were fixed in the old code first, and only then ported.
Recorded answers instead of reading code
Before a line of Rust was written, the old core recorded its answers: fixed inputs (parser cases, archives, checks, whole synthetic projects) and the exact output for each. Every module of the new core had to reproduce them byte for byte, including the files it writes.
749recorded cases, all matching
80operations ported one by one
11milestones, each a reviewed step
Then the real games
Synthetic data only goes so far. The last check opened copies of the translations of eight real games with both cores and asked both the same questions: overview, files, every filter, checks, glossary, the scene and the menu behind sampled lines.
8 968 questions on 8 games. Zero differences.
The same interface, without a server
The interface did not change at all. In the old app it talked to a small local web server; in the new one the same requests go to the core inside the window, so no network port is opened. The interface's own self-test, 139 checks, passes inside the installed app.
The Pro features live in a separate program next to the app. That keeps the free app free of Pro code, and lets Pro update on its own.
↑ All posts
October 20264 min readFor translators
Why one missing {/i} crashes a game
A Ren'Py line is more than text. Some of what you see in a translation file is instructions for the engine, and a mistake there is a mistake in code.
Text tags
Tags in curly braces style the text or control it: {i} for italics, {b} for bold, {w} to wait for a click. A tag that opens must close, in the right order. When a closing tag goes missing in the translation, the game can stop on that line with an exception, in front of the player.
# original
mira "Did you bring the {b}letter{/b}?"
# translation
mira "Přinesl jsi ten {b}dopis?" ← {/b} is missing
mira "Přinesl jsi ten {b}dopis{/b}?" ← fine
Variables
Square brackets insert a value: [player_name] becomes the name the player typed. Translate the word inside and the game looks for a variable that does not exist. A typo has the same effect.
mira "Welcome back, [player_name]."
mira "Vítej zpátky, [jméno_hráče]." ← NameError: not defined
The quieter mistakes
- A
\n line break lost or added changes the layout.
- A number that differs from the original ("3 days" became "4 dny").
- A line that is much longer than the original and no longer fits the dialogue box.
- English words left behind in a translated line.
How Aside handles it
Every translation is compared with its original as you type and on every refresh: tags, variables, format marks, line breaks, numbers, length and the fit in the box, measured with the game's own font. Errors that would crash the game are marked apart from warnings that may be intended. You can try it on the home page.
↑ All posts
October 20264 min readFor translators
The texts the generator never sees
You translated every line in every file, released, and a player sends a screenshot with English in it. It was never in your files.
What the generator writes
Ren'Py's translate command writes blocks for dialogue, menu choices, screen texts and strings marked with _(). Text shown any other way has no block, so it never reaches the translator:
show text "Three days later..."
- prompts of
renpy.input("What is your name?")
- notifications with
renpy.notify(...)
- a chapter title kept in a variable and shown later with
[title]
- the game's own functions that show text
How Aside finds them
Aside reads the game's scripts, compares them with your translation and lists what is missing, split into likely and maybe. You tick what the player sees, and it lands in extra_strings.rpy as lines to translate, in a strings block Ren'Py reads like any other.
24 of 27texts a translator had added by hand were found
185likely texts listed for the same game
0texts written twice
Measured on Being a DIK. The other 3 of those 27 were not really missed: the generator had extracted two of them, and one is assembled from pieces in the code. It is a heuristic, so the list is always yours to review.
↑ All posts