@better-i18n/cli
v0.1.7
Published
CLI tool for detecting hardcoded strings in React/Next.js apps
Downloads
812
Maintainers
Readme
@better-i18n/cli
Detect hardcoded strings in your React/Next.js apps before they become i18n debt.
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/cliQuick Start
# Scan current directory
better-i18n scan
# That's it! The CLI auto-detects your i18n.config.tsFeatures
- ✅ 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.07sCmd+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 outputbetter-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 jsonTranslation 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 completeDetection 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:
",&,' - 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:i18n2. Pre-commit Hook
Install Husky:
npx husky init
echo "npx @better-i18n/cli scan --staged --ci" > .husky/pre-commitOr 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 json4. 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+P → Tasks: Run Task → i18n: 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-appEach 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:i18nTroubleshooting
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 GitHubThe 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
