Skip to content

Latest commit

 

History

History
616 lines (470 loc) · 17.9 KB

File metadata and controls

616 lines (470 loc) · 17.9 KB

Internationalization (i18n) Management System

This project uses i18n-state-manager, a git-native translation management tool with intelligent stale detection, to ensure translation consistency and prevent missing or outdated translations from being deployed.

Overview

The i18n system tracks:

  • Manifest: SHA1 hashes of all English (source) strings
  • State: Translation status for each locale (up-to-date, missing, or stale)
  • CI Enforcement: Automatic verification in GitHub Actions

Requirements

  • Node.js >= 16.0.0
  • ripgrep (required for check-usage and find-unused commands)

Quick Start

For Developers

When you modify English strings in src/i18n/resources/en.ts:

# 1. Edit the English translation file
vim src/i18n/resources/en.ts

# 2. Update the manifest and state files
npm run i18n:sync

# 3. Commit all changes (including generated files)
git add src/i18n/resources/en.ts i18n.manifest.json i18n.state.json
git commit -m "feat: add new translation keys for feature X"

For Translators

To translate strings to another language:

# 1. Check what needs translation
npm run i18n:verify

# 2. Edit the translation file (e.g., French)
vim src/i18n/resources/fr.ts

# 3. Update state to mark translations as current
npm run i18n:sync

# 4. Commit your changes
git add src/i18n/resources/fr.ts i18n.state.json
git commit -m "feat: add French translations for feature X"

Available Commands

Command Purpose When to Use
npm run i18n:sync Update manifest and state files After changing ANY translation files
npm run i18n:verify Check for missing/stale translations Before releasing (fails on issues)
npm run i18n:status Show translation coverage summary To check overall progress
npm run i18n:check-usage Find translation keys used in code To verify all keys in code exist in en.ts
npm run i18n:find-unused Find keys not used in source code To identify potentially unused translation keys
npm run i18n:check-duplicates Check for duplicate keys To catch copy-paste errors in translation files
npm run i18n:generate-template <locale> Generate translation template Creating/updating translations for a locale

All commands are powered by i18n-state-manager. You can also run them directly:

npx i18n-state <command>

How It Works

1. Manifest File (i18n.manifest.json)

Contains SHA1 hashes of all English strings:

{
  "common.appName": "6458145fdd07ad08ff52a2e72d531588936bdca6",
  "common.cancel": "77dfd2135f4db726c47299bb55be26f7f4525a46"
}

2. State File (i18n.state.json)

Tracks translation status for each locale:

{
  "fr": {
    "common.appName": {
      "source": "6458145fdd07ad08ff52a2e72d531588936bdca6",
      "translation": "8f3d9e2a1b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e"
    },
    "common.cancel": null  // Missing translation
  }
}

The system compares source hashes from the manifest with state hashes to determine:

  • Up-to-date: Source hash matches the hash stored in state
  • Missing: Key has no entry or null in state
  • Stale: Source hash in manifest differs from source hash in state (source text changed)

3. CI/CD Enforcement

The GitHub Actions workflow automatically:

  • Runs npm run i18n:sync on every PR/push
  • Fails the build if manifest/state files are out of date
  • Forces developers to commit synchronized files

Workflow Examples

Adding a New English String

# 1. Add to en.ts
export const en = {
  common: {
    newFeature: 'My new feature'  // ← Add this
  }
}

# 2. Sync
npm run i18n:sync
# Generated manifest from "en.ts" with 1210 keys.
# Updated state for locales: fr.

# 3. Check status
npm run i18n:status
# fr: 99% translated, 0% stale  (1 new missing key)

# 4. Commit everything
git add src/i18n/resources/en.ts i18n.manifest.json i18n.state.json
git commit -m "feat: add newFeature translation key"

Translating to French

# 1. See what needs translation
npm run i18n:verify
# Missing translations:
#   [fr] 1 missing keys:
#     - common.newFeature

# 2. Add French translation
# Edit fr.ts and add: newFeature: 'Ma nouvelle fonctionnalité'

# 3. Mark as current
npm run i18n:sync
# Updated state for locales: fr.

# 4. Verify completion
npm run i18n:verify
# All translations are up-to-date.

# 5. Commit
git add src/i18n/resources/fr.ts i18n.state.json
git commit -m "feat: add French translation for newFeature"

When English String Changes

# 1. Modify existing English string
# Change "My new feature" → "My awesome feature"

# 2. Sync (this marks French as stale)
npm run i18n:sync

# 3. Check status
npm run i18n:verify
# Stale translations (source text changed):
#   [fr] 1 stale keys:
#     - common.newFeature

# 4. Update French translation
# Edit fr.ts: "Ma nouvelle fonctionnalité" → "Ma fonctionnalité géniale"

# 5. Mark as current
npm run i18n:sync

# 6. Commit all changes
git add src/i18n/resources/en.ts src/i18n/resources/fr.ts i18n.manifest.json i18n.state.json
git commit -m "feat: improve newFeature translation"

Verifying Keys Used in Code

# 1. After adding new features, check if all translation keys exist
npm run i18n:check-usage

# Example output if keys are missing:
# Keys used in code but missing from en.ts:
#   - modals.newModal.title
#   - modals.newModal.save

# 2. Add missing keys to en.ts
# Edit src/i18n/resources/en.ts

# 3. Sync to update manifest
npm run i18n:sync

# 4. Verify all keys now exist
npm run i18n:check-usage
# All keys used in source code exist in en.ts

# 5. Optional: Run /translate-missing in Claude Code to auto-translate
# This will translate the new keys to all other locales

Generating Translation Templates

# Generate a template for an existing locale (preserves existing translations)
npm run i18n:generate-template fr

# Output:
# Template generated: src/i18n/resources/fr.template.ts
# Statistics:
#   Total keys: 1540
#   Up-to-date: 1467
#   Missing: 73
#   Stale: 0

# The template will have:
# - Up-to-date translations preserved as-is
# - Missing translations marked as "TODO: English text"
# - Stale translations marked as "STALE: old translation" (English changed)

# Search for TODO to find what needs translation
grep "TODO:" src/i18n/resources/fr.template.ts

# Search for STALE to find outdated translations
grep "STALE:" src/i18n/resources/fr.template.ts

# Translate the TODO items, then replace the original file
mv src/i18n/resources/fr.template.ts src/i18n/resources/fr.ts

# Or create a new locale from scratch
npm run i18n:generate-template it
# All values will be "TODO: <English text>"

Technical Details

File Structure

├── i18n-state.config.json       # Configuration for i18n-state-manager
├── i18n.manifest.json           # Source string hashes
├── i18n.state.json              # Translation state tracking
├── src/i18n/resources/
│   ├── en.ts                    # English (source) translations
│   ├── fr.ts                    # French translations
│   ├── de.ts                    # German translations
│   ├── es.ts                    # Spanish translations
│   ├── ja.ts                    # Japanese translations
│   ├── ru.ts                    # Russian translations
│   └── zh.ts                    # Chinese translations
└── .github/workflows/test.yml   # CI/CD enforcement

Configuration File

The i18n-state.config.json file configures how i18n-state-manager processes your translations:

{
  "sourceLocale": "en",
  "resourcesDir": "src/i18n/resources",
  "manifestPath": "i18n.manifest.json",
  "statePath": "i18n.state.json",
  "scanDirs": ["src"],
  "scanExtensions": ["ts", "tsx", "js", "jsx"],
  "patterns": [
    {
      "name": "this.t()",
      "regex": "this\\.t\\([\"']([^\"']+)[\"']\\)"
    },
    {
      "name": "this.translate()",
      "regex": "this\\.translate\\([\"']([^\"']+)[\"']\\)"
    }
  ]
}

Configuration Options:

  • sourceLocale: The source language for your translations (typically "en")
  • resourcesDir: Directory containing translation files
  • manifestPath: Path to manifest file (tracks source hashes)
  • statePath: Path to state file (tracks translation status)
  • scanDirs: Directories to scan for translation usage
  • scanExtensions: File extensions to include in scans
  • patterns: Regular expressions to detect translation function calls in code

Translation Detection Logic

The system considers a translation:

  • Missing: Key doesn't exist in translation file (null in state)
  • Stale: Source hash in manifest differs from source hash in state (English text changed)
  • Up-to-date: Source hash in manifest matches source hash in state

CI/CD Integration

The workflow step in .github/workflows/test.yml:

- name: Check i18n manifest is up-to-date
  run: |
    npm run i18n:sync
    if [[ -n "$(git status --porcelain)" ]]; then
      echo "Error: i18n files are out of date"
      exit 1
    fi
    npm run i18n:verify

This ensures:

  • No untranslated strings slip into production
  • Translation state is always tracked
  • Developers must explicitly acknowledge translation needs

4. Usage Verification (npm run i18n:check-usage)

Scans your source code to find all translation function calls and verifies the keys exist in en.ts.

Detected Patterns: The tool scans for patterns defined in i18n-state.config.json:

  1. this.t("key") - Shorthand class method calls
  2. this.translate("key") - Recommended class pattern
  3. i18n.translate("key") - Direct service calls
  4. i18nService.translate("key") - Service instance calls

Example Output:

$ npm run i18n:check-usage

Checking i18n key usage in source code...

Found 619 unique translation keys in source code

Keys used in code but missing from en.ts:
  - modals.task.newField
  - settings.experimental.feature

2 missing key(s) found.
Add these keys to src/i18n/resources/en.ts and run "i18n-state sync".

What it finds:

  • All function calls with string literal keys
  • Multi-line translation calls
  • Patterns matching the configured regex patterns

What it doesn't find:

  • Dynamically constructed keys (`common.${variable}`)
  • Keys stored in variables before use
  • For these cases, use npm run i18n:find-unused to identify potentially unused keys

Use this to:

  • Catch typos in translation keys before runtime
  • Ensure new features have translation keys in en.ts
  • Identify keys used in code that need to be added to translations

5. Finding Unused Keys (npm run i18n:find-unused)

Scans source code to identify translation keys in en.ts that aren't used anywhere in the codebase.

Example Output:

$ npm run i18n:find-unused

Finding unused translation keys...

Statistics:
  Total keys in en.ts: 1745
  Keys found in source code: 619
  Potentially unused keys: 1126
  Coverage: 35%

Potentially unused keys (not found in source code):

[commands] 24 keys:
  - commands.openCalendarView
  - commands.openAdvancedCalendarView
  ...

Note: These keys might be:
  - Dynamically constructed (e.g., `common.weekdays.${day}`)
  - Used in external files or configurations
  - Reserved for future features
  - Truly unused and can be removed

Manually review before deleting!

Important: This command identifies potentially unused keys. Always manually review before removing any keys, as some may be:

  • Dynamically constructed at runtime
  • Used in configuration files
  • Used by external integrations
  • Reserved for planned features

Best Practices

Translation Management

  1. Always run npm run i18n:sync after modifying translation files
  2. Commit generated files (i18n.manifest.json, i18n.state.json) with your changes
  3. Use descriptive keys like features.taskList.emptyState instead of generic ones
  4. Group related translations using nested objects for better organization
  5. Test locally with npm run i18n:verify before pushing
  6. Run npm run i18n:check-usage periodically to catch missing keys early
  7. Review unused keys with npm run i18n:find-unused before major releases
  8. Check for duplicates with npm run i18n:check-duplicates to catch copy-paste errors

Translation Usage in Code

Recommended Pattern: Use this.translate() for consistent, type-safe translations

class MyComponent {
    private translate: (key: TranslationKey, vars?: Record<string, any>) => string;

    constructor(plugin: TaskNotesPlugin) {
        this.translate = plugin.i18n.translate.bind(plugin.i18n);
    }

    render() {
        // ✅ Recommended: Clear, type-safe, consistent
        return this.translate("modals.task.title");

        // ✅ Also acceptable: For interpolation
        return this.translate("modals.task.count", { count: 5 });
    }
}

Alternative Patterns (also valid):

// For settings/functions where plugin is passed as parameter
function renderSettings(plugin: TaskNotesPlugin) {
    const translate = (key: TranslationKey) => plugin.i18n.translate(key);
    return translate("settings.general.title");
}

// For scoped prefixes (reduces repetition)
function renderCalendarSettings(plugin: TaskNotesPlugin) {
    const t = (key: string) =>
        plugin.i18n.translate(`views.calendar.settings.${key}`);

    return {
        events: t("events.showTasks"),  // → "views.calendar.settings.events.showTasks"
        layout: t("layout.weekStart"),  // → "views.calendar.settings.layout.weekStart"
    };
}

What to Avoid:

// ❌ Avoid: Inconsistent naming (use 'translate' not 't' for main method)
this.t("key")  // Use this.translate() instead

// ❌ Avoid: Direct service calls (verbose, requires plugin reference)
this.plugin.i18n.translate("key")  // Use this.translate() instead

Key Principles:

  • Consistency: Stick to one primary pattern (this.translate())
  • Type Safety: Always use TranslationKey type for keys
  • Scoped Helpers: Use prefix helpers (like const t = ...) only when you have many keys with the same prefix
  • Descriptive Keys: Use modals.task.created not taskCreated

Troubleshooting

CI fails with "i18n files are out of date"

npm run i18n:sync
git add i18n.manifest.json i18n.state.json
git commit -m "chore: update i18n manifest and state files"

Runtime errors: "Translation key not found"

# Find which keys are used in code but missing from en.ts
npm run i18n:check-usage

# Add the missing keys to src/i18n/resources/en.ts
# Then sync
npm run i18n:sync

"Keys used in code but missing from en.ts" This means you're calling a translation function with a key that doesn't exist yet:

// Your code:
this.translate("modals.newFeature.title")  // Key doesn't exist

// Solution: Add to src/i18n/resources/en.ts
export const en = {
  modals: {
    newFeature: {
      title: "New Feature Title"  // Add this
    }
  }
}

"Error: This command requires ripgrep (rg) to be installed" The check-usage and find-unused commands require ripgrep for code scanning:

# macOS
brew install ripgrep

# Ubuntu/Debian
apt install ripgrep

# Arch Linux
pacman -S ripgrep

# Windows
choco install ripgrep

"Could not load source locale" error

  • Check that src/i18n/resources/en.ts exists and has valid syntax
  • Ensure the export follows the pattern: export const en = { ... }

Wrong translation statistics

  • Run npm run i18n:sync to recalculate
  • Check that translated strings are actually different from English

Hook not running after editing translation files

  • Check that .claude/settings.json exists and contains the post-tool hook
  • Verify you're editing files matching src/i18n/resources/*.ts
  • Try restarting Claude Code CLI

"Duplicate keys found" Run npm run i18n:check-duplicates to identify duplicate translation keys in your files. This usually happens from copy-paste errors.

Translation Progress

Use these commands to track progress:

# Quick overview
npm run i18n:status

# Detailed missing/stale report
npm run i18n:verify

# After making changes
npm run i18n:sync

# Check for missing keys in code
npm run i18n:check-usage

# Find potentially unused keys
npm run i18n:find-unused

# Check for duplicate keys
npm run i18n:check-duplicates

Claude Code Integration

Automated Translation with Hooks

The project includes Claude Code hooks that automatically sync translation files when you edit them:

Location: .claude/settings.json

When you edit any file in src/i18n/resources/*.ts, the hook automatically:

  1. Runs npm run i18n:sync to update manifest and state files
  2. Checks if translations are needed and reminds you

Example workflow:

# 1. You edit src/i18n/resources/en.ts
# Hook automatically runs: npm run i18n:sync
# Output:
#   i18n files synced
#   Translations needed. Run /translate-missing to update locales.

# 2. Run the slash command to auto-translate
/translate-missing

# 3. All locales are now updated with AI translations

Slash Command: /translate-missing

Location: .claude/commands/translate-missing.md

This command automatically translates all missing and stale keys across all 7 locales (de, es, fr, ja, ru, zh).

Features:

  • Preserves interpolation syntax (e.g., {{variable}})
  • Maintains consistent tone and style with existing translations
  • Handles all locales in a single command
  • Automatically runs npm run i18n:sync after completion

Usage:

# Just run the command - it handles everything
/translate-missing

What it does:

  1. Runs npm run i18n:verify to find missing/stale keys
  2. Translates each missing key to the target language
  3. Updates all locale files
  4. Runs npm run i18n:sync to mark translations as current
  5. Verifies completion with npm run i18n:verify

This system ensures translation quality and consistency while making the workflow as smooth as possible for both developers and translators.