Skip to main content

Debugging with noloc

The single most useful trick in this section

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:

Normal output — informative for users, useless for debugging
{
"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:

With localization disabled — every value is its own tag
{
"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:

user.config
<!-- <add key="NoLoc" value="true" /> -->

Uncomment it, set it to true, and restart the application.

Read the two constraints before you use this
  • 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'Story has no template of its own. The term-stack walk lands on Command'Delete, whose body is Remove {Plural'2}.
  • {Plural'2} splices in term 2 of the original tag — Story — giving the nested tag Plural'Story, which resolves to Backlog Items.
Do not probe the prefixes directly

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.

NoLoc is not the only switch, and they are easy to confuse.

MechanismScopeWhere
NoLoc application settingServer-wide, until restartAgility — Common/_user.config
V1.Config.nolocThe browser client's own resolverAgility's browser client
?noloc query-string parameterOne HTTP requestOnly the localization engine's sample application
warning
?noloc does not work against Agility

The 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.