Debugging with noloc
noloc makes the localizer return every tag verbatim instead of translating it.
Suddenly every label in the product tells you its own tag — which is exactly the
information you need in order to override it, translate it, or request it from
loc-2.v1.
The Problem It Solves
Localization is a one-way function. A screen shows Backlog Items; you want to rename it. To do that you need the tag, but the tag is nowhere in the output:
{
"Plural'Story": "Backlog Items",
"Command'Delete'Story": "Remove Backlog Items"
}
Worse, the mapping is not one-to-one. Backlog Items on one screen and Backlog Items on another may come from different tags, and a single visible string may be assembled from several tags via nested substitution. Guessing the tag from the text is not a reliable technique.
With localization disabled, the localizer becomes the identity function and the tags surface directly:
{
"Plural'Story": "Plural'Story",
"Command'Delete'Story": "Command'Delete'Story"
}
Now do the same thing in the product UI and read the tags straight off the screen.
Turning It on in Agility
Agility reads a NoLoc application setting. It ships commented out:
<!-- <add key="NoLoc" value="true" /> -->
Uncomment it, set it to true, and restart the application.
- It is server-wide. Every user of the instance sees raw tags, not just you. Never enable it on production or on a shared environment anyone is relying on.
- It is read once at startup, into a static field. Toggling it requires an application restart in both directions, and it is not affected by hot reload.
Use it on a local instance.
Worked Example: Finding the Tag Behind a Label
Suppose the Delete action on a Story reads Remove Backlog Items and you have been asked to change the word Remove.
1. Confirm what you get normally. (The values below are from an illustrative
instance where the Story asset type has been renamed to Backlog Item; yours
will differ.)
curl -sS "https://dev.example.com/VersionOne/loc-2.v1?[Command'Delete'Story]" \
-H "Accept-Language: en-US"
{ "Command'Delete'Story": "Remove Backlog Items" }
2. Enable NoLoc and restart. Then reload the screen. Every label now
renders as its tag, so the button reads Command'Delete'Story. That is the
answer — but the endpoint will confirm it:
curl -sS "https://dev.example.com/VersionOne/loc-2.v1?[Command'Delete'Story]" \
-H "Accept-Language: en-US"
{ "Command'Delete'Story": "Command'Delete'Story" }
3. Turn NoLoc back off and restart. Now work out which template is
actually responsible, because it is probably not the tag you just found. Ask for
the pieces the tag is built from:
curl -sS "https://dev.example.com/VersionOne/loc-2.v1?[Command'Delete'Story,Plural'Story,Story]" \
-H "Accept-Language: en-US"
{
"Command'Delete'Story": "Remove Backlog Items",
"Plural'Story": "Backlog Items",
"Story": "Backlog Item"
}
Read that top to bottom and the structure falls out:
Command'Delete'Storyhas no template of its own. The term-stack walk lands onCommand'Delete, whose body isRemove {Plural'2}.{Plural'2}splices in term 2 of the original tag —Story— giving the nested tagPlural'Story, which resolves toBacklog Items.
It is tempting to also request Command'Delete and Command to "see the
template". Don't — you will get misleading answers, because a template is
expanded against whatever tag you asked for. Command'Delete has no term 2, so
Remove {Plural'2} becomes Remove {Plural'}, which resolves through the
generic Plural={1}s with an empty term and yields Remove s. Likewise a
generic Command={1} {2} asked for as bare Command yields an empty string.
Neither result is a bug, and neither tells you anything about the tag you actually care about. Probe the real tag, plus the nested tags it references.
So to change Remove, override Command'Delete, not Command'Delete'Story.
Overriding the longer tag would work for Stories and quietly leave every other
asset type alone — almost certainly not what was wanted.
4. Apply the override in the Terminology admin screen or in
<culture>.Company.txt, per
Template sets.
Finding Text That Was Never Localized at All
The technique has a second use, which is what the library README highlights:
with localization off, anything still rendering as normal English prose is
hard-coded. Localized strings degrade to terse internal tags like
Command'Delete'Story; a hard-coded literal looks exactly the same as it did
before.
Scanning a screen with NoLoc on is therefore a fast audit for text that has
been missed by localization — the sentences that still read normally are the
bugs.
Related Mechanisms
NoLoc is not the only switch, and they are easy to confuse.
| Mechanism | Scope | Where |
|---|---|---|
NoLoc application setting | Server-wide, until restart | Agility — Common/_user.config |
V1.Config.noloc | The browser client's own resolver | Agility's browser client |
?noloc query-string parameter | One HTTP request | Only the localization engine's sample application |
?noloc does not work against AgilityThe per-request ?noloc query-string parameter is a feature of the
localization engine's sample application, which swaps in a no-op resolver
for the duration of the request.
Agility does not implement it. Appending ?noloc to a loc-2.v1 URL will be
parsed as a tag and echoed back to you as {"noloc": "noloc"} — which looks
deceptively like it worked.
The client-side V1.Config.noloc short-circuits Agility's browser-side
localization resolver only. It does not affect loc-2.v1 responses, which are
produced on the server.
Related Topics
- Tag resolution — reading the term stack you just uncovered
- Nested tag substitution — why one label needs several tags
- Template sets and override order — where to put the override
- Troubleshooting