Install command
Provided
Validates translation files for missing keys and ensures consistency across different language files.
Open the source and read safety notes before installing.
Source-backed facts for citing this resource, derived directly from the registry — also available as plain text for AI assistants.
Decision playbook
Signals are present but mixed. Use the checklist below to confirm the source and operational safety for your environment.
0
78
—
No baseline selected
No major trust-signal divergence detected in the current selection.
Confirm ownership and provenance before trusting install instructions.
Source link availableRequired
Open the canonical repository and verify ownership.
Source provenance statusRequired
Marked as source-backed.
Metadata reviewed
Registry metadata indicates a reviewed listing.
Validate risk disclosures before installation or API wiring.
Safety notes presentRequired
Review the listed safety guidance before running commands.
Privacy notes presentRequired
Review data handling notes before connecting accounts or secrets.
Trust level risk gateRequired
Trust level does not block evaluation.
Check package metadata and artifact integrity signals.
Install payload available
Install or copy payload is available for review.
Package verification flag
No package verification flag provided.
Checksum metadata
No checksum provided for downloaded artifact.
Use compare context to validate trade-offs before adoption.
Compare tray has multiple entries
Add at least one more entry to compare trust differences.
Baseline comparison available
No baseline peer selected yet.
Diverging trust signals identified
No major trust-signal divergence found.
Setup at a glance
Copy-ready — paste the snippet to get started.
Install command
Provided
Config snippet
Provided
Copy snippet
Provided
Prerequisites
None
Platforms
1 listed
Difficulty
0/100
Adoption plan
Current risk score 16/100. Use staged verification before broader rollout.
Validate source and review signals before any execution.
Confirm source provenanceRequired
Source URL/provenance metadata is present.
Confirm metadata review state
Listing has review metadata.
Verify install payload
Install/config payload exists and can be inspected.
Confirm safety, privacy, and package integrity signals.
Review safety notesRequired
Safety notes are present.
Review privacy notesRequired
Privacy notes are present.
Verify package integrity metadata
No package verification/checksum metadata.
Adopt in controlled steps based on the selected plan.
Run in isolated sandbox firstRequired
Use a constrained sandbox and observe behavior across multiple tasks.
Roll out graduallyRequired
Roll out to a small cohort before wider usage.
Set monitoring and fallback
Define rollback path and monitor errors after adoption.
Evidence readiness
Required evidence gates are covered (5/6 signals complete).
Source repository/provenance is listed.
Required in this preset
Review metadata is present.
Required in this preset
Safety notes are present.
Required in this preset
Privacy notes are present.
Optional in this preset
Package integrity metadata is missing.
Optional in this preset
Install payload is available.
Required in this preset
Required evidence gates are covered for this preset.
Decision timeline
5/6 steps complete with no blocking gaps for this preset.
triage
Source/provenance metadata is available.
triage
Review metadata is available.
verify
Safety notes are available.
verify
Privacy notes are available.
verify
Package integrity metadata is missing.
rollout
Install payload is available.
No required blockers for this timeline preset.
Safety & privacy surface
1 safety and 1 privacy notes across 2 risk areas. Review closely: credentials & tokens, permissions & scopes.
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Check if this is a translation/localization file
if [[ "$FILE_PATH" == *locales/*.json ]] || [[ "$FILE_PATH" == *i18n/*.json ]] || [[ "$FILE_PATH" == *lang/*.json ]] || [[ "$FILE_PATH" == *translations/*.json ]] || [[ "$FILE_PATH" == *.po ]] || [[ "$FILE_PATH" == *messages/*.properties ]]; then
echo "🌍 i18n Translation Validation for: $(basename "$FILE_PATH")" >&2
# Initialize validation counters
ERRORS=0
WARNINGS=0
MISSING_KEYS=0
ORPHANED_KEYS=0
TOTAL_KEYS=0
# Function to report validation results
report_validation() {
local level="$1"
local message="$2"
case "$level" in
"ERROR")
echo "❌ ERROR: $message" >&2
ERRORS=$((ERRORS + 1))
;;
"WARNING")
echo "⚠️ WARNING: $message" >&2
WARNINGS=$((WARNINGS + 1))
;;
"MISSING")
echo "🔍 MISSING: $message" >&2
MISSING_KEYS=$((MISSING_KEYS + 1))
;;
"ORPHANED")
echo "🏷️ ORPHANED: $message" >&2
ORPHANED_KEYS=$((ORPHANED_KEYS + 1))
;;
"PASS")
echo "✅ PASS: $message" >&2
;;
"INFO")
echo "ℹ️ INFO: $message" >&2
;;
esac
}
# Check if file exists and is readable
if [ ! -f "$FILE_PATH" ]; then
report_validation "ERROR" "Translation file not found: $FILE_PATH"
exit 1
fi
if [ ! -r "$FILE_PATH" ]; then
report_validation "ERROR" "Translation file is not readable: $FILE_PATH"
exit 1
fi
# Determine file format
FILE_EXT="${FILE_PATH##*.}"
LOCALE_DIR="$(dirname "$FILE_PATH")"
FILE_NAME="$(basename "$FILE_PATH")"
LOCALE_CODE="${FILE_NAME%.*}"
echo "📊 Translation file: $FILE_NAME (format: $FILE_EXT, locale: $LOCALE_CODE)" >&2
# 1. File Format Validation
echo "📋 Validating file format..." >&2
case "$FILE_EXT" in
"json")
if jq empty "$FILE_PATH" 2>/dev/null; then
report_validation "PASS" "Valid JSON syntax"
TOTAL_KEYS=$(jq -r 'keys | length' "$FILE_PATH" 2>/dev/null || echo "0")
else
report_validation "ERROR" "Invalid JSON syntax - file cannot be parsed"
exit 1
fi
;;
"po")
if command -v msgfmt &> /dev/null; then
if msgfmt --check "$FILE_PATH" -o /dev/null 2>/dev/null; then
report_validation "PASS" "Valid PO file format"
else
report_validation "ERROR" "Invalid PO file format"
fi
else
report_validation "WARNING" "msgfmt not available - limited PO validation"
fi
;;
"properties")
# Basic properties file validation
if grep -q '=' "$FILE_PATH" 2>/dev/null; then
report_validation "PASS" "Properties file format detected"
else
report_validation "WARNING" "No key=value pairs found in properties file"
fi
;;
*)
report_validation "WARNING" "Unknown translation file format: $FILE_EXT"
;;
esac
# 2. Find Base Translation File (for comparison)
echo "🔍 Locating base translation file..." >&2
BASE_FILE=""
BASE_CANDIDATES=("en.json" "en-US.json" "en_US.json" "base.json" "default.json")
for candidate in "${BASE_CANDIDATES[@]}"; do
if [ -f "$LOCALE_DIR/$candidate" ] && [ "$LOCALE_DIR/$candidate" != "$FILE_PATH" ]; then
BASE_FILE="$LOCALE_DIR/$candidate"
echo " 📁 Base file found: $candidate" >&2
break
fi
done
if [ -z "$BASE_FILE" ]; then
# Look for any .json file in the directory as base
FIRST_JSON=$(find "$LOCALE_DIR" -name '*.json' -not -path "$FILE_PATH" | head -1)
if [ -n "$FIRST_JSON" ]; then
BASE_FILE="$FIRST_JSON"
echo " 📁 Using first available JSON as base: $(basename "$BASE_FILE")" >&2
else
echo " ⚠️ No base translation file found - running standalone validation" >&2
fi
fi
# 3. Key Structure Validation (JSON files)
if [ "$FILE_EXT" = "json" ]; then
echo "🔑 Analyzing translation keys..." >&2
# Check for nested vs flat structure
NESTED_COUNT=$(jq '[.. | objects | keys] | flatten | length' "$FILE_PATH" 2>/dev/null || echo "0")
if [ "$NESTED_COUNT" -gt "$TOTAL_KEYS" ]; then
echo " 📊 Nested key structure detected ($NESTED_COUNT total keys)" >&2
else
echo " 📊 Flat key structure ($TOTAL_KEYS keys)" >&2
fi
# Check for empty values
EMPTY_VALUES=$(jq '[.. | select(type == "string" and length == 0)] | length' "$FILE_PATH" 2>/dev/null || echo "0")
if [ "$EMPTY_VALUES" -gt 0 ]; then
report_validation "WARNING" "$EMPTY_VALUES empty translation values found"
fi
# Check for untranslated strings (same as key)
UNTRANSLATED=0
if command -v jq &> /dev/null; then
UNTRANSLATED=$(jq -r 'to_entries[] | select(.key == .value) | .key' "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
if [ "$UNTRANSLATED" -gt 0 ]; then
report_validation "WARNING" "$UNTRANSLATED potentially untranslated strings (key equals value)"
fi
fi
fi
# 4. Compare with Base File (if available)
if [ -n "$BASE_FILE" ] && [ -f "$BASE_FILE" ]; then
echo "🔄 Comparing with base translation file..." >&2
if [ "$FILE_EXT" = "json" ]; then
# Extract all keys from both files
BASE_KEYS_FILE="/tmp/base_keys_$$"
CURRENT_KEYS_FILE="/tmp/current_keys_$$"
# Get all nested keys (dot notation)
jq -r 'paths(scalars) as $p | $p | join(".")' "$BASE_FILE" 2>/dev/null | sort > "$BASE_KEYS_FILE"
jq -r 'paths(scalars) as $p | $p | join(".")' "$FILE_PATH" 2>/dev/null | sort > "$CURRENT_KEYS_FILE"
# Find missing keys (in base but not in current)
MISSING_KEYS_LIST="/tmp/missing_keys_$$"
comm -23 "$BASE_KEYS_FILE" "$CURRENT_KEYS_FILE" > "$MISSING_KEYS_LIST"
MISSING_COUNT=$(wc -l < "$MISSING_KEYS_LIST" | xargs)
if [ "$MISSING_COUNT" -gt 0 ]; then
report_validation "MISSING" "$MISSING_COUNT translation keys missing from base"
echo " Missing keys:" >&2
head -10 "$MISSING_KEYS_LIST" | while read key; do
echo " - $key" >&2
done
[ "$MISSING_COUNT" -gt 10 ] && echo " ... and $((MISSING_COUNT - 10)) more" >&2
else
report_validation "PASS" "All base translation keys are present"
fi
# Find orphaned keys (in current but not in base)
ORPHANED_KEYS_LIST="/tmp/orphaned_keys_$$"
comm -13 "$BASE_KEYS_FILE" "$CURRENT_KEYS_FILE" > "$ORPHANED_KEYS_LIST"
ORPHANED_COUNT=$(wc -l < "$ORPHANED_KEYS_LIST" | xargs)
if [ "$ORPHANED_COUNT" -gt 0 ]; then
report_validation "ORPHANED" "$ORPHANED_COUNT keys not found in base (potential orphans)"
echo " Orphaned keys:" >&2
head -5 "$ORPHANED_KEYS_LIST" | while read key; do
echo " - $key" >&2
done
[ "$ORPHANED_COUNT" -gt 5 ] && echo " ... and $((ORPHANED_COUNT - 5)) more" >&2
else
report_validation "PASS" "No orphaned keys detected"
fi
# Calculate completeness percentage
BASE_KEY_COUNT=$(wc -l < "$BASE_KEYS_FILE" | xargs)
if [ "$BASE_KEY_COUNT" -gt 0 ]; then
TRANSLATED_COUNT=$((BASE_KEY_COUNT - MISSING_COUNT))
COMPLETENESS=$((TRANSLATED_COUNT * 100 / BASE_KEY_COUNT))
echo " 📊 Translation completeness: $COMPLETENESS% ($TRANSLATED_COUNT/$BASE_KEY_COUNT)" >&2
if [ "$COMPLETENESS" -eq 100 ]; then
report_validation "PASS" "Translation is 100% complete"
elif [ "$COMPLETENESS" -ge 90 ]; then
report_validation "WARNING" "Translation is $COMPLETENESS% complete (good but not perfect)"
elif [ "$COMPLETENESS" -ge 70 ]; then
report_validation "WARNING" "Translation is $COMPLETENESS% complete (needs attention)"
else
report_validation "ERROR" "Translation is only $COMPLETENESS% complete (significant gaps)"
fi
fi
# Cleanup temp files
rm -f "$BASE_KEYS_FILE" "$CURRENT_KEYS_FILE" "$MISSING_KEYS_LIST" "$ORPHANED_KEYS_LIST"
fi
fi
# 5. Variable Placeholder Validation
echo "🔤 Checking variable placeholders..." >&2
if [ "$FILE_EXT" = "json" ]; then
# Check for common placeholder patterns
PLACEHOLDER_PATTERNS=(
'{{[^}]+}}' # Handlebars: {{variable}}
'{[^}]+}' # Simple: {variable}
'%[a-zA-Z_]+%' # Percent: %variable%
'\$\{[^}]+\}' # Dollar: ${variable}
'%[sd]' # Printf style: %s, %d
)
PLACEHOLDER_COUNT=0
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
COUNT=$(grep -oE "$pattern" "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
PLACEHOLDER_COUNT=$((PLACEHOLDER_COUNT + COUNT))
done
if [ "$PLACEHOLDER_COUNT" -gt 0 ]; then
echo " 📝 $PLACEHOLDER_COUNT variable placeholders found" >&2
# Check for unmatched placeholders if base file exists
if [ -n "$BASE_FILE" ]; then
# This is a simplified check - in practice, you'd want more sophisticated matching
echo " 🔍 Cross-referencing placeholders with base file..." >&2
fi
else
echo " ℹ️ No variable placeholders detected" >&2
fi
fi
# 6. Locale-Specific Validation
echo "🌐 Locale-specific validation..." >&2
case "$LOCALE_CODE" in
"ar"*|"he"*|"fa"*)
echo " 🔄 RTL language detected - ensure proper text direction handling" >&2
;;
"zh"*|"ja"*|"ko"*)
echo " 🈴 CJK language detected - ensure proper character encoding" >&2
;;
"en"*)
echo " 🇺🇸 English locale - checking for common issues" >&2
;;
*)
echo " 🌍 Locale: $LOCALE_CODE" >&2
;;
esac
# Check for potential encoding issues (non-ASCII characters)
if [ "$FILE_EXT" = "json" ]; then
NON_ASCII_COUNT=$(grep -P '[^\x00-\x7F]' "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
if [ "$NON_ASCII_COUNT" -gt 0 ]; then
echo " 🔤 $NON_ASCII_COUNT lines contain non-ASCII characters (normal for international content)" >&2
fi
fi
# 7. Multi-file Consistency Check
echo "📁 Checking consistency across locale files..." >&2
LOCALE_FILES=()
while IFS= read -r -d '' file; do
LOCALE_FILES+=("$file")
done < <(find "$LOCALE_DIR" -name "*.$FILE_EXT" -print0 2>/dev/null)
LOCALE_COUNT=${#LOCALE_FILES[@]}
if [ "$LOCALE_COUNT" -gt 1 ]; then
echo " 📊 Found $LOCALE_COUNT locale files in directory" >&2
# Check if all files have similar key counts (within 20% difference)
if [ "$FILE_EXT" = "json" ] && [ "$TOTAL_KEYS" -gt 0 ]; then
INCONSISTENT_FILES=0
for locale_file in "${LOCALE_FILES[@]}"; do
if [ "$locale_file" != "$FILE_PATH" ]; then
OTHER_KEY_COUNT=$(jq -r 'keys | length' "$locale_file" 2>/dev/null || echo "0")
DIFF_PERCENT=$((abs(TOTAL_KEYS - OTHER_KEY_COUNT) * 100 / TOTAL_KEYS))
if [ "$DIFF_PERCENT" -gt 20 ]; then
INCONSISTENT_FILES=$((INCONSISTENT_FILES + 1))
fi
fi
done
if [ "$INCONSISTENT_FILES" -gt 0 ]; then
report_validation "WARNING" "$INCONSISTENT_FILES locale files have significantly different key counts"
else
report_validation "PASS" "All locale files have consistent key counts"
fi
fi
else
echo " ℹ️ Single locale file found" >&2
fi
# 8. Generate Validation Summary
echo "" >&2
echo "📋 i18n Translation Validation Summary:" >&2
echo "=====================================" >&2
echo " 📄 File: $FILE_NAME" >&2
echo " 🌍 Locale: $LOCALE_CODE" >&2
echo " 📊 Format: $FILE_EXT" >&2
[ "$TOTAL_KEYS" -gt 0 ] && echo " 🔑 Total Keys: $TOTAL_KEYS" >&2
echo " ❌ Errors: $ERRORS" >&2
echo " ⚠️ Warnings: $WARNINGS" >&2
echo " 🔍 Missing Keys: $MISSING_KEYS" >&2
echo " 🏷️ Orphaned Keys: $ORPHANED_KEYS" >&2
if [ "$ERRORS" -eq 0 ] && [ "$MISSING_KEYS" -eq 0 ]; then
if [ "$WARNINGS" -eq 0 ] && [ "$ORPHANED_KEYS" -eq 0 ]; then
echo " 🎉 Status: EXCELLENT - Translation file is complete and consistent" >&2
else
echo " ✅ Status: GOOD - Translation is functional with minor issues" >&2
fi
elif [ "$ERRORS" -eq 0 ]; then
echo " ⚠️ Status: INCOMPLETE - Missing translations need attention" >&2
else
echo " ❌ Status: ERRORS - Critical issues must be fixed" >&2
fi
echo "" >&2
echo "💡 i18n Translation Best Practices:" >&2
echo " • Keep translation keys consistent across all locales" >&2
echo " • Use meaningful, hierarchical key names" >&2
echo " • Validate placeholder variables across languages" >&2
echo " • Consider cultural context in translations" >&2
echo " • Test with longer/shorter text in different languages" >&2
echo " • Use proper character encoding (UTF-8)" >&2
# Exit with error if there are critical issues
if [ "$ERRORS" -gt 0 ]; then
echo "⚠️ Translation validation completed with errors" >&2
exit 1
fi
else
# Not a translation file, exit silently
exit 0
fi
exit 0{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/i18n-translation-validator.sh",
"matchers": [
"write",
"edit"
]
}
}
}.claude/settings.local.json~/.claude/settings.json.claude/settings.json{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/i18n-translation-validator.sh",
"matchers": ["write", "edit"]
}
}
}
#!/usr/bin/env bash
# Read the tool input from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Check if this is a translation/localization file
if [[ "$FILE_PATH" == *locales/*.json ]] || [[ "$FILE_PATH" == *i18n/*.json ]] || [[ "$FILE_PATH" == *lang/*.json ]] || [[ "$FILE_PATH" == *translations/*.json ]] || [[ "$FILE_PATH" == *.po ]] || [[ "$FILE_PATH" == *messages/*.properties ]]; then
echo "🌍 i18n Translation Validation for: $(basename "$FILE_PATH")" >&2
# Initialize validation counters
ERRORS=0
WARNINGS=0
MISSING_KEYS=0
ORPHANED_KEYS=0
TOTAL_KEYS=0
# Function to report validation results
report_validation() {
local level="$1"
local message="$2"
case "$level" in
"ERROR")
echo "❌ ERROR: $message" >&2
ERRORS=$((ERRORS + 1))
;;
"WARNING")
echo "⚠️ WARNING: $message" >&2
WARNINGS=$((WARNINGS + 1))
;;
"MISSING")
echo "🔍 MISSING: $message" >&2
MISSING_KEYS=$((MISSING_KEYS + 1))
;;
"ORPHANED")
echo "🏷️ ORPHANED: $message" >&2
ORPHANED_KEYS=$((ORPHANED_KEYS + 1))
;;
"PASS")
echo "✅ PASS: $message" >&2
;;
"INFO")
echo "ℹ️ INFO: $message" >&2
;;
esac
}
# Check if file exists and is readable
if [ ! -f "$FILE_PATH" ]; then
report_validation "ERROR" "Translation file not found: $FILE_PATH"
exit 1
fi
if [ ! -r "$FILE_PATH" ]; then
report_validation "ERROR" "Translation file is not readable: $FILE_PATH"
exit 1
fi
# Determine file format
FILE_EXT="${FILE_PATH##*.}"
LOCALE_DIR="$(dirname "$FILE_PATH")"
FILE_NAME="$(basename "$FILE_PATH")"
LOCALE_CODE="${FILE_NAME%.*}"
echo "📊 Translation file: $FILE_NAME (format: $FILE_EXT, locale: $LOCALE_CODE)" >&2
# 1. File Format Validation
echo "📋 Validating file format..." >&2
case "$FILE_EXT" in
"json")
if jq empty "$FILE_PATH" 2>/dev/null; then
report_validation "PASS" "Valid JSON syntax"
TOTAL_KEYS=$(jq -r 'keys | length' "$FILE_PATH" 2>/dev/null || echo "0")
else
report_validation "ERROR" "Invalid JSON syntax - file cannot be parsed"
exit 1
fi
;;
"po")
if command -v msgfmt &> /dev/null; then
if msgfmt --check "$FILE_PATH" -o /dev/null 2>/dev/null; then
report_validation "PASS" "Valid PO file format"
else
report_validation "ERROR" "Invalid PO file format"
fi
else
report_validation "WARNING" "msgfmt not available - limited PO validation"
fi
;;
"properties")
# Basic properties file validation
if grep -q '=' "$FILE_PATH" 2>/dev/null; then
report_validation "PASS" "Properties file format detected"
else
report_validation "WARNING" "No key=value pairs found in properties file"
fi
;;
*)
report_validation "WARNING" "Unknown translation file format: $FILE_EXT"
;;
esac
# 2. Find Base Translation File (for comparison)
echo "🔍 Locating base translation file..." >&2
BASE_FILE=""
BASE_CANDIDATES=("en.json" "en-US.json" "en_US.json" "base.json" "default.json")
for candidate in "${BASE_CANDIDATES[@]}"; do
if [ -f "$LOCALE_DIR/$candidate" ] && [ "$LOCALE_DIR/$candidate" != "$FILE_PATH" ]; then
BASE_FILE="$LOCALE_DIR/$candidate"
echo " 📁 Base file found: $candidate" >&2
break
fi
done
if [ -z "$BASE_FILE" ]; then
# Look for any .json file in the directory as base
FIRST_JSON=$(find "$LOCALE_DIR" -name '*.json' -not -path "$FILE_PATH" | head -1)
if [ -n "$FIRST_JSON" ]; then
BASE_FILE="$FIRST_JSON"
echo " 📁 Using first available JSON as base: $(basename "$BASE_FILE")" >&2
else
echo " ⚠️ No base translation file found - running standalone validation" >&2
fi
fi
# 3. Key Structure Validation (JSON files)
if [ "$FILE_EXT" = "json" ]; then
echo "🔑 Analyzing translation keys..." >&2
# Check for nested vs flat structure
NESTED_COUNT=$(jq '[.. | objects | keys] | flatten | length' "$FILE_PATH" 2>/dev/null || echo "0")
if [ "$NESTED_COUNT" -gt "$TOTAL_KEYS" ]; then
echo " 📊 Nested key structure detected ($NESTED_COUNT total keys)" >&2
else
echo " 📊 Flat key structure ($TOTAL_KEYS keys)" >&2
fi
# Check for empty values
EMPTY_VALUES=$(jq '[.. | select(type == "string" and length == 0)] | length' "$FILE_PATH" 2>/dev/null || echo "0")
if [ "$EMPTY_VALUES" -gt 0 ]; then
report_validation "WARNING" "$EMPTY_VALUES empty translation values found"
fi
# Check for untranslated strings (same as key)
UNTRANSLATED=0
if command -v jq &> /dev/null; then
UNTRANSLATED=$(jq -r 'to_entries[] | select(.key == .value) | .key' "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
if [ "$UNTRANSLATED" -gt 0 ]; then
report_validation "WARNING" "$UNTRANSLATED potentially untranslated strings (key equals value)"
fi
fi
fi
# 4. Compare with Base File (if available)
if [ -n "$BASE_FILE" ] && [ -f "$BASE_FILE" ]; then
echo "🔄 Comparing with base translation file..." >&2
if [ "$FILE_EXT" = "json" ]; then
# Extract all keys from both files
BASE_KEYS_FILE="/tmp/base_keys_$$"
CURRENT_KEYS_FILE="/tmp/current_keys_$$"
# Get all nested keys (dot notation)
jq -r 'paths(scalars) as $p | $p | join(".")' "$BASE_FILE" 2>/dev/null | sort > "$BASE_KEYS_FILE"
jq -r 'paths(scalars) as $p | $p | join(".")' "$FILE_PATH" 2>/dev/null | sort > "$CURRENT_KEYS_FILE"
# Find missing keys (in base but not in current)
MISSING_KEYS_LIST="/tmp/missing_keys_$$"
comm -23 "$BASE_KEYS_FILE" "$CURRENT_KEYS_FILE" > "$MISSING_KEYS_LIST"
MISSING_COUNT=$(wc -l < "$MISSING_KEYS_LIST" | xargs)
if [ "$MISSING_COUNT" -gt 0 ]; then
report_validation "MISSING" "$MISSING_COUNT translation keys missing from base"
echo " Missing keys:" >&2
head -10 "$MISSING_KEYS_LIST" | while read key; do
echo " - $key" >&2
done
[ "$MISSING_COUNT" -gt 10 ] && echo " ... and $((MISSING_COUNT - 10)) more" >&2
else
report_validation "PASS" "All base translation keys are present"
fi
# Find orphaned keys (in current but not in base)
ORPHANED_KEYS_LIST="/tmp/orphaned_keys_$$"
comm -13 "$BASE_KEYS_FILE" "$CURRENT_KEYS_FILE" > "$ORPHANED_KEYS_LIST"
ORPHANED_COUNT=$(wc -l < "$ORPHANED_KEYS_LIST" | xargs)
if [ "$ORPHANED_COUNT" -gt 0 ]; then
report_validation "ORPHANED" "$ORPHANED_COUNT keys not found in base (potential orphans)"
echo " Orphaned keys:" >&2
head -5 "$ORPHANED_KEYS_LIST" | while read key; do
echo " - $key" >&2
done
[ "$ORPHANED_COUNT" -gt 5 ] && echo " ... and $((ORPHANED_COUNT - 5)) more" >&2
else
report_validation "PASS" "No orphaned keys detected"
fi
# Calculate completeness percentage
BASE_KEY_COUNT=$(wc -l < "$BASE_KEYS_FILE" | xargs)
if [ "$BASE_KEY_COUNT" -gt 0 ]; then
TRANSLATED_COUNT=$((BASE_KEY_COUNT - MISSING_COUNT))
COMPLETENESS=$((TRANSLATED_COUNT * 100 / BASE_KEY_COUNT))
echo " 📊 Translation completeness: $COMPLETENESS% ($TRANSLATED_COUNT/$BASE_KEY_COUNT)" >&2
if [ "$COMPLETENESS" -eq 100 ]; then
report_validation "PASS" "Translation is 100% complete"
elif [ "$COMPLETENESS" -ge 90 ]; then
report_validation "WARNING" "Translation is $COMPLETENESS% complete (good but not perfect)"
elif [ "$COMPLETENESS" -ge 70 ]; then
report_validation "WARNING" "Translation is $COMPLETENESS% complete (needs attention)"
else
report_validation "ERROR" "Translation is only $COMPLETENESS% complete (significant gaps)"
fi
fi
# Cleanup temp files
rm -f "$BASE_KEYS_FILE" "$CURRENT_KEYS_FILE" "$MISSING_KEYS_LIST" "$ORPHANED_KEYS_LIST"
fi
fi
# 5. Variable Placeholder Validation
echo "🔤 Checking variable placeholders..." >&2
if [ "$FILE_EXT" = "json" ]; then
# Check for common placeholder patterns
PLACEHOLDER_PATTERNS=(
'{{[^}]+}}' # Handlebars: {{variable}}
'{[^}]+}' # Simple: {variable}
'%[a-zA-Z_]+%' # Percent: %variable%
'\$\{[^}]+\}' # Dollar: ${variable}
'%[sd]' # Printf style: %s, %d
)
PLACEHOLDER_COUNT=0
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
COUNT=$(grep -oE "$pattern" "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
PLACEHOLDER_COUNT=$((PLACEHOLDER_COUNT + COUNT))
done
if [ "$PLACEHOLDER_COUNT" -gt 0 ]; then
echo " 📝 $PLACEHOLDER_COUNT variable placeholders found" >&2
# Check for unmatched placeholders if base file exists
if [ -n "$BASE_FILE" ]; then
# This is a simplified check - in practice, you'd want more sophisticated matching
echo " 🔍 Cross-referencing placeholders with base file..." >&2
fi
else
echo " ℹ️ No variable placeholders detected" >&2
fi
fi
# 6. Locale-Specific Validation
echo "🌐 Locale-specific validation..." >&2
case "$LOCALE_CODE" in
"ar"*|"he"*|"fa"*)
echo " 🔄 RTL language detected - ensure proper text direction handling" >&2
;;
"zh"*|"ja"*|"ko"*)
echo " 🈴 CJK language detected - ensure proper character encoding" >&2
;;
"en"*)
echo " 🇺🇸 English locale - checking for common issues" >&2
;;
*)
echo " 🌍 Locale: $LOCALE_CODE" >&2
;;
esac
# Check for potential encoding issues (non-ASCII characters)
if [ "$FILE_EXT" = "json" ]; then
NON_ASCII_COUNT=$(grep -P '[^\x00-\x7F]' "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
if [ "$NON_ASCII_COUNT" -gt 0 ]; then
echo " 🔤 $NON_ASCII_COUNT lines contain non-ASCII characters (normal for international content)" >&2
fi
fi
# 7. Multi-file Consistency Check
echo "📁 Checking consistency across locale files..." >&2
LOCALE_FILES=()
while IFS= read -r -d '' file; do
LOCALE_FILES+=("$file")
done < <(find "$LOCALE_DIR" -name "*.$FILE_EXT" -print0 2>/dev/null)
LOCALE_COUNT=${#LOCALE_FILES[@]}
if [ "$LOCALE_COUNT" -gt 1 ]; then
echo " 📊 Found $LOCALE_COUNT locale files in directory" >&2
# Check if all files have similar key counts (within 20% difference)
if [ "$FILE_EXT" = "json" ] && [ "$TOTAL_KEYS" -gt 0 ]; then
INCONSISTENT_FILES=0
for locale_file in "${LOCALE_FILES[@]}"; do
if [ "$locale_file" != "$FILE_PATH" ]; then
OTHER_KEY_COUNT=$(jq -r 'keys | length' "$locale_file" 2>/dev/null || echo "0")
DIFF_PERCENT=$((abs(TOTAL_KEYS - OTHER_KEY_COUNT) * 100 / TOTAL_KEYS))
if [ "$DIFF_PERCENT" -gt 20 ]; then
INCONSISTENT_FILES=$((INCONSISTENT_FILES + 1))
fi
fi
done
if [ "$INCONSISTENT_FILES" -gt 0 ]; then
report_validation "WARNING" "$INCONSISTENT_FILES locale files have significantly different key counts"
else
report_validation "PASS" "All locale files have consistent key counts"
fi
fi
else
echo " ℹ️ Single locale file found" >&2
fi
# 8. Generate Validation Summary
echo "" >&2
echo "📋 i18n Translation Validation Summary:" >&2
echo "=====================================" >&2
echo " 📄 File: $FILE_NAME" >&2
echo " 🌍 Locale: $LOCALE_CODE" >&2
echo " 📊 Format: $FILE_EXT" >&2
[ "$TOTAL_KEYS" -gt 0 ] && echo " 🔑 Total Keys: $TOTAL_KEYS" >&2
echo " ❌ Errors: $ERRORS" >&2
echo " ⚠️ Warnings: $WARNINGS" >&2
echo " 🔍 Missing Keys: $MISSING_KEYS" >&2
echo " 🏷️ Orphaned Keys: $ORPHANED_KEYS" >&2
if [ "$ERRORS" -eq 0 ] && [ "$MISSING_KEYS" -eq 0 ]; then
if [ "$WARNINGS" -eq 0 ] && [ "$ORPHANED_KEYS" -eq 0 ]; then
echo " 🎉 Status: EXCELLENT - Translation file is complete and consistent" >&2
else
echo " ✅ Status: GOOD - Translation is functional with minor issues" >&2
fi
elif [ "$ERRORS" -eq 0 ]; then
echo " ⚠️ Status: INCOMPLETE - Missing translations need attention" >&2
else
echo " ❌ Status: ERRORS - Critical issues must be fixed" >&2
fi
echo "" >&2
echo "💡 i18n Translation Best Practices:" >&2
echo " • Keep translation keys consistent across all locales" >&2
echo " • Use meaningful, hierarchical key names" >&2
echo " • Validate placeholder variables across languages" >&2
echo " • Consider cultural context in translations" >&2
echo " • Test with longer/shorter text in different languages" >&2
echo " • Use proper character encoding (UTF-8)" >&2
# Exit with error if there are critical issues
if [ "$ERRORS" -gt 0 ]; then
echo "⚠️ Translation validation completed with errors" >&2
exit 1
fi
else
# Not a translation file, exit silently
exit 0
fi
exit 0
Complete hook script that performs i18n translation validation
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [ -z "$FILE_PATH" ]; then
exit 0
fi
if [[ "$FILE_PATH" == *locales/*.json ]] || [[ "$FILE_PATH" == *i18n/*.json ]]; then
echo "🌍 i18n Translation Validation for: $(basename "$FILE_PATH")" >&2
if jq empty "$FILE_PATH" 2>/dev/null; then
echo "✅ Valid JSON syntax" >&2
TOTAL_KEYS=$(jq -r 'keys | length' "$FILE_PATH" 2>/dev/null || echo "0")
echo "📊 Total keys: $TOTAL_KEYS" >&2
else
echo "❌ Invalid JSON syntax" >&2
exit 1
fi
fi
exit 0
Complete hook configuration for .claude/settings.json to enable translation validation
{
"hooks": {
"postToolUse": {
"script": "./.claude/hooks/i18n-translation-validator.sh",
"matchers": ["write", "edit"]
}
}
}
Enhanced hook script for missing translation key detection with base file comparison
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
LOCALE_DIR="$(dirname "$FILE_PATH")"
BASE_FILE="$LOCALE_DIR/en.json"
if [[ "$FILE_PATH" == *.json ]] && [ -f "$BASE_FILE" ]; then
BASE_KEYS=$(jq -r 'paths(scalars) as $p | $p | join(".")' "$BASE_FILE" 2>/dev/null | sort)
CURRENT_KEYS=$(jq -r 'paths(scalars) as $p | $p | join(".")' "$FILE_PATH" 2>/dev/null | sort)
MISSING_KEYS=$(comm -23 <(echo "$BASE_KEYS") <(echo "$CURRENT_KEYS") | wc -l | xargs)
if [ "$MISSING_KEYS" -gt 0 ]; then
echo "🔍 MISSING: $MISSING_KEYS translation keys missing from base" >&2
else
echo "✅ All base translation keys are present" >&2
fi
fi
exit 0
Enhanced hook script for variable placeholder validation and empty value detection
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
if [[ "$FILE_PATH" == *.json ]]; then
PLACEHOLDER_PATTERNS=('{{[^}]+}}' '{[^}]+}' '%[a-zA-Z_]+%' '\${[^}]+}' '%[sd]')
PLACEHOLDER_COUNT=0
for pattern in "${PLACEHOLDER_PATTERNS[@]}"; do
COUNT=$(grep -oE "$pattern" "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
PLACEHOLDER_COUNT=$((PLACEHOLDER_COUNT + COUNT))
done
if [ "$PLACEHOLDER_COUNT" -gt 0 ]; then
echo "📝 $PLACEHOLDER_COUNT variable placeholders found" >&2
fi
EMPTY_VALUES=$(jq '[.. | select(type == "string" and length == 0)] | length' "$FILE_PATH" 2>/dev/null || echo "0")
if [ "$EMPTY_VALUES" -gt 0 ]; then
echo "⚠️ $EMPTY_VALUES empty translation values found" >&2
fi
fi
exit 0
Enhanced hook script for locale-specific validation including RTL and CJK language detection
#!/usr/bin/env bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
LOCALE_DIR="$(dirname "$FILE_PATH")"
FILE_NAME="$(basename "$FILE_PATH")"
LOCALE_CODE="${FILE_NAME%.*}"
if [[ "$FILE_PATH" == *.json ]]; then
case "$LOCALE_CODE" in
ar*|he*|fa*)
echo "🔄 RTL language detected - ensure proper text direction handling" >&2
;;
zh*|ja*|ko*)
echo "🈴 CJK language detected - ensure proper character encoding" >&2
;;
esac
NON_ASCII_COUNT=$(grep -P '[^\x00-\x7F]' "$FILE_PATH" 2>/dev/null | wc -l | xargs || echo "0")
if [ "$NON_ASCII_COUNT" -gt 0 ]; then
echo "🔤 $NON_ASCII_COUNT lines contain non-ASCII characters (normal for international content)" >&2
fi
fi
exit 0
Strengthen path detection: [["$FILE_PATH" =~ /(locales|i18n|lang|translations)/]] || exit 0. Check for translation-specific keys like locale/language markers before running full validation. Verify file path patterns match translation file locations. Test with various directory structures.
Use jq to flatten nested keys: jq -r 'paths(scalars) as $p | $p | join(".")' to get dot notation paths. Compare flattened key lists instead of top-level keys only for accurate missing key detection. Verify both files use same nesting structure. Test with deeply nested JSON structures.
Some translations legitimately have locale-specific keys (e.g., currency formats, date patterns). Add whitelist patterns or check key prefixes like 'locale.' to skip cultural adaptation keys from orphan detection. Configure locale-specific key patterns. Document expected locale-specific keys.
Script uses top-level key count but should count leaf nodes: jq '[paths(scalars)] | length'. Ensure both base and current files are counted at same nesting level for accurate percentage. Use consistent key counting method. Verify completeness calculation logic.
Bash doesn't have abs() function. Replace with: DIFF_PERCENT=$(( (TOTAL_KEYS - OTHER_KEY_COUNT) < 0 ? (OTHER_KEY_COUNT - TOTAL_KEYS) : (TOTAL_KEYS - OTHER_KEY_COUNT) )) or use bc: echo "scale=0; sqrt(($A-$B)^2)" | bc. Verify absolute value calculation. Test with various key count differences.
grep only searches file content, not parsed JSON values. Use jq to extract string values: jq -r '.. | strings' | grep -oE pattern. This ensures placeholders in nested values are detected. Verify placeholder detection works with nested structures. Test with various placeholder formats.
Hook falls back to basic validation when msgfmt unavailable. Install gettext package: brew install gettext (macOS), apt-get install gettext (Linux). Verify msgfmt installation: command -v msgfmt. Add msgfmt availability check before PO validation.
Hook should handle missing base file gracefully. Add check: if [ -z "$BASE_FILE" ]; then echo "⚠️ No base file found - completeness check skipped"; fi. Verify base file detection logic. Provide fallback base file suggestions. Document base file requirements.
Show that I18n Translation Validator - Hooks is listed on HeyClaude. Paste this Markdown into your README — it renders the badge and links back to this page.
[](https://heyclau.de/entry/hooks/i18n-translation-validator)I18n Translation Validator - Hooks side by side with 3 alternatives on trust, install, platform support, and disclosed safety notes — all from reviewed registry metadata.
| Field | Validates translation files for missing keys and ensures consistency across different language files. Open dossier | Validates AWS CloudFormation templates for syntax errors and best practices using cfn-lint v1.40.4+ and AWS CLI v2.27.54+. Open dossier | Validates environment variables, checks for required vars, and ensures proper configuration across environments. Open dossier | Validates GraphQL schema files and checks for breaking changes when modified. Open dossier |
|---|---|---|---|---|
| Next steps | ||||
| Trust | ||||
| Review status | ReviewedMaintainer reviewed | ReviewedMaintainer reviewed | ReviewedMaintainer reviewed | ReviewedMaintainer reviewed |
| Package trust | Package not verified | Package not verified | Package not verified | Package not verified |
| Source provenance | Source-backed | Source-backed | Source-backed | Source-backed |
| Submitter | — | — | — | — |
| Install risk | Review first | Review first | Review first | Review first |
| Notes | Safety ✓ Privacy ✓ | Safety ✓ Privacy ✓ | Safety ✓ Privacy ✓ | Safety ✓ Privacy ✓ |
| Brand | — | — | — | |
| Category | hooks | hooks | hooks | hooks |
| Source | source-backed | source-backed | source-backed | source-backed |
| Author | JSONbored | JSONbored | JSONbored | JSONbored |
| Added | 2025-09-19 | 2025-09-19 | 2025-09-16 | 2025-09-19 |
| Platforms | Claude Code | Claude Code | Claude Code | Claude Code |
| Source repo | — | — | — | — |
| Safety notes | ✓Runs automatically on its configured Claude Code hook event and executes shell logic that can read, modify, or delete files in your project (and may run builds, installs, or network calls); review the script and scope it to expected paths before enabling. | ✓Runs automatically on its configured Claude Code hook event and executes shell logic that can read, modify, or delete files in your project (and may run builds, installs, or network calls); review the script and scope it to expected paths before enabling. | ✓Runs automatically on its configured Claude Code hook event and executes shell logic that can read, modify, or delete files in your project (and may run builds, installs, or network calls); review the script and scope it to expected paths before enabling. | ✓Runs automatically on its configured Claude Code hook event and executes shell logic that can read, modify, or delete files in your project (and may run builds, installs, or network calls); review the script and scope it to expected paths before enabling. |
| Privacy notes | ✓Receives Claude Code hook input (session metadata, file paths, and tool output) and reads local project files; review what the script logs or forwards to external services and keep credentials out of its output. | ✓Receives Claude Code hook input (session metadata, file paths, and tool output) and reads local project files; review what the script logs or forwards to external services and keep credentials out of its output. | ✓Receives Claude Code hook input (session metadata, file paths, and tool output) and reads local project files; review what the script logs or forwards to external services and keep credentials out of its output. | ✓Receives Claude Code hook input (session metadata, file paths, and tool output) and reads local project files; review what the script logs or forwards to external services and keep credentials out of its output. |
| Prerequisites | — none listed | — none listed | — none listed | — none listed |
| Install | | | | |
| Config | | | | |
| Citations | ||||
| Claim | Unclaimed | Unclaimed | Unclaimed | Unclaimed |
Source-backed guides for putting this to work.
Loading live community signals…
A short, calm digest of reviewed Claude resources. Unsubscribe any time.