Tag Resolution: the Term Stack
A Tag Is a Stack, Not a Key
A localization tag is a tic-separated stack of terms, ordered from most general to most specific:
AttributeDefinition'Name'Story
└─ term 0 ──┘ └─ 1 ┘ └─ 2 ┘
Terms may not themselves contain a tic ('), because the tic is the separator.
When resolving a tag, the localizer looks for a template registered under the whole tag, then progressively drops terms from the right until it finds one:
Resolving Plural'AssetType'Story
try Plural'AssetType'Story
try Plural'AssetType
try Plural
The first hit wins. This is why a single generic template can answer an unbounded family of tags, and why adding one specific template quietly overrides just that case.
The resolver trims the tag at its last separator, looks again, and repeats until a template answers or nothing is left to trim.
Worked Example
Given these templates:
Plural={1}s
Plural'Story=Stories
| Tag | Template found | Result |
|---|---|---|
Plural'Scope | Plural | Scopes |
Plural'Story | Plural'Story | Stories |
The specific entry shadows the generic one for exactly one tag, and nothing else changes.
A three-term case behaves the same way:
Plural={1}s
resolves Plural'AssetType'Story by falling all the way back to Plural,
whose body then substitutes term 1 — AssetType — giving AssetTypes.
Specificity Beats Position
Adding a longer tag never disturbs shorter ones, and the search always prefers the longest registered prefix:
Plural={1}s
Plural'Story=Stories
Report2=Report {Plural'1}/{Plural'2}
Report2'Member=Report: User by {2}
Scope=Project
| Tag | Result |
|---|---|
Report2'Scope'Story | Report Projects/Stories |
Report2'Member'Story | Report: User by Story |
Report2'Member'Scope | Report: User by Project |
Unmatched Tags Resolve to Themselves
If no prefix of the tag matches anywhere in the chain, the localizer returns the
tag itself, trimmed. It never errors, never returns null, and never omits the
key from a loc-2.v1 response.
{ "bogus": "bogus" }
This has two practical consequences:
- You cannot detect a typo from the response. A misspelled tag looks exactly like a tag that has no translation in the requested culture. If a value comes back identical to the tag you sent, check your spelling before filing a bug.
- English-looking output is the designed default. Agility's tags are already
English words, so an unlocalized instance degrades to readable text rather
than to
???.
The empty tag is a special case that is pre-seeded to map to the empty string,
so "" resolves to "" rather than to itself.
Results Are Trimmed, and Cached
The resolved value is Trim()ed before it is returned, which is what makes
optional trailing terms produce clean output:
A=x {1}
| Tag | Result |
|---|---|
A | x |
A'B | x y |
With no term 1, {1} expands to nothing and the trailing space is trimmed away.
Trim() removes leading and trailing whitespace from the finished string. A
missing substitution in the middle of a template leaves a double space behind:
x {1} z with no term 1 yields x z. Put optional terms at the end.
Each localizer memoizes resolved tags. The cache is cleared whenever a template is added or removed, so it is invisible to callers — but it does mean the first request after a template change is fractionally slower.
The Search Order Across the Fallback Chain
The trim-the-rightmost-term walk runs to completion within one culture's template map before the fallback culture is consulted at all. The fallback is then handed the original, full tag and repeats the whole walk itself.
Resolving Plural'AssetType'Story on fr-FR:
fr-FR: Plural'AssetType'Story -> Plural'AssetType -> Plural
fr: Plural'AssetType'Story -> Plural'AssetType -> Plural
en: Plural'AssetType'Story -> Plural'AssetType -> Plural
The two dimensions do not interleave. A generic Plural in fr-FR beats a
specific Plural'AssetType'Story in en. Specificity within a culture always
outranks specificity in a fallback culture.
This trips people up when adding translations: putting a very specific entry in the default English set will not help if the target culture already has a broad generic entry.
Related Topics
- Nested tag substitution — what happens once a template is found
- Culture fallback — how the fallback chain is built
- Template sets and override order — how templates get into a culture's map