---
id: hot-reload
title: 'Hot Reload and Caching'
description: 'How Agility picks up edited localization template files without a restart, and the ETag caveat that can hide the change from HTTP clients.'
sidebar_label: 'Hot Reload and Caching'
sidebar_position: 8
tags: ['api', 'localization', 'configuration', '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`](/apis/loc-2-v1/noloc) |

## The ETag Caveat

:::danger 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:

```bash title="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`:

```bash title="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:

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

## Related Topics

- [Requests and responses](/apis/loc-2-v1/requests-and-responses#caching)
- [Template sets and override order](/apis/loc-2-v1/template-sets#debugging-an-override-that-is-not-taking-effect)
- [Debugging with `noloc`](/apis/loc-2-v1/noloc)
