Skip to main content

Hot Reload and Caching

Editing Templates on a Running Instance​

Localization template files can be edited while Agility is running. No restart, no recycle, no rebuild.

A file system watcher monitors the Strings folder for *.txt, on both last-write and file-name changes. When any watched file is created, changed, renamed or deleted, the current localization manager is discarded. The next tag resolution lazily rebuilds it, re-reading every template set from disk.

The rebuild is wholesale: every culture's localizer is rebuilt from scratch, and the per-tag resolution caches go with it. Editing one line in en.Company.txt therefore invalidates French too, which is correct — English is the fallback for everything.

What Is Not Watched​

SourceHot reloaded?
Strings/*.txt template filesYes, automatically
Terminology overrides in the Localization tableNot by the watcher — the admin screens that write those rows trigger a refresh explicitly
System values (Runtime'MaxUploadMb)Recomputed on rebuild
The NoLoc settingNo — read once at startup. See Debugging with noloc

The ETag Caveat​

A stale ETag can hide a template change from clients

loc-2.v1 responses to GET and HEAD carry Cache-Control: public, max-age=3600 and an ETag. The ETag is derived from the newest last-write timestamp in the Strings folder — but it is computed once per application lifetime and memoized in a static field that the reload path does not clear.

So after a hot reload:

  • The server resolves tags correctly and returns the new text to any client that actually asks.
  • But a client that sends If-None-Match with the previously issued ETag gets 304 Not Modified and keeps its old copy, indefinitely.
  • Intermediate caches similarly hold the old body for up to an hour on max-age alone.

Working Around It​

If you are verifying a terminology change over HTTP:

Bypass the conditional request and the shared cache
curl -sS "https://www.example.com/VersionOne/loc-2.v1?[Story,Plural'Story]" \
-H "Accept-Language: en-US" \
-H "Cache-Control: no-cache" \
-H "Pragma: no-cache"

Send no If-None-Match header at all. If you are testing through a browser or a client library that adds one automatically, add a throwaway element to the tag list to change the URL, or use POST, whose responses are marked no-cache:

POST is never conditionally cached
curl -X POST "https://www.example.com/VersionOne/loc-2.v1" \
--data-urlencode '["Story","Plural'"'"'Story"]'

For end users on a deployed instance, an application restart clears the static ETag and is the reliable remedy.

Cache Layers, End to End​

There are four caches between a template file and rendered text. When a change does not appear, walk them in order:

#CacheCleared by
1Per-tag resolution cache inside each localizerAny add/remove — i.e. every rebuild
2The localization manager itselfThe file watcher, or an explicit refresh
3The static ETag on the HTTP handlerApplication restart only
4Browser / proxy / CDN cachesmax-age=3600 expiry, or a cache-busting URL

Caches 1 and 2 are reliable. Caches 3 and 4 are where "I changed the file and nothing happened" almost always lives.

~/loc-2.img is registered to the same handler as ~/loc-2.v1, which gives you a second URL for the identical payload — useful as a crude cache-buster for layer 4.

Vary and per-Culture Caching​

Responses set Vary to include Accept-Language, so a shared cache will not serve French text to an English client. This is correct but easy to defeat: if your client omits Accept-Language entirely, it participates in the no-language cache entry and receives invariant-culture — i.e. English — output. Always send the header explicitly.