4 ways canvas text rendering breaks in multilingual apps (that en/ja testing will never catch)

작성자

카테고리:

← 피드로
DEV Community · Mxhlix · 2026-07-23 개발(SW)

Mxhlix

I run a large fleet of “preview it, then download it as PNG” web tools — name tags, certificate generators, price cards, badges — in five languages: Japanese, English, Spanish, French, Portuguese.

Canvas 2D text rendering looks correct as long as you only test Japanese and English.

It breaks when you run es/fr/pt through it.

After stepping on these repeatedly, the failures collapse into four patterns.

The premise: Latin languages run 1.4–2× longer than Japanese

Design data first. The same label, measured across five locales:

Example ja en es fr pt Tool name 22 chars 35 62 48 50 “Standard” button 4 8 10 20 12

Rule of thumb: fr/pt come out 1.4–1.7× longer than ja; es can balloon to nearly 3×. A font size and maxWidth tuned to fit Japanese will not fit the Latin locales. All four failure patterns grow from this.

Pattern 1: hand-rolled wrapping via text.split(/\s+/) collapses on CJK

The classic snippet — split on spaces, wrap word by word — does nothing for Japanese or Chinese, where words aren’t space-delimited. An entire sentence becomes one unbreakable token and clips at the canvas edge.

Test with real Japanese input and check that the final line renders to its last character. “Most of it showed up” is not a pass.

Pattern 2: an ASCII-only tokenizer splits words at accented characters

Fix pattern 1 with a character-class tokenizer like [A-Za-z0-9'\-_] and you’ve traded one regression for another: ç é ã ó ñ aren’t in that class, so produção fragments into produ / ç / ão mid-word.

An English test will never catch this.

Generate actual PNGs with fr/es/pt samples and eyeball the area around accented characters. I never found another detection method — string-comparison tests can’t see a rendering-level split.

Pattern 3: the important word at the end vanishes into “…”

Since fr/pt run 1.4–1.7× longer than the ja the layout was tuned for, text overflows its two lines and gets ellipsized. The cruel part: what disappears is the tail of the phrase — often the semantically critical word, like production / produção.

The fix was splitting fields into two classes. Fields that may truncate (descriptions) wrap-and-ellipsize. Fields that must never truncate (titles, names) get auto-fit: measure on an offscreen canvas with measureText, search for a font size that fits, render at that size. A title with an ellipsis is a broken deliverable, so never mix the two strategies.

Pattern 4: the preview and the downloaded PNG break lines in different places

The sneakiest one. Implement pattern 3’s auto-fit only on the canvas side while the DOM preview keeps its fixed font size, and the line breaks you see on screen won’t match the line breaks in the PNG. From the user’s perspective, the tool’s core promise — “download what you see” — is broken.

The fix: centralize the auto-fit font-size decision in one place (the offscreen canvas measurement) and apply the resulting size to both the canvas render and the preview DOM’s style.fontSize. My pass condition: preview line count equals PNG line count, in all five locales.

Pre-release checklist

For every new image-output tool:

  • [ ] ja: CJK renders to the last character of the final line, no edge clipping
  • [ ] fr / es / pt: generate real PNGs; no word splits around ç é ã ó ñ
  • [ ] fr / pt: no ellipsis eating the important trailing words
  • [ ] all five locales: preview (DOM) and downloaded PNG agree on line breaks and line count

Declaring “multilingual support: done” after checking only en and ja was the single biggest trap.

Limits and caveats

The character-count ratios are measured from my own fleet (utility-tool names, descriptions, UI labels). Different content genres will shift the ratios.

On automation limits: patterns 2 and 4 resist static analysis entirely. Generating PNGs and looking at them was the only detection that worked. Five locales × generation checks is a real cost — whatever you skip surfaces in production instead.

Verified: April–May 2026. Environment: Canvas 2D API / vanilla JS / ja·en·es·fr·pt locales in production.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다