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.
- Overview
- Supported Placeholder Formats
- How It Works
- Validation Rules
- Configuration
- CLI Usage
- TUI Usage
- Common Scenarios
- Best Practices
- Troubleshooting
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}
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}!"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"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}}"
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} アイテム"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"
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}" ✓
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
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
You can customize which placeholder types to validate using configuration or CLI options.
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
}
}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-validationPriority: CLI arguments override configuration file settings.
See Also: Configuration Guide for more details on configuration options.
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 simpleExample 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}"
}
}
}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] │
└─────────────────────────────────────────┘
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}"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}"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 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}}"
Stick to one placeholder format per project:
- .NET projects → .NET format strings
- Android → Printf-style
- JavaScript/React → Template literals
Prefer named placeholders over indexed:
// Good
"Welcome {userName}, you have {messageCount} messages"
// Less clear
"Welcome {0}, you have {1} messages"Add comments in resource files:
<!-- {0} = user name, {1} = item count -->
<data name="UserItems">
<value>User {0} has {1} items</value>
</data>Run validation frequently during development:
# Add to CI/CD pipeline
lrm validate || exit 1Empty translations skip placeholder validation:
- Placeholder mismatches are not reported for empty values
- Empty values are tracked separately in validation results
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.
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.
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.
Example:
Source: "Use %% to escape"
Detected: (no placeholders) ✓ Correct
Solution: Escaped printf placeholders (%%) are correctly ignored.
. 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.]*)\}- 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
- 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)