# I18N Messaging

> Manage and maintain i18n messaging or copy for Daedalus multi-language support.

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

---


# i18n Messaging

**Manage and maintain i18n messaging for Daedalus multi-language support**

Handles message extraction, validation, localization workflows, and schema compliance for Daedalus's internationalization system. Supports English (en-US) and Japanese (ja-JP) locales using react-intl and Format.js.

---

## Message Schema

Messages in Daedalus follow a structured format using `defineMessages()` from react-intl:

```typescript
{
  messageKey: {
    id: "namespace.context.messageKey",          // Unique identifier with dot notation
    defaultMessage: "!!!Message text with placeholders",
    description: "Context/description for translators",
    values?: Record<string, any>                  // Runtime variable placeholders
  }
}
```

### ID Naming Convention

- **Format**: `namespace.context.messageKey` (dot-separated)
- **Prefix**: All messages start with `!!!` in defaultMessage
- **Examples**:
  - `global.errors.fieldIsRequired`
  - `api.errors.IncorrectPasswordError`
  - `global.ada.name`

### Variable Placeholders

Messages with dynamic content use curly braces:

```typescript
knownMnemonicWordCount: {
  id: 'global.info.knownMnemonicWordCount',
  defaultMessage: '!!!{actual} of {required} words entered',
  values: { actual: 5, required: 12 }
}
```

---

## Operations

### 1. Extract Messages

Extract all i18n messages from source code to generate message catalog.

```bash
yarn i18n:extract
```

**What it does**:
- Scans `source/**/*.{ts,tsx}` files
- Extracts `defineMessages()` and `<FormattedMessage />` calls
- Ignores `.d.ts` TypeScript declaration files
- Outputs to `translations/messages.json`
- Records source file locations

**When to use**:
- After adding new messages with `defineMessages()`
- Before running `i18n:check`
- As part of `i18n:manage` workflow

**Output format**:
```json
[
  {
    "path": "source/main/ipc/handlers.ts",
    "descriptors": [
      {
        "id": "global.errors.fieldIsRequired",
        "defaultMessage": "!!!This field is required.",
        "description": "Error message when required fields are left empty."
      }
    ]
  }
]
```

### 2. Check Translations

Validate translation files for consistency and completeness against extracted messages.

```bash
yarn i18n:check
```

**What it does**:
- Compares extracted messages with translation files
- Validates en-US and ja-JP locales
- Ensures all message IDs are present
- Detects missing or obsolete translations
- Uses `react-intl-translations-manager` to manage lifecycle

**When to use**:
- After extracting new messages
- Before committing translation changes
- To validate message format across locales

**Validates**:
- All en-US messages have corresponding ja-JP translations
- No orphaned message IDs
- Message structure consistency (id, defaultMessage, description)

### 3. Manage Translations

Combined operation: extract messages AND validate translations in one command.

```bash
yarn i18n:manage
```

**Equivalent to**: `yarn i18n:extract && yarn i18n:check`

**When to use**:
- Primary workflow for updating i18n content
- Part of `check:all` verification
- Before creating commits with messaging changes

---

## Supported Locales

| Locale  | Language | Directory                                  |
|---------|----------|-------------------------------------------|
| en-US   | English  | `source/renderer/app/i18n/locales/en-US.json`  |
| ja-JP   | Japanese | `source/renderer/app/i18n/locales/ja-JP.json`  |

### Locale Files Structure

```
source/renderer/app/i18n/locales/
├── defaultMessages.json          # All extracted messages (auto-generated)
├── en-US.json                    # English translations (English only)
├── ja-JP.json                    # Japanese translations
├── whitelist_en-US.json          # Approved en-US messages
├── whitelist_ja-JP.json          # Approved ja-JP messages
└── terms-of-use/                 # Locale-specific documents
```

---

## Best Practices

### Adding New Messages

1. Use `defineMessages()` or `<FormattedMessage />` in source code
2. Follow naming convention: `namespace.context.messageKey`
3. Include descriptive `description` for translators
4. Always prefix defaultMessage with `!!!`
5. Run `yarn i18n:extract` to register new messages

### Validating Message IDs

**Do**:
- Use hierarchical namespaces: `wallet.send.confirmButton`
- Keep IDs consistent across related messages
- Document placeholder variables in description
- Use lowercase with dots as separators

**Don't**:
- Use spaces or special characters in IDs
- Duplicate IDs across features
- Leave placeholders undocumented
- Change ID format mid-project

### Working with Placeholders

Messages with runtime values use curly braces:

```typescript
// Define message with placeholder
notEnoughFunds: {
  id: 'wallet.errors.notEnoughFunds',
  defaultMessage: '!!!Remaining balance: {balance} ADA',
  description: 'Error when insufficient funds'
}

// Use at runtime
<FormattedMessage
  id="wallet.errors.notEnoughFunds"
  defaultMessage="Remaining balance: {balance} ADA"
  values={{ balance: walletBalance }}
/>
```

### Translation Flow

1. **Extract**: Run `yarn i18n:extract` after code changes
2. **Check**: Run `yarn i18n:check` to validate
3. **Review**: Update localized JSON files as needed
4. **Verify**: Run `check:all` before committing
5. **Commit**: Include updated `translations/messages.json` and locale files

### Generated `!!!` Placeholders

- `yarn i18n:manage` seeds missing locale entries from `defaultMessage`, so new keys in `en-US.json` and `ja-JP.json` can be written with the `!!!` prefix.
- Treat `!!!` in locale files as a new or untranslated message marker, not polished release copy.
- This workflow does not remove the prefix automatically. If locale polish is in scope, manually replace new `en-US.json` entries with approved English copy and add real Japanese translations in `ja-JP.json` before commit.
- If the task only covers extraction or catalog sync, keeping generated `!!!` placeholders is acceptable, but document the follow-up translation work explicitly.

---

## Related Files

| File | Purpose |
|------|---------|
| `translations/translation-runner.ts` | Validates translations (manages mapping) |
| `translations/formatter.js` | Custom extraction format for Format.js |
| `translations/messages.json` | Extracted message catalog |
| `source/renderer/app/i18n/global-messages.ts` | Global message definitions |
| `source/renderer/app/i18n/errors.ts` | Error message definitions |
| `source/renderer/app/i18n/types.ts` | TypeScript types for messages |

---

## Troubleshooting

### Messages not extracted

- Verify `defineMessages()` is imported from `react-intl`
- Check files are in `source/**/*.{ts,tsx}` (not .d.ts)
- Run `yarn clear:translations` then `yarn i18n:extract`

### Translation validation fails

- Ensure all en-US messages have ja-JP equivalents
- Check message IDs match exactly (case-sensitive)
- Verify JSON format is valid in locale files

### Missing or duplicate IDs

- Check for conflicting message keys across files
- Remove obsolete messages from translation files manually
- Re-run `yarn i18n:manage` to refresh state

---

## Template: Adding New Message

```typescript
import { defineMessages } from 'react-intl';

// In your component file or messages.ts
export const componentMessages = defineMessages({
  newMessageKey: {
    id: 'feature.component.newMessageKey',
    defaultMessage: '!!!Default English text',
    description: 'Context for translators explaining where/how this message is used',
  },
  messageWithPlaceholder: {
    id: 'feature.component.messageWithPlaceholder',
    defaultMessage: '!!!You have {count} items',
    description: 'Message showing item count with placeholder',
  },
});
```

Then run: `yarn i18n:manage`


