◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Expo SDK 58 is out of beta as of 10/15 (beta since 09/15). Check your dependencies before upgrading●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/App Dev
◇ App Dev/2026-06-04Intermediate

How I Localized My Play Store Screenshots into 16 Languages — Swapping PSD Text with Python and Standardizing on Noto Fonts

An implementation log of localizing an Android wallpaper app's store screenshots into 16 languages: swapping PSD text layers per language with Python, auto-shrinking overflow, and assigning the right font per script — with the actual code and a font cheat sheet.

ASO27Localization8Google Play39PSDFontsIndie Dev40

✦ Premium Article

"Make sixteen languages of store screenshots." It's one sentence to say, but the moment you pour translated copy into a finished design, you hit a dull wall: the layout breaks. German runs about 1.8× longer than Japanese and overflows the button. Arabic flows right-to-left and flips everything. Korean and Thai don't even have glyphs in the font you have on hand. I've been building iOS and Android wallpaper apps on my own since 2014 — they've grown past 50 million cumulative downloads — and this "make the final look right" stage is still what eats the most time.

I recently rebuilt the store screenshots for the Android app Beautiful Wallpapers (Beautiful Wallpapers on Google Play) into 16 languages: English, Spanish, French, German, and the rest. Instead of retyping 16 languages × 5 frames by hand in Photoshop, I switched to swapping the PSD's text layers per language with Python, and the job got noticeably lighter. This article is that implementation log. Not clean theory — the code I actually ran, the places I tripped, and the cheat sheet of which font I assigned to which language.

Why swap the text instead of baking it into pixels

Let me start with the approach. When people think "automate multilingual screenshots," the first idea is usually to render each language's text as an image and composite it onto the background (a transparent overlay PNG). I built that too. But it has a weakness: you can't change the font afterward.

With the overlay approach, the text is frozen into pixels the moment you generate it. If you later decide "the Korean heading should be a touch bolder," your only option is to re-run the script. As an indie dev, I want to do the final visual tweaks in Photoshop, by hand, to the very end.

So the approach I committed to was swapping only the string, per language, without rasterizing the text layer. I duplicate the original Japanese type layers, replace the string inside with the translation, and produce an "editable text PSD." Drag a language group onto the master and it lands in the exact same spot, with the font and size still fully editable in Photoshop. In practice, that single property was the biggest win.

For the record: I use none of this automation on the artwork itself (the wallpapers). The automation is strictly for the store-listing side — operations and promotion. I keep that line fixed.

Understand the source structure first

The source PSD was 1440 × 13333px with five artboards stacked vertically, each 1440 × 2560px (9:16). There are about 40 text layers total — title, subtitle, feature bullets, number badges — distributed across the artboards.

The first job is to make those text layers machine-pickable. With psd-tools you can recurse into groups and pull out only the layers where kind == 'type'.

from psd_tools import PSDImage
 
def find_types(layers):
    """Recurse through groups and yield only text layers."""
    for layer in layers:
        if layer.kind == 'type':
            yield layer
        if layer.is_group():
            yield from find_types(layer)
 
src = PSDImage.open("screenshot.psd")
for t in find_types(src):
    # layer.text is the visible string, layer.bbox is the coordinates
    print(repr(t.text), t.bbox)

layer.text gives the visible string and layer.bbox gives (left, top, right, bottom). By checking which artboard's range a layer's top falls into, you can mechanically decide "this line is the heading of screenshot 5." Keep a translation dictionary keyed on the original Japanese string (TR[japanese] = {"en": "...", "de": "...", ...}) and the layer matches its translation cleanly through layer.text.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦If you've been stuck where the translation is fine but the layout breaks every single time, you'll get a repeatable workflow that swaps PSD text per language and auto-shrinks overflow
✦You'll learn the real psd-tools code that edits text layers without rasterizing, plus a per-language cheat sheet of which Noto font to assign for Cyrillic, Hangul, Thai, and Arabic
✦You'll be able to mass-produce 16 languages of screenshots with a script instead of by hand, and apply the same flow to your own app's store presence
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◇ App Dev2026-03-22
Creating App Store Screenshots with Figma
A Figma workflow for App Store and Google Play screenshots, rebuilt around the current official specs (checked October 2026): which sizes to make, how to leave room for long translations, and how to export and re-check before submitting.
◇ App Dev2026-09-09
Those 14 Days of Closed Testing Are for Gathering What the Production Form Asks
Google Play production access starts from a closed test with 12 testers opted in continuously for 14 days. Here is how the count actually works, the order the tracks open in, what the application form asks, and the records worth keeping during those two weeks.
◇ App Dev2026-09-07
Sending user text to an external AI: what actually goes in Play's Data safety form
Classifying user input sent to an external AI as collected, shared, or ephemeral in Play's Data safety form, plus a redaction ordering bug found by running the code.