Extracts hardcoded strings from code and converts them to translation keys for i18n...
Automatically finds hardcoded strings in code and converts them to translation keys for internationalization (i18n).
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:
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.
Create meaningful keys from strings:
Rules:
common.button.saveExamples:
"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"
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"
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")
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>"
}
}
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')
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"
}
}
}
Include:
Exclude:
Example filtering:
// Extract
"Save changes" β
"Welcome" β
"Error: Invalid input" β
// Don't extract
"userId" β
"/api/users" β
"btn-primary" β
console.log("Debug info") β
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:
Context:
{
"button": {
"save": "Save", // Generic
"save_changes": "Save changes", // Specific
"save_draft": "Save as draft" // Different context
}
}