Skip to content

t1k:cocos:playable:localize

FieldValue
Moduleplayable
Version2.14.4
Efforthigh
Tools—

Keywords: dich-thuat, i18n, language-switch, locale, localization, localize, multi-language, multilanguage, ngon-ngu, playable-localize, translate, translation

/t1k:cocos:playable:localize
[--langs es,pt-BR,ja] [--assets <path>] [--scan-only] [--no-prefab] [--json]

Cocos Playable — End-to-End Localization

Section titled “Cocos Playable — End-to-End Localization”

Turn an English-only playable into a multi-language one in a single run: scan → translate → generate → wire → verify. Built on @playablelabs/localization, whose auto-binder means ordinary labels need no component and no key.

IntentPath
”Localize this game” / “add multi-language”Full run → Workflow
”What text does this game have?”--scan-only → stop after Phase 1
”Translate but don’t touch my prefabs”--no-prefab → skip Phase 6
”Add one more language later”Re-run; Phase 4 merges into existing tables, never overwrites
”Text still shows English at runtime”Gotchas rows 1-3

es, pt-BR, fr, de, ja, ko, zh-CN, ru, id, vi — the ten markets that carry most playable-ad revenue. Override with --langs. English is never a table: it is the text already authored in the editor.

  1. Locate the Cocos assets/ dir (--assets, else find the dir containing scene/ + scripts/).
  2. Confirm @playablelabs/localization exists under assets/packages/@playablelabs/. If absent, STOP and tell the user to install it — this skill wires that package, it does not vendor a copy.
  3. Inspect git status --short; preserve unrelated user changes and keep localization edits separable.
Terminal window
node <skill>/scripts/scan-text.cjs --assets <assets-dir> --json

Reports unique translatable strings, non-text strings it skipped, code sites assigning .string, and font coverage. Read references/scan-report-guide.md for how to interpret each field.

Never hand-roll this scan. A scene often contains zero labels because all text lives in prefabs, and prefab instances override text via CCPropertyOverrideInfo — the script handles both.

The scanner cannot read words baked into PNG/JPG pixels. Inspect title, CTA, win/lose, and logo sprites in the actual browser preview; replace baked UI copy with Labels or provide an explicit per-locale sprite system before declaring the game localized.

If the report shows any `customFonts` entry AND the target languages include a non-Latin script (`ja ko zh-CN zh-TW ru th ar he hi`), you MUST stop and `AskUserQuestion` before translating. A custom TTF ships the glyphs its designer chose — almost always Latin only — so non-Latin text renders as blank boxes. This is invisible in code review and in an English playtest.

Offer: per-language fonts (keep the designed face where it works, swap only for scripts it cannot draw — TextBinder.setFontMap) · a subset font covering every shipped language · drop the non-Latin languages · proceed anyway, explicitly accepting blank text. No override flag skips this gate.

State the visual cost of each option before asking. A font covering CJK is never a display face, so “one font for everything” always means the UI typeface changes — say so rather than presenting it as free.

Subsetting recipe when a covering font is needed: include the scanned strings plus printable ASCII, then subset an official TrueType source instead of renaming a CFF/OTF file. For Noto CJK, instantiate NotoSansCJKsc-VF.ttf at wght=400 with fontTools.varLib.instancer, then subset it with fontTools.subset and --no-hinting. Keep the SIL OFL license beside the project notices. Never ship an OS font — Windows and macOS faces are not redistributable.

Re-verify the written output, not the in-memory object: reopen it with TTFont, require glyf + loca, and compare its cmap against every locale value and printable ASCII. A typical playable subset is roughly 50–150KB depending on copy.

Translate the scanned strings per references/translation-guide.md: keep length within the source’s budget (buttons do not reflow), preserve {placeholder} tokens exactly, match the game’s register, never translate brand names.

Write assets/scripts/localization/locales/<lang>.ts plus a barrel, using the source English string as the key. Merge with any existing table — never drop a key a human edited. Shape and examples: references/wiring-guide.md.

  1. Insert one setupLocalization({ locales, fallback: 'en' }) at the earliest bootstrap (usually GameView.onLoad, or right after ad parameters arrive).
  2. Convert every code write the scan flagged. Use Localization.setText(label, key, params) for translated copy and Localization.setLiteralText(label, value) for percentages, timers, IDs, and other non-translatable dynamic text. Any raw label.string = ... can be overwritten by the next language or font sweep.
  3. After a parameter tool applies Label.string, rebind it immediately with setText or setLiteralText. Include parameter defaults (text, title, subtitle, ctaText) in locale coverage.
  4. Optional: add a language SelectParameter so the ad network can pick the locale.

Prefabs spawned through a pool or instantiate may not be in the scene when the binder sweeps. Add LocalizedRoot to those prefab roots (one per prefab, not per label). Mutating .prefab files requires an explicit AskUserQuestion confirmation and a git branch. Skipped entirely under --no-prefab.

Run node --test <skill>/tests/scan-text.test.cjs, then run scan-text.cjs again: every raw .string site should now be gone. Preview the real game in a browser through gameplay and both end cards; a clean text scan does not prove image-baked copy is localized. Report per language: strings translated, keys skipped, placeholder mismatches, baked-text sprites, font risk, files changed, and anything left for a human.

  • Never mutate .prefab/.scene to inject translated text. The runtime translates; the editor keeps English. Rewriting authored text destroys the key the binder relies on.
  • Never write an en table. Naming en as fallback registers it; a key missing everywhere resolves to itself.
  • Never translate numbers, percentages, {placeholder} tokens, asset paths, or brand/product names.
  • Never leave numeric/dynamic text on raw .string. Bind it with setLiteralText so a scene/font sweep cannot restore a stale captured value.
  • Never ship blank table values. Blank/invalid translations fall back safely but remain release-blocking diagnostics.
  • Re-runs merge. Existing translated values win over freshly generated ones unless the source English string changed.
SymptomCause
Text still English at runtimesetupLocalization never called, or called after the labels enabled
One screen never translatesIts prefab is pooled/detached — needs LocalizedRoot on the root
Dynamic label shows a stale value after switchingSite still assigns .string directly instead of Localization.setText
Percentage/timer jumps back after font loadNon-translatable text needs Localization.setLiteralText
Parameter text revertsParameter apply wrote .string without rebinding it afterward
pt-BR/zh-CN never selectsUse runtime tags with hyphens; identifier exports pt_BR/zh_CN are normalized by package v0.2+
Explicit key renders ui.some_keyProvide English fallback text to setText/LocalizedLabel
Whole playable renders in the wrong languagefallback names a language that has no table and an old package build; upgrade — current versions auto-register it
ja/ko/zh/ru shows empty boxesCustom TTF lacks those glyphs — Phase 2 gate
A translated string overflows its buttonLength budget ignored; see references/translation-guide.md
Scan finds text nobody seesIt read a source prefab whose instance overrides _string; the script prefers overrides — do not bypass it

Cocos Creator 3.8.7 playable ads using @playablelabs/localization. Per-language fonts are supported via TextBinder.setFontMap. Localized sprites and RTL layout are not — the package does not support them.