◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 45 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/Cowork
◈ Cowork/2026-05-30Advanced

Localizing an App Store Screenshot PSD into 15 Languages Overnight with Cowork

A work log of localizing the store screenshots for the Android wallpaper app 'Beautiful Wallpapers' into many languages with Cowork: extracting text with psd-tools, pouring translations into layer names, and the real walls I hit with Thai and Korean glyphs.

Cowork46psd-toolsLocalizationApp Store9Google Play3PhotoshopMultilingualASO

✦ Premium Article

The moment I opened the PSD from my designer, I froze. 1440x13333px, 220MB. It held the store screenshots for my Android wallpaper app "Beautiful Wallpapers," with Japanese headline copy scattered across five artboards. I wanted to expand it into at least ten, ideally fifteen languages — and I wanted to start swapping the store assets the next morning.

Translating and replacing the Photoshop text layers by hand would mean 5 boards x 15 languages, or 75 screens of copy-paste, where mix-ups are guaranteed. So I handed the PSD itself to Cowork and let it process the file mechanically.

Doing so, I ran straight into an obvious truth: fonts and glyphs are far more stubborn than translation. This is the log of that one night.

Why screenshot localization can be cleanly separated from making art

The first thing I settled in my own mind was where to draw the line on letting AI help. I make contemporary art alongside indie development, and I have consistently kept AI out of the artwork itself. Making things by hand carries meaning of its own, and I want to preserve that.

But putting a work or an app "out into the world" is a different job from making it. A store screenshot is not the substance of the wallpaper product; it is closer to the price tag or the point-of-sale display that puts it on the shelf. So screenshot layout and localization fall, without hesitation, into the designer work I am happy to delegate to AI.

Keeping creation and promotion consciously separate lets me make this call in an instant. When that line stays blurry, you end up relitigating "how far should I automate this?" on every single task.

Count what the PSD is made of, first

Before localizing, I need to know exactly what the PSD is built from. The Cowork environment can install the Python edition of psd-tools, so I opened the file and dumped its structure.

One caveat: pip in that environment is protected by PEP 668, so a plain install stops with externally-managed-environment. I covered that in When pip install stops with externally-managed-environment in Cowork's Bash, and reading it first will save you a detour.

from psd_tools import PSDImage
 
psd = PSDImage.open("screenshot.psd")
print(f"canvas: {psd.width} x {psd.height}")
 
for layer in psd.descendants():
    kind = layer.kind  # 'type' / 'pixel' / 'group', etc.
    if kind == "type":
        print(f"[text] {layer.name!r} @ {layer.bbox} -> {layer.text!r}")
    elif kind == "group":
        print(f"[group] {layer.name!r} children={len(list(layer))}")

The output showed that each of the five artboards was a group, with roughly 40 layers per board (two text streams: headline and description).

One thing mattered here: the PSD that had been sitting in my workspace and the uploaded one had different resolutions. The canonical file was the uploaded 1440x13333px, 220MB version, so I pinned the script to extract and render from that file. When you receive several copies, deciding which one is canonical exactly once, up front, saves you from rebuilds later.

If the PSD arrives through a cloud-synced folder, you can hit a stranger problem: bash claims the file does not exist while Finder shows it plainly. I wrote about that in Why Cowork's bash says "no such file" when Finder can see it. With a 220MB file, materialize it before you touch it.

✦

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
✦Follow the full hands-on flow of taking a 1440x13333px, 220MB store PSD, counting its artboards and text layers mechanically with psd-tools, and pouring 15 languages into it
✦See working code for the mac_roman trap, where psd-tools fails to save non-ASCII layer names, and how writing the luni block directly resolves it
✦Take away an auto-shrink implementation for translations that overflow the canvas, plus a clear line for where automation should stop and hand back to a human
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

Claude 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

◈ Cowork2026-10-15
Visible in the Listing, Zero Hits by Name: Counting Japanese Filenames With Split Dakuten
In a folder I gave Cowork, Japanese filenames were missing from name searches. Counting about 19,000 entries turned up 17 names with decomposed dakuten. Here is how I counted and fixed it.
◈ Cowork2026-10-14
I Put a Ten-Line Promise File at the Top of the Folder I Gave Cowork
I kept retyping the same cautions every time I asked Cowork for help. One CLAUDE.md file at the top of the folder settled it. Here are the three things worth writing, how to check it is being read, and how to cut it back when it grows.
◈ Cowork2026-10-11
I Stopped Making Cowork Read My Whole Troubleshooting Notes — Pulling One Section by Number Cut 105 KB to 1.5 KB
A record of turning a 105 KB file of web-ops troubleshooting notes into something you query by number. My first extraction script could reach only 30 of 120 numbers, and #70 came back as zero bytes. The fixed awk, a zero-byte check, and measured section sizes are all here.