slowlp
← Blog
Lesson 2026.06.16 · 6 min read

Neither My Mom Nor I Speak English — But the Museum Went Multilingual Anyway

Running a multilingual site without a translator — filling the DB with an LLM pipeline

Lesson

Neither My Mom Nor I Speak English — But the Museum Went Multilingual Anyway

Running a multilingual site without a translator — filling the DB with an LLM pipeline

After getting the museum up, I got greedy.

My mom’s work deserves a wider audience. The way her calligraphy, folk painting, and lettering all converge — words and images meeting on the same canvas — that’s something uniquely hers. Keeping it Korean-only felt like a waste.

The problem was simple: neither my mom nor I speak English.


Hiring a professional translator was off the table from the start. The cost wasn’t the only issue — every time a new piece gets added, we’d need to commission a new translation. My mom is still actively creating and exhibiting. The goal was a living museum, not a one-time build.

So the structure became: Mom inputs Korean only. A script handles the English.


The DB has titleEn, descriptionEn, and similar columns on the artwork table. They’re null by default. When my mom adds or updates a piece through /admin, she only fills in Korean. There’s one important detail: whenever Korean content is updated, the paired *En column is automatically set to null. That way stale translations never linger. If En is null, the English page falls back to showing the Korean original. Even with zero translations, the site works fine.

The translation flow looks like this:

# Step 1: Export untranslated Korean fields to a file
npm run db:translate -- --export data/translations.json

# Step 2: An agent fills in the "en" fields
# (The /translate-en project skill handles steps 1–3 together)

# Step 3: Write the filled file back to the DB
npm run db:translate -- --import data/translations.json

Step 2 is the key. Instead of calling an LLM API directly, I delegate via file. The reason: Hermes (the Discord bot running on my dev server) and Claude Code are subscription-based agents — no per-token charges. Handing them a translation file means the work gets done on a subscription I’m already paying for.

When I ran this against the production DB, there were 31 artworks. Translation targets: 263 fields. I reused 166 previously translated fields from local testing and only processed 97 new or changed ones. 73 rows were written back to the DB.


One thing isn’t fully resolved yet.

Transliteration of traditional art terminology. Whether to render hwaseonji (화선지) as “Hwaseonji” or something more descriptive. Whether sungan bunche (순간분채) maps to “mineral pigment” or something else. Some artwork titles need review too. The LLM does well with general translation, but traditional Korean art terms — their romanization and conventional English equivalents — need a human pass.

That’s actually fine with this setup. Any field can be re-filled with --force, and anything unreviewed shows the Korean original as fallback. The imperfect state is safe.


The museum runs multilingually without a translator.

My mom still inputs everything in Korean. I run npm run db:translate occasionally, and it’s done. Translation burden doesn’t scale with the number of works.

If language has been your reason for not reaching an international audience, try building the structure first before hiring anyone. Add *En columns to your DB, delegate translation to a file-based LLM workflow, and auto-null the English when Korean gets updated. That’s enough to keep it running.

COMMENTS