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

@better-i18n/cli

v0.1.7

Published

CLI tool for detecting hardcoded strings in React/Next.js apps

Downloads

812

Readme

@better-i18n/cli

Detect hardcoded strings in your React/Next.js apps before they become i18n debt.

npm version License: MIT

Why?

Hardcoded strings slip into codebases easily. Finding them manually is tedious. This CLI automatically scans your React/Next.js code and reports untranslated text—before it ships to production.

// ❌ These get flagged
<h1>Welcome to our app</h1>
<button>Click me</button>
<input placeholder="Enter your name" />

// ✅ These are fine
<h1>{t('welcome')}</h1>
<button>{t('actions.click')}</button>
<input placeholder={t('form.namePlaceholder')} />

Installation

# Global install
npm install -g @better-i18n/cli

# Or use with npx (no install needed)
npx @better-i18n/cli scan

# Or add to your project
npm install -D @better-i18n/cli

Quick Start

# Scan current directory
better-i18n scan

# That's it! The CLI auto-detects your i18n.config.ts

Features

  • Auto-config detection - Reads your existing i18n.config.ts
  • Server component support - Detects getTranslations() in Next.js App Router
  • Smart filtering - Ignores CSS classes, URLs, constants, HTML entities
  • Glob patterns - Exclude test files, stories, UI components
  • Clickable output - File paths are Cmd+clickable in VS Code terminal
  • CI/CD ready - JSON output, exit codes, staged files support
  • Fast - Scans 100+ files in <100ms

Example Output

$ better-i18n scan

✓ Project: better-i18n/landing
✓ Found 57 files

components/sign-up.tsx (11)
  24:13  missing  "Create an account"  i18n/jsx-text
  32:22  missing  "Name"  i18n/jsx-text
  40:22  missing  "Email"  i18n/jsx-text

components/contact.tsx (9)
  24:59  missing  "Contact us"  i18n/jsx-text
  31:22  missing  "Message"  i18n/jsx-text

✖ 87 problems (87 missing translations)

Scanned 57 files in 0.07s

Cmd+Click on any file path to jump directly to the issue in VS Code!

Commands & Options

better-i18n scan

Scan your codebase for hardcoded strings.

# Basic usage
better-i18n scan

# Scan specific directory
better-i18n scan --dir ./src

# Output formats
better-i18n scan --format json    # JSON output for CI/tooling
better-i18n scan --format eslint  # Human-readable (default)

# CI/CD integration
better-i18n scan --ci             # Exit with code 1 if issues found
better-i18n scan --staged         # Only scan git staged files

# Debug
better-i18n scan --verbose        # Show detailed output

better-i18n sync

Compare local translation keys (t() calls) with your Better i18n cloud project.

# Basic usage (grouped tree output)
better-i18n sync

# Minimal metrics only
better-i18n sync --summary

# Deep audit log & scope trace
better-i18n sync --verbose

# JSON output for CI automation
better-i18n sync --format json

Translation Hook Detection

The CLI automatically detects namespaces from both client and server translation hooks.

Client Components (React Hooks):

// Detected as 'auth.login' and 'auth.register'
const { t } = useTranslations('auth');

t('login');
t('register');

Server Components (Async Functions):

// Also detected as 'welcome.title' and 'welcome.subtitle'
const t = await getTranslations('welcome');

return (
  <div>
    <h1>{t('title')}</h1>
    <p>{t('subtitle')}</p>
  </div>
);

Advanced Pattern (Object with locale):

const t = await getTranslations({
  locale: params.locale,
  namespace: 'maintenance'
});

Supported Patterns:

  • useTranslations('namespace') - Client components
  • getTranslations('namespace') - Server components
  • getTranslations({ locale, namespace: 'namespace' }) - Server with locale
  • useTranslations() / getTranslations() - Root scoped (no namespace)

Output format (JSON):

{
  "localKeys": {
    "project": "better-i18n/landing",
    "namespaces": {
      "auth": ["auth.login", "auth.register", "auth.forgot"],
      "nav": ["nav.home", "nav.about"],
      "hero": ["hero.title", "hero.description"]
    },
    "totalCount": 6,
    "filesScanned": 42
  }
}

With sync output (default):

📊 Translation Keys Comparison
Source locale: en

Coverage:
  Local → Remote: 59%
  Remote Used: 63%

⊕ Missing in Remote (473 keys)
pages (300)
  affordableEnglishLearning (meta.title, meta.description, ...+12)
  bestApps (hero.badge, title_prefix, title_accent)

hero (5)
  hero (ariaLabel, imageAlt, ...)

⊖ Unused in Code (386 keys)
features (25)
  practiceSpeaking (title, subtitle, icon)

Scanned 246 files in 0.85s
✓ Comparison complete

Detection Rules

| Rule | Severity | What it catches | Example | | ---------------- | -------- | ---------------------- | --------------------------------- | | jsx-text | missing | Hardcoded text in JSX | <h1>Hello</h1> | | jsx-attribute | missing | Hardcoded attributes | <img alt="Logo" /> | | ternary-locale | error | Locale-based ternaries | locale === 'en' ? 'Hi' : 'Hola' |

Framework Support

| Framework | Client Hooks | Server Functions | Status | |-----------|-------------|------------------|--------| | Next.js (Pages Router) | useTranslations() | N/A | ✅ Full support | | Next.js (App Router) | useTranslations() | getTranslations() | ✅ Full support | | React (SPA) | useTranslations() | N/A | ✅ Full support |

Automatically Ignored

  • HTML entities: &quot;, &amp;, &#39;
  • CSS classes: className="flex items-center"
  • URLs: href="https://example.com"
  • Paths: /api/users
  • Numbers: 42, 3.14, 100%
  • Constants: SCREAMING_CASE
  • Symbols: , , ...

Configuration

Create or update your i18n.config.ts:

export const project = "your-org/your-project";
export const defaultLocale = "en";

export const i18nWorkspaceConfig = {
  project,
  defaultLocale,
  lint: {
    // Files to scan (defaults: ["src", "app", "components", "pages"])
    include: ["src/**/*.tsx", "app/**/*.tsx"],

    // Files to ignore (automatically merges with defaults)
    exclude: [
      "**/skeletons.tsx", // Mock/demo components
      "**/*.stories.tsx", // Storybook files
      "**/*.test.tsx", // Test files
      "**/components/ui/**", // UI library components
    ],

    // Rule configuration (optional)
    rules: {
      "jsx-text": "warning",
      "jsx-attribute": "warning",
      "ternary-locale": "error",
    },
  },
};

Config Options

| Option | Type | Description | | --------- | ---------- | ---------------------------------------------------------------------------------- | | include | string[] | Glob patterns for files to scan (default: ["src", "app", "components", "pages"]) | | exclude | string[] | Glob patterns to ignore (merges with defaults: node_modules, .next, etc.) | | rules | object | Set severity: "error" | "warning" | "off" |

Usage Scenarios

1. Local Development

Add to your package.json:

{
  "scripts": {
    "lint": "next lint && better-i18n scan --ci",
    "lint:i18n": "better-i18n scan"
  }
}

Run before commits:

npm run lint:i18n

2. Pre-commit Hook

Install Husky:

npx husky init
echo "npx @better-i18n/cli scan --staged --ci" > .husky/pre-commit

Or with lint-staged:

{
  "lint-staged": {
    "*.{tsx,jsx}": ["better-i18n scan --ci"]
  }
}

3. GitHub Actions CI

# .github/workflows/i18n-check.yml
name: i18n Check

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npx @better-i18n/cli scan --ci --format json

4. VS Code Integration

Add to .vscode/tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "i18n: Check translations",
      "type": "shell",
      "command": "npx @better-i18n/cli scan",
      "problemMatcher": [],
      "presentation": {
        "reveal": "always",
        "panel": "new"
      }
    }
  ]
}

Run with: Cmd+Shift+PTasks: Run Taski18n: Check translations

5. Monorepo Usage

# Scan specific package
cd packages/web-app
better-i18n scan

# Or from root with --dir
better-i18n scan --dir packages/web-app

Each package can have its own i18n.config.ts.

JSON Output

Use --format json for programmatic integration:

better-i18n scan --format json > i18n-report.json
{
  "project": {
    "workspaceId": "better-i18n",
    "projectSlug": "landing",
    "defaultLocale": "en"
  },
  "files": 57,
  "issues": [
    {
      "file": "components/sign-up.tsx",
      "line": 24,
      "column": 13,
      "text": "Create an account",
      "type": "jsx-text",
      "severity": "warning",
      "message": "Hardcoded text: \"Create an account\"",
      "suggestedKey": "signUp.createAnAccount"
    }
  ],
  "duration": 67
}

JSON Schema

interface ScanResult {
  project?: {
    workspaceId: string;
    projectSlug: string;
    defaultLocale: string;
  };
  files: number;
  issues: Issue[];
  duration: number;
}

interface Issue {
  file: string; // Relative path
  line: number; // Line number
  column: number; // Column number
  text: string; // Hardcoded text
  type: "jsx-text" | "jsx-attribute" | "ternary-locale";
  severity: "error" | "warning";
  message: string; // Human-readable message
  suggestedKey?: string; // Auto-generated translation key
}

Advanced Usage

Custom Scripts

# Count missing translations
better-i18n scan --format json | jq '.issues | length'

# Get unique files with issues
better-i18n scan --format json | jq -r '.issues[].file' | sort -u

# Filter only errors
better-i18n scan --format json | jq '.issues[] | select(.severity == "error")'

Combine with Other Tools

# Run with TypeScript checks
tsc --noEmit && better-i18n scan --ci

# Run with ESLint
eslint . && better-i18n scan --ci

# Parallel execution
npm-run-all --parallel typecheck lint:eslint lint:i18n

Troubleshooting

Config not detected

Make sure your i18n.config.ts exports either:

  • export const project = "org/slug"
  • export const i18nWorkspaceConfig = { project: "org/slug" }

Too many false positives

Add exclusions to your config:

exclude: ["**/*.stories.tsx", "**/demo/**", "**/examples/**"];

Clickable links not working

Make sure you're using VS Code's integrated terminal. External terminals may not support clickable file paths.

Part of Better i18n Ecosystem

This CLI is one component of the Better i18n translation management platform:

Platform Components

  • @better-i18n/cli - This CLI tool (detect hardcoded strings, extract keys)
  • @better-i18n/next - Next.js SDK for runtime translation
  • @better-i18n/app - Web dashboard for translation management
  • @better-i18n/mcp: Model Context Protocol server for AI assistants.

Platform Features

  • GitHub Integration - Sync translations with your repositories
  • Real-time Collaboration - Team workflows on translations
  • CDN Delivery - Serve translations globally from edge locations
  • Multi-language Editor - Manage all languages in one interface
  • REST API - Programmatic access for CI/CD automation
  • AI Context Analysis - Automatically extract terminology from websites
  • Namespace Organization - Organize translations by feature/module

How This CLI Fits In

Developer Workflow:
├─ Write code with hardcoded strings
├─ Run: better-i18n scan → Detect hardcoded strings ⚠️
├─ Run: better-i18n sync → Compare local vs cloud keys
├─ Review in Better i18n Dashboard
├─ GitHub Hook: better-i18n scan --staged → Pre-commit check
├─ CI/CD: better-i18n sync --format json → Audit translations in pipeline
└─ Dashboard: Manage translations, sync with GitHub

The CLI works in your local development to catch issues before they ship, while the platform handles the translation management workflow.

Contributing

Found a bug or have a feature request? Open an issue.

License

MIT © Better i18n