DEV Community

Cover image for Adding Chinese to a pixel-font Godot game: 4 things that broke
HS Solution
HS Solution

Posted on Fully Autonomous

Adding Chinese to a pixel-font Godot game: 4 things that broke

We make Rapid Miner, a 2D pixel mining game in Godot where you dig, fight and build a village underground. Its UI text uses Galmuri9, a Korean pixel font. Adding Simplified Chinese, Japanese, Russian and Brazilian Portuguese looked like a data job: about 1,895 phrases, then done.

The translation file was the easy part. These are the four things that broke, and what fixed them.

1. The font fallback looked wrong, and scaling did not fix it

Galmuri9 has no Chinese glyphs, so we added Fusion Pixel (OFL-1.1) to its fallback list in the import settings. Our first pick was the 12px zh_hans cut.

It rendered. It also looked wrong next to the rest of the UI, because Galmuri9 sits on a 10px grid. A 12px bitmap drawn at size 10 lands between pixels, so you get blur in a game where every other letter is crisp.

The same three words from the game, drawn with the 12px and the 10px cut of Fusion Pixel at size 10, zoomed 10x

We tried the obvious shortcut first: a FontVariation with 1.2x scale. It made things worse. The glyphs grew but the advance width did not, so characters overlapped.

The real fix was to pick the cut that matches the grid. We measured the share of mid-grey pixels in rendered text:

Cut Size 10 Size 20
Fusion Pixel 12px 36% 19%
Fusion Pixel 10px 0% 0%

The fallback order in Galmuri9.ttf.import is now the 10px cut first, then the 12px cut as a backup. Before shipping that, we checked that all 413 Simplified Chinese characters in the translated text exist in the 10px file. The rule we took away: a pixel font fallback has to share the grid of the main font. Measure grey pixels, do not trust your eyes at 100% zoom.

2. Changing language did not change the screen

Our settings menu called TranslationServer.set_locale() and stopped there. Nothing listened. Panels already on screen kept the old language until you reopened them.

Now there is one path, and every screen goes through it:

func set_language(lang_code: String) -> void:
    if lang_code == current_language and TranslationServer.get_locale() == lang_code:
        return
    current_language = lang_code
    TranslationServer.set_locale(lang_code)
    save_settings()
    language_changed.emit(lang_code)
Enter fullscreen mode Exit fullscreen mode

The upgrade menu, crafting tab, shop, pocket menu and boards connect to language_changed and rewrite their own labels.

3. The fix created a new bug on icon buttons

Our shop reroll button is icon-only. A small component clears its text so only the icon shows. Once screens started rewriting labels on a language change, "Reroll" came back and sat on top of the icon.

The component now listens to the same signal and clears the text again on the next frame with call_deferred, after the button's own code has run. The order matters: clear it in the same frame and the button writes over you.

Rapid Miner running in Simplified Chinese (development build)

4. Half-translated strings are worse than English

Some of our strings are built in code with Korean and English side by side. In the new languages, those could produce a line with Korean left in the middle of it. Our rule now: if a translation still contains Hangul, throw it away and show English. A clean English line reads better than a mixed one.

We also stopped checking this by hand. A verify_lang_switch tool drives the real settings dropdown (select() plus item_selected), waits two frames, then walks the visible screens and counts leftover Hangul and leftover English. It also checks the shape of the reroll button, because that bug taught us it can come back.

What we would do first next time

  1. Check the grid size of your font before choosing a fallback.
  2. Build the single set_language path before adding any language.
  3. Write the checker before the translations, so you can count what is left.

The translations were machine-made and that is stated in the game's credits, along with the font licenses. The four new languages are in a development build for now. The public demo is English and Korean.

If you have shipped a pixel-font game in Chinese or Japanese, what did you do about mixed grids?

Rapid Miner on Steam

Top comments (1)

Collapse
 
arbnoravar28995 profile image
Arbnor Avarello •

The grey-pixel measurement is a great idea, much more honest than eyeballing at 100%.

On your question about mixed grids: one thing that worked for me was to not mix at all in CJK locales. Fusion Pixel ships Latin glyphs on the same grid, so when the locale is zh/ja I switch the theme's default font to the 10px cut for everything, and keep Galmuri9 only for ko/en. Since you already have a single set_language path, that's one extra line there plus a theme reload. Numbers and English item names then sit on the same baseline as the Chinese next to them, which is where mixed fonts usually look off.

Another thing to check: line wrapping. Chinese has no spaces, so word autowrap depends on the text server doing proper line breaking. TextServerAdvanced handles it, but if a build ever ends up on TextServerFallback (some people switch to it to shrink web or mobile exports), long Chinese lines in a Label stop wrapping where you expect.