I'm a solo dev in South Korea, and since September I've been running Yeoul Games, a small hub where indie devs list browser games that anyone can play without sign-up or install. It started as a home for my own games and a Korean text MUD; other Korean solo devs started listing theirs after that.
The whole thing runs in Docker containers on a Synology NAS at home, with a Python web server. This month I made the interface work in eight languages (Korean, English, Japanese, Portuguese, Spanish, Vietnamese, Turkish, German), switched with ?lang=en. Here's how, and where it broke.
1. A translation table for fixed UI text
All interface strings live in a JSON table. Every HTML response passes through a filter that swaps each Korean string for its translation. When a sentence is split by HTML tags, the key stays the full sentence so the replacement is exact.
2. Game descriptions, translated automatically
When a dev uploads a game, they tick which languages the game itself supports. Regardless of what they tick, the description they write is sent to a free LLM API (Groq) and translated into the other seven languages. That way a visitor from Brazil can at least read what a Korean-only game is about.
A background thread fingerprints the source fields; if the dev edits the text, it gets re-translated:
import hashlib, json, re, time
def src_hash(src, fields):
"""fingerprint of the translatable fields (name, description...)"""
body = {f: canon(get_field(src, f)) for f in sorted(fields)}
raw = json.dumps(body, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
def auto_ok(src, tr):
"""reject a translation that still contains hangul not present in the source"""
if isinstance(src, list):
return isinstance(tr, list) and len(tr) == len(src) and all(auto_ok(a, b) for a, b in zip(src, tr))
if not isinstance(tr, str) or not tr.strip():
return False
return all(w in str(src) for w in re.findall(r"[가-힣]+", tr))
def auto_loop():
time.sleep(90)
while True:
r = auto_once() # one game x one language group per round
time.sleep(70 if r else 240) # stay under the free-tier rate limit
When a page is rendered, a translation is only used if its stored _src fingerprint matches the current text. If the dev edits the description, the Korean original shows until the new translation lands, so a stale translation never describes something else.
3. Notices and rules
I write notices and rules in Korean. They're machine-translated on non-Korean pages, always with a link to the Korean original, which stays authoritative. Other users' posts are left exactly as written.
4. A "playable in my language" filter
On non-Korean pages there's a filter for games playable in your language, based on what the dev ticked. Since most games are Korean-only for now, it shows very few results.
Where it broke
The free tier's tokens-per-minute limit. I started getting HTTP 413 and 429. The fix was sending less per request (one game x one language group), capping max_tokens, and waiting 70 seconds between requests (240 when idle). Even then some translations came back half done, for example a Japanese sentence that ended in Korean characters. I fixed those by hand at first, then added auto_ok above, which throws away any translation that still has hangul the source didn't have.
What still doesn't work
There are 42 games listed, almost all by Korean solo devs, and the games themselves are still in Korean. Only the interface and the descriptions are translated, and the translation quality still slips in places.
If you make browser games
That's the gap I'd love help with. Listing is free: upload a single HTML file, or just link to your game's page. Your description gets translated into the other seven languages, there are no ads or fees, and I also introduce listed games in Korean communities.
Feedback on the approach, especially on whether auto-translated descriptions help or just set wrong expectations, is very welcome.
Top comments (0)