---
name: vtex-io-messages-and-i18n
description: "Apply when implementing localization and translated copy in VTEX IO apps. Covers the `messages` builder, `/messages/*.json`, `context.json`, frontend message usage, and how VTEX apps integrate with `vtex.messages` for runtime translation. Use for storefront, admin, or backend flows that should use VTEX IO message infrastructure instead of hardcoded strings."
---
# Messages & Internationalization
## When this skill applies
Use this skill when a VTEX IO app needs translated copy instead of hardcoded strings.
- Adding localized UI text to storefront or Admin apps
- Creating or updating `/messages/*.json` translation files
- Defining message keys in `context.json`
- Reviewing React, Admin, or backend code that currently hardcodes user-facing copy
- Integrating app-specific translations with `vtex.messages`
Do not use this skill for:
- general UI layout or component composition
- authorization, policies, or auth tokens
- service runtime sizing
- choosing between HTTP, GraphQL, and event-driven APIs
## Decision rules
- Use the `messages` builder and translation files for user-facing copy instead of hardcoding labels, button text, or UI messages in source code.
- Keep translation keys stable, explicit, and scoped to the app domain instead of generic keys such as `title` or `button`. The exact format may vary, but keys should remain specific, descriptive, and clearly owned by the app.
- Prefix message IDs according to their UI surface or domain, for example `store/...` for storefront messages and `admin/...` for Admin or Site Editor messages, so keys stay organized and do not collide across contexts.
- Define message keys in `/messages/context.json` so VTEX IO can discover and manage the app’s translation surface. Keep it as a flat map of `messageId -> description` and include the keys the app actually uses.
- Keep translated message payloads small and app-focused. Do not turn the messages system into a general content store.
- In React or Admin UIs, prefer message IDs and localization helpers over literal copy in JSX.
- In backend or GraphQL flows, translate only when the app boundary truly needs localized text; otherwise return stable machine-oriented data and let the caller localize the presentation.
- Use app-level overrides of `vtex.messages` only when the app truly needs to customize translation behavior or message resolution beyond normal app-local message files.
## Hard constraints
### Constraint: User-facing strings must come from the messages infrastructure
User-facing strings MUST come from the messages infrastructure instead of being hardcoded in components, handlers, or resolvers.
**Why this matters**
Hardcoded copy breaks localization, makes message review harder, and creates inconsistent behavior across storefront, Admin, and backend flows.
**Detection**
If you see labels, buttons, headings, alerts, or other user-facing text embedded directly in JSX or backend response formatting for a localized app, STOP and move that copy to message files.
**Correct**
```tsx
```
**Wrong**
```tsx
```
### Constraint: Message keys must be declared and organized explicitly
Message keys MUST be app-scoped and represented in the app’s message configuration instead of being invented ad hoc in code.
**Why this matters**
Unstructured keys become hard to maintain, collide across app areas, and make message ownership unclear.
**Detection**
If code introduces new message IDs with no corresponding translation files or `context.json` entry, STOP and add the message contract explicitly.
**Correct**
```json
{
"admin/my-app.save": "Save"
}
```
**Wrong**
```json
{
"save": "Save"
}
```
### Constraint: The messages system must not be used as a general content or configuration store
Translation files MUST contain localized copy, not operational configuration, secrets, or large content payloads.
**Why this matters**
The messages infrastructure is designed for translated strings. Using it for other data creates maintenance confusion and mixes localization concerns with configuration or content storage.
**Detection**
If message files contain API URLs, credentials, business rules, or long structured content blobs, STOP and move that data to app settings, configuration apps, or a content-specific mechanism.
**Correct**
```json
{
"store/my-app.emptyState.title": "No records found"
}
```
**Wrong**
```json
{
"apiBaseUrl": "https://partner.example.com",
"featureFlags": {
"betaMode": true
}
}
```
## Preferred pattern
Recommended file layout:
```text
.
├── messages/
│ ├── context.json
│ ├── en.json
│ └── pt.json
└── react/
└── components/
└── SaveButton.tsx
```
Minimal messages setup:
```json
// messages/context.json
{
"admin/my-app.save": "Label for the save action in the admin settings page"
}
```
```json
// messages/en.json
{
"admin/my-app.save": "Save",
"store/my-app.emptyState.title": "No records found"
}
```
```json
// messages/pt.json
{
"admin/my-app.save": "Salvar"
}
```
```tsx
import { FormattedMessage } from 'react-intl'
export function SaveButton() {
return
}
```
Backend or GraphQL translation pattern:
```graphql
scalar IOMessage
type ProductLabel {
id: ID
label: IOMessage
}
type Query {
productLabel(id: ID!): ProductLabel
}
```
```typescript
export const resolvers = {
Query: {
productLabel: async (_: unknown, { id }: { id: string }) => {
return {
id,
label: {
content: 'store/my-app.product-label',
description: 'Label for product badge',
from: 'en-US',
},
}
},
},
}
```
Keep a complete `en.json` as the default fallback, even when the app’s main audience uses another locale, so the messages system has a stable base for resolution and auto-translation behavior.
Use translated IDs in code, keep translation files explicit, and centralize user-facing copy in the messages system instead of scattering literals through the app.
## Common failure modes
- Hardcoding user-facing strings in JSX, resolvers, or handler responses.
- Adding new message IDs in code without updating `context.json` or the message files.
- Using generic or collision-prone keys such as `title`, `save`, or `button`.
- Storing configuration values or non-localized business payloads in message files.
- Treating `vtex.messages` overrides as the default path instead of app-local message management.
## Review checklist
- [ ] Are user-facing strings sourced from the messages infrastructure instead of hardcoded in code?
- [ ] Are message keys explicit, app-scoped, and declared consistently?
- [ ] Does `context.json` reflect the translation surface used by the app?
- [ ] Are message files limited to localized copy rather than configuration or operational data?
- [ ] Is any customization of `vtex.messages` truly necessary for this app?
## Related skills
- [`vtex-io-storefront-react`](../vtex-io-storefront-react/SKILL.md) - Use when the main question is storefront component structure and shopper-facing UI behavior
- [`vtex-io-admin-react`](../vtex-io-admin-react/SKILL.md) - Use when the main question is Admin UI structure and operational interaction patterns
- [`vtex-io-graphql-api`](../vtex-io-graphql-api/SKILL.md) - Use when the main question is GraphQL schema and resolver design rather than translation infrastructure
## Reference
- [Messages](https://developers.vtex.com/docs/apps/vtex.messages) - VTEX messages app and runtime translation behavior
- [Overwriting the Messages app](https://developers.vtex.com/docs/guides/vtex-io-documentation-overwriting-the-messages-app) - How app-specific overrides of `vtex.messages` work