Translation Key Extractor
Automatically finds hardcoded strings in code and converts them to translation keys for internationalization (i18n).
When to Use
- "Extract hardcoded strings for translation"
- "Find all translatable text"
- "Convert strings to i18n keys"
- "Setup internationalization"
- "Create translation files from code"
- "Prepare code for multiple languages"
Instructions
1. Scan for Hardcoded Strings
Identify strings that need translation:
# Find string literals in JavaScript/TypeScript
grep -r "[\"\'\`][A-Z]" src/ --include="*.js" --include="*.jsx" --include="*.ts" --include="*.tsx"
# Find Python strings
grep -r "[\"'][A-Z]" . --include="*.py"
# Exclude imports, technical strings, CSS, etc.
Present findings with context:
- File and line number
- Surrounding code
- String content
- Suggested key name
2. Detect i18n Framework
Check what's already installed:
# React
grep -E "(react-i18next|react-intl|formatjs)" package.json
# Vue
grep -E "(vue-i18n)" package.json
# Python
grep -E "(gettext|Babel)" requirements.txt
# Ruby
grep -E "(i18n)" Gemfile
If none found, offer to install appropriate framework.
3. Generate Translation Keys
Create meaningful keys from strings:
Rules:
- Use dot notation:
common.button.save - Lowercase with underscores or camelCase
- Descriptive but concise
- Organized by scope (page, component, common)
Examples:
"Save changes" → "common.button.save"
"Welcome to our app" → "home.hero.welcome"
"Invalid email address" → "validation.email.invalid"
"Settings" → "navigation.settings"
"Are you sure?" → "common.confirm.message"
4. Create/Update Translation Files
Based on framework, create appropriate structure:
react-i18next (en.json):
{
"common": {
"button": {
"save": "Save changes",
"cancel": "Cancel",
"delete": "Delete"
},
"confirm": {
"message": "Are you sure?",
"yes": "Yes",
"no": "No"
}
},
"home": {
"hero": {
"welcome": "Welcome to our app",
"subtitle": "Build amazing things"
}
},
"validation": {
"email": {
"invalid": "Invalid email address",
"required": "Email is required"
}
}
}
Vue i18n (en.js):
export default {
common: {
button: {
save: 'Save changes',
cancel: 'Cancel'
}
},
home: {
hero: {
welcome: 'Welcome to our app'
}
}
}
gettext (.po file):
msgid "save"
msgstr "Save changes"
msgid "welcome"
msgstr "Welcome to our app"
5. Replace Strings in Code
Transform hardcoded strings to use i18n:
React (react-i18next):
// Before
<button>Save changes</button>
<h1>Welcome to our app</h1>
<p className="error">Invalid email address</p>
// After
import { useTranslation } from 'react-i18next'
function Component() {
const { t } = useTranslation()
return (
<>
<button>{t('common.button.save')}</button>
<h1>{t('home.hero.welcome')}</h1>
<p className="error">{t('validation.email.invalid')}</p>
</>
)
}
Vue (vue-i18n):
<!-- Before -->
<template>
<button>Save changes</button>
<h1>Welcome to our app</h1>
</template>
<!-- After -->
<template>
<button>{{ $t('common.button.save') }}</button>
<h1>{{ $t('home.hero.welcome') }}</h1>
</template>
Python (gettext):
# Before
print("Welcome to our app")
error_message = "Invalid email address"
# After
from gettext import gettext as _
print(_("Welcome to our app"))
error_message = _("Invalid email address")
6. Handle Special Cases
Dynamic content:
// Variables in strings
const message = `Welcome, ${name}!`
// Convert to
const message = t('welcome.greeting', { name })
// Translation file
{
"welcome": {
"greeting": "Welcome, {{name}}!"
}
}
Pluralization:
// Before
const message = count === 1 ? '1 item' : `${count} items`
// After
const message = t('items.count', { count })
// Translation file
{
"items": {
"count_one": "{{count}} item",
"count_other": "{{count}} items"
}
}
Rich text/HTML:
// Before
<p>Visit our <a href="/help">help center</a></p>
// After (react-i18next with Trans component)
<Trans i18nKey="help.message">
Visit our <a href="/help">help center</a>
</Trans>
// Translation file
{
"help": {
"message": "Visit our <1>help center</1>"
}
}
7. Setup i18n Framework (if needed)
React (react-i18next):
npm install react-i18next i18next
// i18n.js
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import en from './locales/en.json'
import es from './locales/es.json'
i18n
.use(initReactI18next)
.init({
resources: {
en: { translation: en },
es: { translation: es }
},
lng: 'en',
fallbackLng: 'en',
interpolation: {
escapeValue: false
}
})
export default i18n
// index.js
import './i18n'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
Vue (vue-i18n):
npm install vue-i18n
// i18n.js
import { createI18n } from 'vue-i18n'
import en from './locales/en'
import es from './locales/es'
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {
en,
es
}
})
export default i18n
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
createApp(App).use(i18n).mount('#app')
8. Create Translation Template
For other languages:
// locales/es.json (Spanish - empty template)
{
"common": {
"button": {
"save": "",
"cancel": "",
"delete": ""
}
}
}
Or provide English as placeholders for translators:
// locales/es.json
{
"common": {
"button": {
"save": "[ES] Save changes", // Translator replaces this
"cancel": "[ES] Cancel",
"delete": "[ES] Delete"
}
}
}
9. String Detection Rules
Include:
- User-visible text
- Error messages
- Labels and placeholders
- Navigation items
- Notifications/alerts
- Help text and tooltips
Exclude:
- Technical identifiers (IDs, keys)
- API endpoints
- Class names, CSS
- Console logs (unless user-facing)
- Test data
- Environment variables
- Regular expressions
Example filtering:
// Extract
"Save changes" ✅
"Welcome" ✅
"Error: Invalid input" ✅
// Don't extract
"userId" ❌
"/api/users" ❌
"btn-primary" ❌
console.log("Debug info") ❌
10. Provide Migration Plan
- Setup i18n framework
- Create translation files
- Replace strings file by file
- Test each component
- Create template for other languages
- Send to translators
- Add language switcher UI
- Test with all locales
Best Practices
Key Naming:
✅ Good:
- common.button.save
- home.hero.title
- errors.validation.email
❌ Bad:
- string1
- text_for_button
- thisIsAReallyLongKeyNameThatSaysExactlyWhatTheStringIs
Organization:
locales/
en/
common.json # Shared across app
home.json # Home page specific
auth.json # Authentication
validation.json # Error messages
es/
common.json
home.json
...
Performance:
- Lazy load translations per page/feature
- Use namespaces to avoid loading all translations
- Cache translations in production
Context:
{
"button": {
"save": "Save", // Generic
"save_changes": "Save changes", // Specific
"save_draft": "Save as draft" // Different context
}
}