Skip to content
Development
Skill

/cm-safe-i18n

Safe i18n: multi-pass batching, parallel-per-language dispatch, 8 audit gates, HTML integrity. Use for translating or mass-converting hardcoded strings to t() calls.

From plugin
cm
5362 skills8 agents11 commands3 hooks
+1
Install
$ npx -y skills add tody-agent/codymaster --skill cm-safe-i18n --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/cm-safe-i18n

Context preview

The summary Claude sees to decide when to auto-load this skill.

Safe i18n: multi-pass batching, parallel-per-language dispatch, 8 audit gates, HTML integrity. Use for translating or mass-converting hardcoded strings to t() calls.

SKILL.md

cm-safe-i18n.SKILL.md
name: cm-safe-i18n
description: "Safe i18n: multi-pass batching, parallel-per-language dispatch, 8 audit gates, HTML integrity. Use for translating or mass-converting hardcoded strings to t() calls."

Safe i18n Translation v2.0

Overview

Mass i18n conversion is the most dangerous code transformation in a frontend monolith. A single-pass conversion of 600+ strings corrupted `app.js` beyond repair while 572 backend tests passed green. Additional incidents include HTML tag corruption, variable shadowing, and placeholder translation errors.

**Core principle:** Every batch of i18n changes MUST pass ALL 8 audit gates before proceeding. No exceptions.

**Violating the letter of this rule is violating the spirit of this rule.**

The Iron Law

NO BATCH WITHOUT PASSING ALL 8 AUDIT GATES.
NO LANGUAGE FILE WITHOUT KEY PARITY.
NO DEPLOY WITHOUT FULL SYNTAX VALIDATION.
NO HTML TAG MODIFICATION — TEXT CONTENT ONLY.
NO REGEX TO FIX REGEX ERRORS — USE LEXICAL SCANNER.

When to Use

**ALWAYS** when any of these happen:

  • Extracting hardcoded strings to `t()` calls
  • Adding new language file (e.g., `ph.json`)
  • Mass-converting strings across >10 lines
  • Updating translation keys or namespaces
  • Migrating i18n library or pattern

**Don't use for:**

  • Adding 1-3 translation keys (just add manually + test)
  • Fixing a single typo in a JSON file

The Protocol

digraph i18n_flow {
    rankdir=TB;
    "0. Pre-flight" [shape=box];
    "1. Scan ALL files" [shape=box];
    ">10 strings?" [shape=diamond];
    "Manual add + test" [shape=box];
    "2. Plan passes" [shape=box];
    "3. Extract batch (max 30)" [shape=box];
    "4. 8-Gate Audit" [shape=box, style=filled, fillcolor="#ffffcc"];
    "All 8 pass?" [shape=diamond];
    "FIX or ROLLBACK" [shape=box, style=filled, fillcolor="#ffcccc"];
    "More batches?" [shape=diamond];
    "5. Parallel language sync" [shape=box];
    "6. Final validation" [shape=box];

    "0. Pre-flight" -> "1. Scan ALL files";
    "1. Scan ALL files" -> ">10 strings?";
    ">10 strings?" -> "Manual add + test" [label="no"];
    ">10 strings?" -> "2. Plan passes" [label="yes"];
    "2. Plan passes" -> "3. Extract batch (max 30)";
    "3. Extract batch (max 30)" -> "4. 8-Gate Audit";
    "4. 8-Gate Audit" -> "All 8 pass?";
    "All 8 pass?" -> "FIX or ROLLBACK" [label="no"];
    "FIX or ROLLBACK" -> "4. 8-Gate Audit";
    "All 8 pass?" -> "More batches?" [label="yes"];
    "More batches?" -> "3. Extract batch (max 30)" [label="yes"];
    "More batches?" -> "5. Parallel language sync" [label="no"];
    "5. Parallel language sync" -> "6. Final validation";
}

---

Phase 0: Pre-Flight Checks (NEW)

Before ANY i18n work:

# NEVER work on main
git checkout -b i18n/$(date +%Y%m%d)-target-description

# Verify baseline is clean
node -c public/static/app.js
npm run test:gate

If either fails, DO NOT PROCEED. Fix the baseline first.

---

Phase 1: Scan ALL Frontend Files (IMPROVED)

> [!CAUTION] > **Lesson #11:** `import-adapters.js` and `import-engine.js` had 60+ hardcoded strings that were initially missed because only `app.js` was scanned.

Scan EVERY file that produces user-visible UI text:

# Scan ALL .js files for Vietnamese strings
node scripts/i18n-lint.js

# Also check non-app.js files
grep -rnP '[àáạảãâầấậẩẫăằắặẳẵ]' public/static/*.js --include="*.js" | grep -v "\.backup" | grep -v "i18n"

Group strings by **functional domain** — never by file position:

| Pass | Domain | Example Keys | |------|--------|-------------| | 1 | Core UI | `sidebar.*`, `common.*`, `login.*` | | 2 | Primary Feature | `vio.*`, `emp.*`, `scores.*` | | 3 | Config & Settings | `config.*`, `benconf.*` | | 4 | Reports & Export | `report.*`, `export.*` | | 5 | Secondary Files | `import-adapters.js`, `import-engine.js` | | 6 | Edge cases | Tooltips, error messages, dynamic labels |

**Output:** A numbered list of passes with estimated string count per pass per FILE.

---

Phase 2: Extract Batch (MAX 30 strings per batch)

> [!CAUTION] > **MAX 30 strings per batch. Not 31. Not "about 30". Exactly 30 or fewer.** > The i18n crash happened because 600+ strings were done in one pass.

For each batch:

1. **Identify** up to 30 hardcoded strings in the current pass domain 2. **Generate** namespace-compliant keys: `domain.descriptive_key` 3. **Replace** strings with `t('domain.key')` calls 4. **Add** keys to the **primary** language JSON (usually `vi.json`)

String Replacement Rules (12 Bug Categories Encoded)

// ✅ CORRECT — backtick template with t() inside
`<div>${t('login.welcome')}</div>`

// ✅ CORRECT — concatenation
'<div>' + t('login.welcome') + '</div>'

// ❌ BUG #1 (FATAL) — single-quote wrapping template expression
'${t("login.welcome")}'     // ← THIS DESTROYED APP.JS

// ❌ BUG #4 — mismatched delimiters
t('login.welcome`)           // ← quote/backtick mismatch
t(`login.welcome')           // ← backtick/quote mismatch

Ternary Inside Template Literals (Bug #5)

// ❌ BROKEN — single-quote ternary result with template expression
${ canDo ? '...${t('key')}...' : '' }

// ✅ CORRECT — backtick ternary result
${ canDo ? `...${t('key')}...` : '' }

Variable Shadowing (Bug #3)

// ❌ BROKEN — shadows global t() translation function
items.map((t, i) => `<div>${t('key')}</div>`)

// ✅ CORRECT — use different variable name
items.map((item, i) => `<div>${t('key')}</div>`)

HTML Tag Protection (Bug #2)

// ❌ NEVER modify content inside HTML tags
`< div class="card" >`    // spaces inside tags = broken rendering
`style = "color: red"`     // space around = breaks attributes
`<!-- text-- >`            // broken comment closers

// ✅ ONLY replace text content between tags
`<div class="card">${t('card.title')}</div>`

Static Keys Only (Bug #8)

// ❌ FORBIDDEN — dynamic keys can't be statically validated
t('nav.' + pageName)
t(`messages.${type}`)

// ✅ REQ
Read more
Ships withcm

"I can't write code. But in 6 months, I shipped 12 real products using AI. CodyMaster is everything I learned — so you don't have to repeat my mistakes." — Tody Le, Head of Product, Creator of CodyMaster 50+ skills. One install.

Get the whole plugin

Other skills on cm.