Placeholder Validation
November 16, 2025 · View on GitHub
LocalizationManager automatically validates that placeholders in translations match the source text. This ensures that format strings, variables, and dynamic content are correctly preserved across all languages.
Table of Contents
- Overview
- Supported Placeholder Formats
- How It Works
- Validation Rules
- Configuration
- CLI Usage
- TUI Usage
- Common Scenarios
- Best Practices
- Troubleshooting
Overview
Placeholder validation is automatically enabled in the lrm validate command and TUI validation panel (F6). It detects and validates four major placeholder formats commonly used in localization:
- .NET Format Strings -
{0},{name},{0:C2} - Printf-Style -
%s,%d,%1$s - ICU MessageFormat -
{count, plural, one {# item} other {# items}} - Template Literals -
${var},${user.name}
Supported Placeholder Formats
.NET Format Strings
Common in C#, .NET applications, and resource files (.resx).
Syntax:
- Indexed:
{0},{1},{2} - Named:
{name},{userName},{count} - With format specifiers:
{0:N2},{price:C2},{date:yyyy-MM-dd}
Examples:
// Source (English)
"Hello {0}!"
"Your balance is {amount:C2}"
"Welcome back, {userName}!"
// Valid Translation (Greek)
"Γεια σου {0}!"
"Το υπόλοιπό σας είναι {amount:C2}"
"Καλώς ήρθες, {userName}!"
Printf-Style Placeholders
Common in C, Java, Android (strings.xml), and iOS localization.
Syntax:
- Type specifiers:
%s(string),%d(integer),%f(float) - Positional:
%1$s,%2$d,%3$f - With width/precision:
%10.2f,%.2f
Examples:
// Source (English)
"You have %d items"
"Hello %s, you have %d new messages"
"Price: %1$s, Quantity: %2$d"
// Valid Translation (Spanish)
"Tienes %d artículos"
"Hola %s, tienes %d mensajes nuevos"
"Precio: %1$s, Cantidad: %2$d"
ICU MessageFormat
Common in complex pluralization and selection scenarios.
Syntax:
- Plurals:
{count, plural, one {# item} other {# items}} - Select:
{gender, select, male {He} female {She} other {They}} - Select ordinal:
{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}
Examples:
// Source (English)
"{count, plural, one {# item} other {# items}}"
"{gender, select, male {He} female {She} other {They}}"
// Valid Translation (French)
"{count, plural, one {# article} other {# articles}}"
"{gender, select, male {Il} female {Elle} other {Ils}}"
Template Literals
Common in JavaScript, TypeScript, and modern frameworks.
Syntax:
- Simple:
${name},${count} - With property access:
${user.name},${cart.total}
Examples:
// Source (English)
"Hello ${name}!"
"User: ${user.firstName} ${user.lastName}"
"Total: ${cart.count} items"
// Valid Translation (Japanese)
"こんにちは ${name}!"
"ユーザー: ${user.firstName} ${user.lastName}"
"合計: ${cart.count} アイテム"
How It Works
The placeholder validator performs these steps:
- Detection - Scans source and translation text for placeholders using regex patterns
- Normalization - Converts placeholders to normalized identifiers for comparison
- Comparison - Validates that translation placeholders match source placeholders
- Error Reporting - Generates detailed error messages for any mismatches
Normalization Examples:
{0}→"0"{name}→"name"%1$s→"1"%s→"s"${user.name}→"user.name"
Validation Rules
✅ Valid Scenarios
1. Exact Match
Source: "Hello {0}!"
Translation: "Bonjour {0}!" ✓
2. Different Order (same placeholders)
Source: "First {0}, Second {1}"
Translation: "Second {1}, First {0}" ✓
3. Named Placeholders
Source: "User {name} has {count} items"
Translation: "L'utilisateur {name} a {count} articles" ✓
4. Format Specifiers (optional in translation)
Source: "Price: {0:C2}"
Translation: "Prix: {0}" ✓
❌ Invalid Scenarios
1. Missing Placeholder
Source: "Hello {0}!"
Translation: "Bonjour!" ✗
Error: Missing placeholder: {0}
2. Extra Placeholder
Source: "Hello!"
Translation: "Bonjour {0}!" ✗
Error: Extra placeholder not in source: {0}
3. Wrong Placeholder Index/Name
Source: "Hello {0}!"
Translation: "Bonjour {1}!" ✗
Error: Missing placeholder: {0}; Extra placeholder not in source: {1}
4. Placeholder Count Mismatch
Source: "Value: {0}"
Translation: "Value: {0} {0}" ✗
Error: Placeholder count mismatch: source has 1, translation has 2
5. Different Placeholder Systems
Source: "Count: {0}"
Translation: "Contagem: %d" ✗
Error: Missing placeholder: {0}; Extra placeholder not in source: %d
Configuration
Default Behavior
By default, LRM validates only .NET format placeholders ({0}, {1}, {name}). This is because:
- LRM is designed for .NET
.resxfiles - .NET placeholders are used in 99% of .resx projects
- This prevents false positives from other placeholder types
Changing Placeholder Types
You can customize which placeholder types to validate using configuration or CLI options.
Option 1: Configuration File (lrm.json)
Create or update lrm.json in your Resources directory:
{
"Validation": {
"PlaceholderTypes": ["dotnet"],
"EnablePlaceholderValidation": true
}
}
Available Types:
"dotnet"- .NET format strings (default)"printf"- Printf-style placeholders"icu"- ICU MessageFormat"template"- Template literals"all"- All types
Examples:
.NET Only (Default):
{
"Validation": {
"PlaceholderTypes": ["dotnet"]
}
}
Multiple Types (e.g., Blazor with JavaScript):
{
"Validation": {
"PlaceholderTypes": ["dotnet", "template"]
}
}
Disable Placeholder Validation:
{
"Validation": {
"EnablePlaceholderValidation": false
}
}
Option 2: CLI Override
Override configuration for a single command:
# Validate specific placeholder types
lrm validate --placeholder-types dotnet,printf
# Validate all placeholder types
lrm validate --placeholder-types all
# Disable placeholder validation entirely
lrm validate --no-placeholder-validation
Priority: CLI arguments override configuration file settings.
See Also: Configuration Guide for more details on configuration options.
CLI Usage
Placeholder validation is automatically enabled in the validate command:
# Validate all resource files
lrm validate
# Validate with specific output format
lrm validate --format json
lrm validate --format simple
Example Output (Table Format):
Scanning: /path/to/resources
✓ Found 3 language(s):
• en (default)
• fr
• de
⚠ Validation found 2 issue(s)
┌─────────────────────────────────────────────┐
│ Placeholder Mismatches (red) │
├──────────┬───────────────┬──────────────────┤
│ Language │ Key │ Error │
├──────────┼───────────────┼──────────────────┤
│ fr │ Welcome │ Missing │
│ │ │ placeholder: {0} │
│ de │ ItemCount │ Extra │
│ │ │ placeholder not │
│ │ │ in source: {1} │
└──────────┴───────────────┴──────────────────┘
Example Output (JSON Format):
{
"isValid": false,
"totalIssues": 2,
"placeholderMismatches": {
"fr": {
"Welcome": "Missing placeholder: {0}"
},
"de": {
"ItemCount": "Extra placeholder not in source: {1}"
}
}
}
TUI Usage
Press F6 in the TUI to validate all resource files. Placeholder errors are displayed in the validation summary:
┌─────────────────────────────────────────┐
│ Validation Results │
├─────────────────────────────────────────┤
│ Found 3 issue(s): │
│ │
│ Missing: 0 │
│ Extra: 0 │
│ Duplicates: 0 │
│ Empty: 1 │
│ Placeholder Mismatches: 2 │
│ │
│ [OK] │
└─────────────────────────────────────────┘
Common Scenarios
Mixed Placeholder Types
You can use multiple placeholder formats in the same string:
// Source
"Hello {0}, you have %d items in ${cart.name}"
// Valid Translation
"Bonjour {0}, vous avez %d articles dans ${cart.name}"
Complex .NET Format Specifiers
Format specifiers are optional in translations:
// Source
"Date: {date:yyyy-MM-dd}, Amount: {amount:N2}"
// Both valid:
"日付: {date:yyyy-MM-dd}, 金額: {amount:N2}"
"日付: {date}, 金額: {amount}"
Printf Positional Arguments
Positional arguments allow reordering:
// Source (English)
"Hello %1$s, you have %2$d items"
// Valid Translation (can reorder)
"Sie haben %2$d Artikel, Hallo %1$s"
ICU Plurals
ICU plural rules vary by language:
// English (two forms)
"{count, plural, one {# item} other {# items}}"
// Polish (three forms)
"{count, plural, one {# przedmiot} few {# przedmioty} other {# przedmiotów}}"
Best Practices
1. Use Consistent Placeholder Systems
Stick to one placeholder format per project:
- .NET projects → .NET format strings
- Android → Printf-style
- JavaScript/React → Template literals
2. Meaningful Named Placeholders
Prefer named placeholders over indexed:
// Good
"Welcome {userName}, you have {messageCount} messages"
// Less clear
"Welcome {0}, you have {1} messages"
3. Document Placeholder Meaning
Add comments in resource files:
<!-- {0} = user name, {1} = item count -->
<data name="UserItems">
<value>User {0} has {1} items</value>
</data>
4. Test Placeholder Validation Early
Run validation frequently during development:
# Add to CI/CD pipeline
lrm validate || exit 1
5. Handle Empty Translations
Empty translations skip placeholder validation:
- Placeholder mismatches are not reported for empty values
- Empty values are tracked separately in validation results
Troubleshooting
Problem: False Positive - "Missing placeholder" for optional format specifiers
Example:
Source: "Price: {amount:C2}"
Translation: "Prix: {amount}"
Status: ✓ Valid (format specifiers are optional)
Solution: This is expected behavior. Format specifiers are optional in translations.
Problem: "Type mismatch" error for similar placeholders
Example:
Source: "Count: {0}"
Translation: "Contagem: %d"
Error: Missing placeholder: {0}; Extra placeholder: %d
Solution: Different placeholder systems ({0} vs %d) are treated as different placeholders. Keep placeholder systems consistent across translations.
Problem: Template literal detected inside .NET string
Example:
Source: "var js = \"${user.name}\";"
Detected: ${user.name} as TemplateLiteral ✓ Correct
Solution: This is correct! Template literals (${...}) are detected separately from .NET format strings ({...}). Both can coexist in the same string.
Problem: Escaped characters not handled
Example:
Source: "Use %% to escape"
Detected: (no placeholders) ✓ Correct
Solution: Escaped printf placeholders (%%) are correctly ignored.
Technical Details
Regex Patterns
. NET Format:
(?<!\$)\{(?<index>\d+)(?::(?<format>[^}]+))?\}
(?<!\$)\{(?<name>[a-zA-Z_]\w*)(?::(?<format>[^}]+))?\}
Printf-Style:
%(?:(?<position>\d+)\$)?(?<flags>[-+0 #]*)(?<width>\d+)?(?:\.(?<precision>\d+))?(?<type>[sdifFeEgGxXocpn%])
ICU MessageFormat:
\{(?<name>\w+),\s*(?<type>plural|select|selectordinal)(?:,\s*(?<format>[^}]+))?\}
Template Literals:
\$\{(?<name>[a-zA-Z_][\w.]*)\}
Performance
- Detection uses compiled regex patterns for optimal performance
- Validation is O(n) where n is the number of placeholders
- Typical validation time: < 1ms per key
- Memory efficient: placeholders are normalized to simple strings
See Also
- Validation Guide - Complete validation features
- Commands Reference - CLI command documentation
- README - Getting started guide
Last Updated: 2025-01-16 Version: 0.7.0 (Phase 2: Variable Validation)