intl-pipeline
v0.0.1
Published
Automated i18n workflow: detect hardcoded strings, generate translations, deduplicate, and cleanup unused keys
Maintainers
Readme
intl-pipeline
Automated i18n workflow: Detect hardcoded strings, generate scope-aware translation keys, auto-translate with LLM, deduplicate, and cleanup unused keys.
⚠️ Important Notice
AI-Powered Translation Disclaimer: This tool uses AI/LLM for string detection and translation. While it significantly speeds up the i18n process, AI-generated results can be unpredictable and may not always be accurate.
Always review the following:
- Verify that detected strings are actually user-facing text
- Check that generated translation keys are appropriate
- Review all AI-translated content for accuracy and cultural appropriateness
- Test your application thoroughly after running automated transformations
Critical systems: For production applications, especially those with legal, medical, or safety-critical content, human review and validation of all translations is essential.
Features
- 🎨 Multi-Framework Support - Works with React, Vue, Angular, Solid.js, HTML and more
- 🔍 Smart Detection - Automatically finds user-facing hardcoded strings in your code
- 🌍 Auto-Translation - Translate using AI (Ollama, OpenAI, Anthropic, Google Gemini)
- ♻️ Deduplication - Consolidate duplicate translations into shared keys
- 🧹 Cleanup - Remove unused translation keys automatically
- 📊 Coverage Analysis - Track translation completeness across locales
- 🎯 Scope-Aware Keys - Generate organized keys based on file structure
- 📁 Flexible Formats - Support for TypeScript and JSON locale files
- 🔤 Customizable - Configure key naming, sorting, structure, and more
Installation
npm install intl-pipeline
# or
pnpm add intl-pipeline
# or
yarn add intl-pipelinePrerequisites
For auto-translation, choose one of the supported LLM providers:
Option 1: Ollama (Local, Free)
curl https://ollama.ai/install.sh | sh
ollama pull gemma3:4b
ollama serveOption 2: Cloud Providers
Create a .env.i18n file in your project root with your API key:
# Google Gemini (recommended for speed and cost)
GOOGLE_API_KEY=your_api_key_here
# OpenAI
OPENAI_API_KEY=your_api_key_here
# Anthropic Claude
ANTHROPIC_API_KEY=your_api_key_hereThen configure your provider in intl-pipeline.config.js:
export default {
// ... other config
llmProvider: 'google', // or 'openai', 'anthropic', 'ollama'
llmModel: 'gemini-2.5-flash', // or 'gpt-4o-mini', 'claude-3-haiku-20240307'
};Getting API Keys:
- Google Gemini: Get API key (Free tier: 15 requests/min)
- OpenAI: Get API key (Pay-as-you-go)
- Anthropic: Get API key (Pay-as-you-go)
Quick Start
CLI Usage
# Full sync (detect → replace → translate → deduplicate → cleanup)
npx intl-pipeline sync
# Individual commands
npx intl-pipeline detect # Find hardcoded strings
npx intl-pipeline replace # Replace with t() calls
npx intl-pipeline sync-keys # Add translation keys from code to locale files
npx intl-pipeline translate # Translate missing keys
npx intl-pipeline deduplicate # Consolidate duplicates
npx intl-pipeline cleanup # Remove unused keys
# Process specific files
npx intl-pipeline replace --files src/App.tsx src/pages/Home.tsx
# Custom configuration
npx intl-pipeline sync --src ./app --locales ./locales --model llama2Programmatic API
import {
detect,
replace,
translate,
deduplicate,
cleanup,
} from 'intl-pipeline';
const config = {
srcDir: './src',
localesDir: './src/locales',
sourceLocale: 'en',
targetLocales: ['en', 'es', 'ru', 'de', 'fr'],
};
// Detect hardcoded strings
const scanResult = await detect(config);
// Replace with t() calls
const replaceResult = await replace(
config,
scanResult.strings,
scanResult.byFile
);
// Translate missing keys
const translationResult = await translate(config);
// Deduplicate values
const dedupResult = await deduplicate(config);
// Cleanup unused keys
const cleanupResult = await cleanup(config);Complete Workflow Example
Here's a real example showing the entire workflow from detection to translation:
Step 1: Detect hardcoded strings
$ intl-pipeline detect -f tests/fixtures/frameworks/React.tsx
🔍 Detecting hardcoded strings
✔ ✓ Found 2 hardcoded strings in 1 files
💾 Results cached for fast re-use
📝 Found 2 hardcoded strings:
tests/fixtures/frameworks/React.tsx:
Line 5: "React Component"
Line 6: "Click Me"
💡 Next: Review your locales and run the replace command!Step 2: Replace with t() calls
$ intl-pipeline replace
🔧 Replacing hardcoded strings
✔ Loaded 2 strings from cache (skipped LLM detection!)
✔ Replaced strings in 1 files, added 2 translations
✓ Modified 1 files
✓ Generated 2 new translation keys
📝 Sample translations:
frameworksReactComponent: "React Component"
frameworksClickMe: "Click Me"The source file is automatically transformed:
// Before
export function ReactComponent() {
return (
<div>
<h1>React Component</h1>
<button>Click Me</button>
</div>
);
}
// After
export function ReactComponent() {
return (
<div>
<h1>{t('frameworksReactComponent')}</h1>
<button>{t('frameworksClickMe')}</button>
</div>
);
}Step 3: Translate to target locales
$ intl-pipeline translate
🤖 Translating missing keys
✔ google available (model: gemini-2.5-flash)
📊 Missing translations:
ES: 2 keys (85% complete)
FR: 13 keys (0% complete)
DE: 13 keys (0% complete)
✔ ES: 2 keys translated
✔ FR: 13 keys translated
✔ DE: 13 keys translated
📊 Token Usage Summary:
TOTAL:
LLM calls: 10
Input: 637 tokens
Output: 396 tokens
Total: 1,033 tokens
Execution time: 17.45sThat's it! Your app is now internationalized with translations in multiple languages.
Bonus: Syncing manually added keys
If you've manually added translation keys in your code but haven't defined them in locale files:
$ intl-pipeline sync-keys
🔑 Syncing translation keys from source code
⚠ Found 2 missing translation keys
⚠ 2 keys need manual translation:
- key [params: count, email, variables]
- oldKey
✔ Added 2 missing keys to all locales
📝 Next steps:
1. Update en locale: Replace 2 NEEDS_TRANSLATION with actual text
2. Run translate command to translate to other locales
3. Review and commit changesThis command scans your codebase for t() calls and automatically adds any missing keys to your locale files with placeholders, saving you from manual synchronization.
Checking translation status
View your translation coverage across all locales:
$ intl-pipeline status
📊 Translation Status Report
Source Locale:
EN: 13 keys
Translation Coverage:
ES: [████████████████████] 100% (13/13)
FR: [███░░░░░░░░░░░░░░░░░] 15% (2/13)
DE: [███░░░░░░░░░░░░░░░░░] 15% (2/13)
Translation Metadata:
ES: 🤖 Auto-translated: 2
FR: 🤖 Auto-translated: 13
DE: 🤖 Auto-translated: 12, 🔒 Locked: 1
💡 Recommendations:
• Run translate to translate missing keysDeduplicating translations
Consolidate duplicate translation values into the common.* namespace:
$ intl-pipeline deduplicate
🔧 Deduplicating translations
Found duplicates:
"Save" appears 3 times:
• buttons.save
• actions.saveButton
• forms.submitButton
"Cancel" appears 2 times:
• modal.cancelButton
• forms.cancelButton
✓ Consolidated 5 keys → 2 common keys
✓ Updated 3 source files
Your code is automatically updated:
t('buttons.save') → t('common.save')
t('actions.saveButton') → t('common.save')
t('modal.cancelButton') → t('common.cancel')Cleaning up unused keys
Remove translation keys that aren't used anywhere in your code:
$ intl-pipeline cleanup
🧹 Cleaning up unused keys
Scanning source files for t() usage...
Found unused keys:
• oldFeature (not referenced in code)
• deprecatedButton (not referenced in code)
• legacyText (not referenced in code)
✓ Removed 3 unused keys from 4 localesConfiguration
Create intl-pipeline.config.js in your project root:
export default {
// Source directory to scan for hardcoded strings
srcDir: './src',
// Locales directory
localesDir: './src/locales',
// Source locale (base translations)
sourceLocale: 'en',
// Target locales for translation
targetLocales: ['en', 'es', 'ru', 'de', 'fr', 'pt', 'ja', 'zh'],
// File patterns to scan
filePatterns: ['.tsx', '.ts', '.jsx', '.js'],
// ====================
// File Format & Structure
// ====================
// Locale file format: 'ts' (default), 'json', or 'js'
localeFileFormat: 'ts',
// Locale structure:
// - 'nested': { common: { save: "Save" } }
// - 'flat': { "common.save": "Save" }
localeStructure: 'nested',
// Translation function names to recognize in source code
// Default: ['t']
// Examples: ['t', 'i18n', '$t']
translationFunctions: ['t'],
// Sorting configuration for locale keys
sort: {
order: 'asc', // 'asc' or 'desc'
case: 'insensitive', // 'sensitive' or 'insensitive'
},
// Key naming format for auto-generated keys
// Options: 'camelCase', 'snake_case', 'kebab-case', 'dot.case'
keyFormat: 'snake_case', // default
// ====================
// LLM Provider Configuration
// ====================
// Provider: 'ollama' | 'openai' | 'anthropic' | 'google'
llmProvider: 'ollama',
// Model name (varies by provider)
llmModel: 'gemma3:4b',
// Base URL (optional, uses provider defaults)
// llmBaseUrl: 'http://127.0.0.1:11434',
// ====================
// i18n Hook Configuration
// ====================
// i18n hook configuration
i18nHook: 'useI18n',
i18nImportPath: '../i18n',
};How It Works
intl-pipeline automates your i18n workflow in six steps:
1. Smart String Detection
Scans your code to find user-facing text while filtering out technical strings like CSS classes, file paths, and variable names.
// Detects user-facing text:
<button className="btn-primary" title="Save changes">
Save
</button>
// ✓ "Save changes" → user-facing
// ✓ "Save" → user-facing
// ✗ "btn-primary" → technical (skipped)2. Safe Code Transformation
Replaces hardcoded strings with translation function calls and automatically adds necessary imports:
// Before
export function SaveButton() {
return <button>Save Changes</button>;
}
// After
import { useI18n } from '../i18n';
export function SaveButton() {
const { t } = useI18n();
return <button>{t('saveButton.save_changes')}</button>;
}3. Scope-Aware Keys
Generates translation keys based on your file structure for better organization:
src/features/schema/SchemaEditor.tsx → "schemaEditor.save_changes"
src/components/modals/DeleteModal.tsx → "deleteModal.confirm_deletion"4. Deduplication
Finds duplicate translations and consolidates them into the common.* namespace:
// Consolidates:
deleteModal.cancel → common.cancel
exportModal.cancel → common.cancel
editModal.cancel → common.cancel
// Your code is automatically updated to use the common key5. Auto-Translation
Translates missing keys to your target languages using your chosen LLM provider:
// en.ts
{ common: { save: "Save", cancel: "Cancel" } }
// es.ts (auto-generated)
{ common: { save: "Guardar", cancel: "Cancelar" } }Features: Batch processing, template variable preservation, validation checks.
6. Cleanup
Removes unused translation keys from your locale files by scanning your codebase for actual usage.
Commands Reference
detect
Find hardcoded strings in source files.
npx intl-pipeline detect [options]Options:
-f, --files <paths...>- Scan specific files only
Example:
npx intl-pipeline detect --files src/App.tsx src/pages/Home.tsxreplace
Replace hardcoded strings with t() calls.
npx intl-pipeline replace [options]Options:
-f, --files <paths...>- Process specific files only
What it does:
- Detects hardcoded strings
- Transforms source code using AST
- Adds
useI18nimport and hook - Generates new translation keys
- Updates source locale file (e.g.,
en.ts)
sync-keys
Find translation keys used in source code and add missing ones to locale files.
npx intl-pipeline sync-keysWhat it does:
- Scans source code for all translation function calls (e.g.,
t('key'),i18n('key')) - Compares with keys defined in source locale
- Adds missing keys to source locale with
NEEDS_TRANSLATIONplaceholder - Syncs missing keys to target locales as
UNTRANSLATED - Skips dynamic keys with template interpolations (e.g.,
`key-${variable}`)
Use case: When you've manually added translation keys in your code but haven't defined them in locale files yet.
Example:
// You wrote this in your code:
<button>{i18n('common.save')}</button>
// But 'common.save' doesn't exist in en.json yet
// sync-keys will add it automatically!translate
Translate missing keys to target locales.
npx intl-pipeline translateWhat it does:
- Compares source locale with target locales
- Finds missing translations
- Translates using your configured LLM provider
- Validates template variables
- Updates all locale files
Requires: Configured LLM provider (Ollama, OpenAI, Anthropic, or Google Gemini)
deduplicate
Consolidate duplicate translation values.
npx intl-pipeline deduplicateWhat it does:
- Finds duplicate values across keys
- Promotes duplicates to
common.*namespace - Updates source code to use common keys
- Removes old duplicate keys from locales
cleanup
Remove unused translation keys.
npx intl-pipeline cleanupWhat it does:
- Scans source code for
t('key')usage - Compares with defined keys
- Removes unused keys from all locales
sync
Run full pipeline (detect → replace → translate → deduplicate → cleanup).
npx intl-pipeline sync [options]Options:
-f, --files <paths...>- Process specific files only
What it does:
- Creates backup of locale files
- Detects and replaces hardcoded strings
- Deduplicates translation values
- Translates missing keys
- Cleans up unused keys
Locale File Formats
intl-pipeline supports multiple locale file formats and structures.
TypeScript Format (default)
// src/locales/en.ts
export default {
common: {
save: 'Save',
cancel: 'Cancel',
delete: 'Delete',
},
navbar: {
home: 'Home',
settings: 'Settings',
profile: 'Profile',
},
schemaEditor: {
title: 'Edit Schema',
save_changes: 'Save Changes',
},
} as const;JSON Format
Set localeFileFormat: 'json' in config:
// src/locales/en.json
{
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete"
},
"navbar": {
"home": "Home",
"settings": "Settings",
"profile": "Profile"
}
}Flat Structure
Set localeStructure: 'flat' in config for dot-notation keys:
// src/locales/en.json (flat)
{
"common.save": "Save",
"common.cancel": "Cancel",
"common.delete": "Delete",
"navbar.home": "Home",
"navbar.settings": "Settings",
"navbar.profile": "Profile"
}Key Sorting
Keys are automatically sorted based on your config:
// Config
sort: {
order: 'asc', // or 'desc'
case: 'insensitive', // or 'sensitive'
}
// Result: keys sorted alphabetically (case-insensitive)
{
"common.cancel": "Cancel",
"common.delete": "Delete",
"common.save": "Save",
"navbar.home": "Home"
}Key Naming Formats
Auto-generated keys use the format specified in your config:
// For text "Model Name" with namespace "form"
// camelCase (combines namespace + key without dot)
keyFormat: 'camelCase';
// => "formModelName"
// snake_case (default - uses dot separator)
keyFormat: 'snake_case';
// => "form.model_name"
// kebab-case (uses dot separator)
keyFormat: 'kebab-case';
// => "form.model-name"
// dot.case (uses dot separator)
keyFormat: 'dot.case';
// => "form.model.name"Note: camelCase combines the namespace and key without a dot separator, making it ideal for flat structure JSON files. Other formats use a dot separator between namespace and key.
This ensures consistency across your translation keys and matches your project's naming conventions.
Integration Examples
React + TypeScript
// src/i18n.ts
import { useContext } from 'react';
import { I18nContext } from './I18nProvider';
export function useI18n() {
return useContext(I18nContext);
}
// Component usage (auto-generated by intl-pipeline)
import { useI18n } from '../i18n';
export function SaveButton() {
const { t } = useI18n();
return <button onClick={handleSave}>{t('saveButton.save_changes')}</button>;
}CI/CD Integration
# .github/workflows/i18n.yml
name: i18n Sync
on:
push:
branches: [main]
paths:
- 'src/**/*.{ts,tsx}'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npx intl-pipeline sync
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: 'chore: Update translations'
commit-message: 'chore: sync i18n translations'Git Hooks
# .husky/pre-commit
#!/bin/sh
npx intl-pipeline detect || exit 1Troubleshooting
LLM Provider Issues
Ollama not available:
ollama serve # Start Ollama
ollama pull gemma3:4b # Download modelCloud provider errors: Check your API key in .env.i18n and verify you have sufficient quota.
Translation Issues
Template variable mismatch: The tool preserves variables like {{count}} and ${name}. If validation fails, review and correct the translations manually.
Incorrect translations: AI translations may not always be contextually accurate. Always review translations before deploying to production.
Contributing
Contributions welcome! Please read CONTRIBUTING.md first.
License
MIT © Vasily Mitronov
Acknowledgments
Inspired by real-world i18n challenges and built to streamline the internationalization process for modern web applications.
