Quick Answer
The best practice for translation keys is hierarchical naming with feature-based namespaces: feature.component.element (e.g., checkout.cart.removeButton). Use snake_case or camelCase consistently, keep keys under 50 characters, add descriptions for context, and organize by feature rather than page. IntlPull enforces these patterns automatically through CLI validation and provides AI-powered suggestions when keys don't follow conventions.
Why Key Management Matters
Poor key management creates compound problems:
| Problem | Impact |
|---|---|
| Inconsistent naming | Translators lose context |
| Duplicate keys | Wasted translation effort |
| No namespaces | Impossible to find keys |
| Missing descriptions | Wrong translations |
| Orphan keys | Bloated translation files |
The cost is real: Teams with poor key hygiene spend 2-3x more time on translation management than teams with good conventions.
Naming Convention: The Foundation
Recommended Format
{namespace}.{component}.{element}.{modifier}
Examples:
| Key | Meaning |
|---|---|
auth.login.submitButton | Login page submit button |
checkout.cart.emptyState.title | Cart empty state title |
common.errors.networkError | Shared network error |
settings.profile.avatar.uploadHint | Avatar upload hint text |
Rules That Work
1. Use Hierarchical Structure
❌ loginSubmitButton
❌ login_submit_button
✅ auth.login.submitButton
2. Be Specific, Not Generic
❌ button.text
❌ title
✅ checkout.payment.submitButton
✅ dashboard.analytics.pageTitle
3. Avoid Positional Names
❌ sidebar.firstItem
❌ modal.button1
✅ sidebar.dashboard
✅ modal.confirmButton
4. Use Consistent Case
❌ auth.Login.Submit (mixed)
✅ auth.login.submit (camelCase throughout)
✅ auth.login.submit (or snake_case: auth.login.submit_button)
IntlPull Enforcement
IntlPull validates key naming in your CI/CD pipeline:
Terminal1npx @intlpullhq/cli lint --strict 2 3# Output: 4# ❌ loginBtn -> auth.login.submitButton (suggested) 5# ❌ error_msg -> common.errors.generic (suggested) 6# ✅ checkout.cart.itemCount (valid)
Namespace Architecture
Feature-Based Organization (Recommended)
Organize by feature, not by page or file type:
messages/
├── auth/
│ ├── en.json
│ └── es.json
├── checkout/
│ ├── en.json
│ └── es.json
├── dashboard/
│ ├── en.json
│ └── es.json
└── common/
├── en.json
└── es.json
Benefits:
- Easy to find keys related to a feature
- Can load namespaces on-demand (code splitting)
- Matches how developers think about the app
- Clear ownership for each namespace
Flat vs Nested Keys
Both approaches work; choose one and be consistent.
Flat (recommended for <1000 keys):
JSON1{ 2 "auth.login.title": "Sign In", 3 "auth.login.submitButton": "Continue", 4 "auth.login.forgotPassword": "Forgot password?" 5}
Nested (recommended for 1000+ keys):
JSON1{ 2 "auth": { 3 "login": { 4 "title": "Sign In", 5 "submitButton": "Continue", 6 "forgotPassword": "Forgot password?" 7 } 8 } 9}
IntlPull supports both formats and can convert between them automatically.
Key Descriptions: Context for Translators
Always add descriptions. Translators need context to translate accurately.
What to Include in Descriptions
| Include | Example |
|---|---|
| Where it appears | "Shown in the checkout page header" |
| Maximum length | "Max 20 characters for button width" |
| Variables explained | "{count} is the number of items" |
| Tone guidance | "Friendly, encouraging tone" |
| Screenshot link | Link to design or screenshot |
In IntlPull
JSON1{ 2 "checkout.cart.itemCount": { 3 "value": "{count, plural, one {# item} other {# items}}", 4 "description": "Shows item count in cart badge. Max 15 chars. {count} is number of items.", 5 "maxLength": 15, 6 "screenshot": "https://..." 7 } 8}
IntlPull displays descriptions directly in the translation interface, ensuring translators always have context.
Handling Variables and Plurals
Variable Naming
Use meaningful variable names, not positional:
❌ "Hello {0}, you have {1} messages"
✅ "Hello {name}, you have {count} messages"
Plural Handling (ICU Format)
JSON{ "cart.items": "{count, plural, =0 {No items} one {# item} other {# items}}" }
Gender and Selection
JSON{ "profile.greeting": "{gender, select, male {He} female {She} other {They}} updated their profile" }
IntlPull validates ICU syntax during upload and highlights errors before they reach production.
Avoiding Common Mistakes
1. String Concatenation
JavaScript1// ❌ Breaks in many languages (word order varies) 2t('cart.total') + ': ' + price 3 4// ✅ Use variables 5t('cart.totalWithPrice', { price }) 6// "Total: {price}" or "{price}: Total" depending on language
2. Hardcoded Punctuation
JavaScript1// ❌ Some languages use different punctuation 2t('question') + '?' 3 4// ✅ Include punctuation in the key 5t('question') // "Are you sure?"
3. Splitting Sentences
JSX1// ❌ Impossible to translate naturally 2<span>{t('click')}</span> <a>{t('here')}</a> <span>{t('toContinue')}</span> 3 4// ✅ Use rich text support 5<Trans i18nKey="clickToContinue"> 6 Click <a>here</a> to continue 7</Trans>
4. Assuming Text Length
CSS1/* ❌ German is ~30% longer than English */ 2.button { width: 100px; } 3 4/* ✅ Allow for expansion */ 5.button { min-width: 100px; padding: 0 1rem; }
Workflow Patterns
Developer Workflow
- Create key in code with placeholder text
- CLI extracts keys automatically (
npx @intlpullhq/cli extract) - Push to IntlPull (
npx @intlpullhq/cli upload) - AI translates or assign to translator
- Pull translations (
npx @intlpullhq/cli download)
Review Workflow
- Developer creates key with English value
- IntlPull AI generates translations
- Reviewer approves or edits in dashboard
- Approved translations sync to codebase
Cleanup Workflow (Quarterly)
Terminal1# Find unused keys 2npx @intlpullhq/cli unused 3 4# Remove orphaned keys 5npx @intlpullhq/cli prune --dry-run 6npx @intlpullhq/cli prune --confirm
IntlPull tracks key usage and flags keys that haven't been referenced in your codebase.
Scaling Patterns
For 100-1,000 Keys
- Single namespace is fine
- Flat key structure works
- Manual review is manageable
For 1,000-10,000 Keys
- Feature-based namespaces required
- Nested key structure helps
- Automated validation essential
- Weekly cleanup reviews
For 10,000+ Keys
- Strict namespace governance
- Code owners per namespace
- Automated orphan detection
- Monthly audit process
IntlPull scales to 100,000+ keys with namespace-level permissions, automated quality checks, and usage analytics.
Frequently Asked Questions
What is the best naming convention for translation keys?
Use hierarchical dot notation: feature.component.element (e.g., auth.login.submitButton). This provides clear context, easy searching, and natural grouping. Avoid generic names like button.text or positional names like modal.button1. Be consistent with case (camelCase or snake_case throughout).
How should I organize translation keys for a large app?
Organize by feature/domain using namespaces. Create separate files/namespaces for auth, checkout, dashboard, settings, etc., plus a common namespace for shared strings. This enables code splitting, clear ownership, and easier maintenance. IntlPull supports namespace-level permissions for large teams.
How do I handle translation key descriptions?
Add descriptions to every key that isn't self-explanatory. Include: where it appears (context), maximum character length, explanation of variables, tone guidance, and links to screenshots/designs. IntlPull displays descriptions in the translator interface and can flag keys missing descriptions.
How often should I clean up unused translation keys?
Run orphan detection monthly, full cleanup quarterly. Unused keys bloat bundles and confuse translators. IntlPull's npx @intlpullhq/cli unused command identifies keys not referenced in your codebase. Run prune with --dry-run first to preview deletions before confirming.
Should I use flat or nested translation key structures?
Use flat structure for <1,000 keys, nested for larger projects. Flat (auth.login.title: Sign In) is simpler and easier to search. Nested (auth: { login: { title: ... } }) scales better and matches code structure. IntlPull supports both and can convert between formats.
How do I prevent translation key naming inconsistencies?
Use automated validation in CI/CD. IntlPull's CLI includes a lint command that validates key naming against configurable rules. Run npx @intlpullhq/cli lint --strict in your pipeline to catch inconsistencies before they merge. The CLI also suggests corrections for malformed keys.
Summary
Key best practices:
- Use hierarchical naming:
feature.component.element - Organize by feature with namespaces
- Add descriptions for translator context
- Use ICU format for plurals and variables
- Validate automatically with CLI tools
- Clean up unused keys regularly
IntlPull enforces these patterns through CLI validation, automated suggestions, and usage tracking. Our AI even learns your project's conventions and suggests consistent key names as you work.
Ready for organized translations? Start free with IntlPull — AI-powered key suggestions included.
