Skip to content
better-i18n.com
本页内容

Compare your codebase's t() calls with translations stored in Better i18n (remote) and audit your project for consistency.

When to use sync #

The sync command is a powerful auditing tool. Common scenarios include:

  • Before opening a PR: Ensure all new translation keys are present in Better i18n.
  • After adding new pages: Automatically identify and log missing keys.
  • After refactors: Detect "dead" keys that are no longer referenced in code.
  • Before shipping a new locale: Verify that the source keys are stable.
  • Debugging: Use --verbose to trace why certain keys are not being picked up.
  • CI Gating: Use --format json to fail builds if missing keys are detected.

Workflow #

  1. Run sync: See the tree of missing or unused keys.
  2. Dashboard/AI: Add the missing keys via the Better i18n dashboard or AI suggestions.
  3. Verify: Run sync --summary to confirm missingCount is 0.
  4. Enforce: Integrate into CI to prevent missing keys from reaching production.

Usage #

Bash
better-i18n sync                # Grouped tree output (default)
better-i18n sync --summary      # High-level metrics only
better-i18n sync --verbose      # Deep audit & verification
better-i18n sync --format json  # JSON output for automation
better-i18n sync -d ./src       # Scan specific directory

Options #

OptionDescription
--format <type>eslint (default) for humans, json for machines.
--verboseDetailed audit log: invariant checks (PASS/FAIL), scoping summary, dynamic key skips, and specific key probes.
--summaryShow only top-level percentage coverage and counts without the detailed tree.
-d, --dir <path>Specify the directory to scan (default: current directory).

Output Example #

The default output provides a 2-level compact tree of mismatches.

Text
📊 Translation Keys Comparison
Source locale: en

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

⊕ Missing in Remote (473 keys)
  Keys used in code but not uploaded to Better i18n

pages (300)
  affordableEnglishLearning (meta.title, meta.description, meta.keywords, hero.badge, ...+12)
  bestApps (hero.badge, title_prefix, title_accent, subtitle)

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

⊖ Unused in Code (386 keys)
  Keys in Better i18n but not detected in code usage

features (25)
  practiceSpeaking (title, subtitle, icon)

Scanned 246 files in 0.85s
✓ Comparison complete

How namespace binding works #

The CLI uses Lexical Scope Tracking to resolve namespaces automatically for both client and server components.

Client Components (React Hooks) #

TSX
// All t() calls inside this scope become 'hero.key'
const t = useTranslations('hero');

return <h1>{t('title')}</h1>; // Detected as: hero.title

Server Components (Async Functions) #

TSX
// ✅ Direct string namespace
const t = await getTranslations('welcome');
return <h1>{t('title')}</h1>; // Detected as: welcome.title

// ✅ Object with namespace property
const t = await getTranslations({
  locale: params.locale,
  namespace: 'maintenance'
});
return <p>{t('message')}</p>; // Detected as: maintenance.message

// ⚠️  Root scoped (no namespace)
const t = await getTranslations();
return <p>{t('global.title')}</p>; // Detected as: global.title (root scope)

Supported Patterns #

  • useTranslations('namespace') - Client component with namespace
  • getTranslations('namespace') - Server component with namespace
  • getTranslations({ locale, namespace: 'namespace' }) - Server with locale
  • useTranslations() / getTranslations() - Root scoped (no namespace prefix)
  • Standard Methods: All patterns support t(), t.raw(), t.rich(), and t.has().

Dynamic Namespaces #

If a namespace is a variable or template literal (e.g., useTranslations(`pages.${slug}`)), it is reported as unknown-scoped in --verbose mode and skipped from metrics to prevent false positives.


Flattening rules #

To ensure CLI metrics match the Better i18n UI:

  1. Primitives are leaves: Strings, numbers, and booleans are counted as keys.
  2. Arrays are leaves: meta.keywords: ["i18n", "cli"] is counted as one key, not two. This matches the UI's "Base Key" counting.
  3. Objects are containers: Keys like pages.home.hero representing a grouping of sub-keys are ignored in the final key count.

JSON Output #

Use --format json for CI scripts and automation.

JSON
{
  "project": {
    "workspace": "my-org",
    "slug": "my-app",
    "sourceLocale": "en"
  },
  "localKeys": {
    "total": 1223
  },
  "comparison": {
    "missingInRemote": { "pages.bestApps": ["pages.bestApps.meta.title"] },
    "unusedInCode": { "old": ["old.unused_key"] },
    "missingCount": 473,
    "unusedCount": 386
  },
  "coverage": {
    "local": 59,
    "remote": 63
  },
  "files": 246,
  "duration": 850
}

CI Integration #

Fail build on missing keys #

Bash
# Using jq to check missingCount
better-i18n sync --format json | jq -e '.comparison.missingCount == 0' > /dev/null || exit 1

List missing keys in CI logs #

Bash
better-i18n sync --format json | jq '.comparison.missingInRemote'