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.
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
- Node.js >= 16.0.0
- ripgrep (required for
check-usageandfind-unusedcommands)
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"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"| 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>Contains SHA1 hashes of all English strings:
{
"common.appName": "6458145fdd07ad08ff52a2e72d531588936bdca6",
"common.cancel": "77dfd2135f4db726c47299bb55be26f7f4525a46"
}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)
The GitHub Actions workflow automatically:
- Runs
npm run i18n:syncon every PR/push - Fails the build if manifest/state files are out of date
- Forces developers to commit synchronized files
# 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"# 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"# 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"# 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# 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>"├── 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
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 filesmanifestPath: Path to manifest file (tracks source hashes)statePath: Path to state file (tracks translation status)scanDirs: Directories to scan for translation usagescanExtensions: File extensions to include in scanspatterns: Regular expressions to detect translation function calls in code
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
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:verifyThis ensures:
- No untranslated strings slip into production
- Translation state is always tracked
- Developers must explicitly acknowledge translation needs
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:
this.t("key")- Shorthand class method callsthis.translate("key")- Recommended class patterni18n.translate("key")- Direct service callsi18nService.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-unusedto 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
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
- Always run
npm run i18n:syncafter modifying translation files - Commit generated files (
i18n.manifest.json,i18n.state.json) with your changes - Use descriptive keys like
features.taskList.emptyStateinstead of generic ones - Group related translations using nested objects for better organization
- Test locally with
npm run i18n:verifybefore pushing - Run
npm run i18n:check-usageperiodically to catch missing keys early - Review unused keys with
npm run i18n:find-unusedbefore major releases - Check for duplicates with
npm run i18n:check-duplicatesto catch copy-paste errors
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() insteadKey Principles:
- Consistency: Stick to one primary pattern (
this.translate()) - Type Safety: Always use
TranslationKeytype 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.creatednottaskCreated
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.tsexists and has valid syntax - Ensure the export follows the pattern:
export const en = { ... }
Wrong translation statistics
- Run
npm run i18n:syncto recalculate - Check that translated strings are actually different from English
Hook not running after editing translation files
- Check that
.claude/settings.jsonexists 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.
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-duplicatesThe 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:
- Runs
npm run i18n:syncto update manifest and state files - 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 translationsLocation: .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:syncafter completion
Usage:
# Just run the command - it handles everything
/translate-missingWhat it does:
- Runs
npm run i18n:verifyto find missing/stale keys - Translates each missing key to the target language
- Updates all locale files
- Runs
npm run i18n:syncto mark translations as current - 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.