npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

intl-pipeline

v0.0.1

Published

Automated i18n workflow: detect hardcoded strings, generate translations, deduplicate, and cleanup unused keys

Readme

intl-pipeline

Automated i18n workflow: Detect hardcoded strings, generate scope-aware translation keys, auto-translate with LLM, deduplicate, and cleanup unused keys.

License: MIT

⚠️ 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-pipeline

Prerequisites

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 serve

Option 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_here

Then 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:

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 llama2

Programmatic 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.45s

That'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 changes

This 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 keys

Deduplicating 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 locales

Configuration

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 key

5. 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.tsx

replace

Replace hardcoded strings with t() calls.

npx intl-pipeline replace [options]

Options:

  • -f, --files <paths...> - Process specific files only

What it does:

  1. Detects hardcoded strings
  2. Transforms source code using AST
  3. Adds useI18n import and hook
  4. Generates new translation keys
  5. 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-keys

What it does:

  1. Scans source code for all translation function calls (e.g., t('key'), i18n('key'))
  2. Compares with keys defined in source locale
  3. Adds missing keys to source locale with NEEDS_TRANSLATION placeholder
  4. Syncs missing keys to target locales as UNTRANSLATED
  5. 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 translate

What it does:

  1. Compares source locale with target locales
  2. Finds missing translations
  3. Translates using your configured LLM provider
  4. Validates template variables
  5. Updates all locale files

Requires: Configured LLM provider (Ollama, OpenAI, Anthropic, or Google Gemini)


deduplicate

Consolidate duplicate translation values.

npx intl-pipeline deduplicate

What it does:

  1. Finds duplicate values across keys
  2. Promotes duplicates to common.* namespace
  3. Updates source code to use common keys
  4. Removes old duplicate keys from locales

cleanup

Remove unused translation keys.

npx intl-pipeline cleanup

What it does:

  1. Scans source code for t('key') usage
  2. Compares with defined keys
  3. 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:

  1. Creates backup of locale files
  2. Detects and replaces hardcoded strings
  3. Deduplicates translation values
  4. Translates missing keys
  5. 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 1

Troubleshooting

LLM Provider Issues

Ollama not available:

ollama serve  # Start Ollama
ollama pull gemma3:4b  # Download model

Cloud 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.