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
| Source | Hot reloaded? |
|---|---|
Strings/*.txt template files | Yes, automatically |
Terminology overrides in the Localization table | Not by the watcher — the admin screens that write those rows trigger a refresh explicitly |
System values (Runtime'MaxUploadMb) | Recomputed on rebuild |
The NoLoc setting | No — read once at startup. See Debugging with noloc |
The ETag Caveat
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-Matchwith the previously issued ETag gets304 Not Modifiedand keeps its old copy, indefinitely. - Intermediate caches similarly hold the old body for up to an hour on
max-agealone.
Working Around It
If you are verifying a terminology change over HTTP:
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:
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:
| # | Cache | Cleared by |
|---|---|---|
| 1 | Per-tag resolution cache inside each localizer | Any add/remove — i.e. every rebuild |
| 2 | The localization manager itself | The file watcher, or an explicit refresh |
| 3 | The static ETag on the HTTP handler | Application restart only |
| 4 | Browser / proxy / CDN caches | max-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.