# I18N Messages

> Editing extension i18n message files and their consumers. Use when editing packages/*/src/messages/i18n.ts, adding/changing nls.localize strings, or choosing placeholder tokens (%s/%d/...).

- Skill: `forcedotcom/i18n-messages` (Agent Skill)
- Install (CLI): `npx skillmds@latest add forcedotcom/i18n-messages`
- Raw SKILL.md: https://api.skillmd.com/api/skills/forcedotcom/i18n-messages/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: forcedotcom (https://skillmd.com/u/forcedotcom)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/forcedotcom/i18n-messages

---


# i18n Messages

Scope:
- `packages/*/src/messages/i18n.ts` + code consuming those messages via `nls.localize`
- NOT `package.json`/`package.nls.json` nls strings (command titles etc.)
- NOT unrelated code

## Placeholder semantics

`nls.localize(key, ...args)` substitutes via `node:util.format` — `packages/salesforcedx-vscode-i18n/src/i18n/message.ts:50` (`return format(possibleLabel, ...labelArgs)`).

| Token | Use | Behavior (`node:util.format`) |
|---|---|---|
| `%s` | string | string |
| `%d` | count/number | number as-is (`3.7`→`3.7`, no truncation) |
| `%i` | integer | truncates float (`3.7`→`3`) |
| `%f` | float | float |
| `%j` | value | JSON |
| `%o` `%O` | value | inspected object |
| `%c` | — | CSS directive, no output |
| `%%` | literal `%` | `%` only when args present; bare string skips `format()` (`message.ts:37` `args.length > 0` guard) and keeps `%%` |
| `%n` | NONE | unsupported — renders literal `%n`, NOT substituted |

Default: counts → `%d`, strings → `%s`. (Origin: `Removed %n orgs` shipped literal `%n`.)

### Arg-count gotcha

`message.ts:39` counts specifiers with `/%[sdifj%]/g` (`%%` excluded, L40). On mismatch it logs and trims extra args (L41-48); missing args are NOT filled — trailing unmatched tokens render literally.

- `%o` `%O` `%c` substitute at runtime but are NOT in that regex → they escape arg-count validation. Avoid relying on them; prefer `%s`/`%d`/`%j`.
- `%n` is not counted and not a real token → expected-count is off by what you intended and the `%n` prints literally.

## Reuse first

`grep` `package.nls.json` for an existing equivalent before adding — reuse, don't duplicate.

## Per-surface style

From `salesforcedx-vscode-org` (`src/messages/i18n.ts`, `package.nls.json`):

| Surface | Example | Cap | Punctuation |
|---|---|---|---|
| Command title (`package.nls.json` `*_text`) | `SFDX: Create a Default Scratch Org...` | Title Case, `SFDX:` prefix | none (`...` ok for further input) |
| QuickPick/InputBox placeholder | `i18n.ts:67` `Select scratch orgs and sandboxes to delete` | sentence | none |
| Confirm prompt | `i18n.ts:68` `Permanently delete %d org(s)? This cannot be undone.` | sentence | terminal `.`/`?` |
| Notification (`show*Message`) | `i18n.ts:92` `... orgs expire in the next %d days. ...` | sentence | terminal `.` |
| Button/action label | `i18n.ts:69,70` `Delete`, `Logout` | Title Case | none |
| Tree/status-bar label+tooltip | `i18n.ts:96` `Open Default Org in Browser` | Title Case | none |
| Log/channel line | — | sentence | `.` |
| Validation error | — | sentence | terminal `.` |

## No severity prefix

Don't prefix new strings with `Error:`/`Warning:` — the `show*Message` API supplies severity. `i18n.ts:92` (`Warning: One or more...`) is a legacy anti-pattern; don't copy it, and don't edit it (out of scope). See `vscode-window-messages`.

## Cross-ref

`vscode-window-messages` covers the notification/button API (which `show*Message`, return values, Effect, modal). This skill is the message *text*. Don't duplicate API guidance here.

`package.json` / `package.nls.json` command titles, menus, and notification slot keys: [command-ui](../command-ui/SKILL.md).

