---
id: template-file-format
title: 'Template File Format'
description: 'The text file format behind Agility localization template sets: naming, UTF-8 encoding, comments, escaping, multi-line values and deletions.'
sidebar_label: 'Template File Format'
sidebar_position: 7
tags: ['api', 'localization', 'configuration', 'reference']
---


## File Naming

```text
<culture>.<setname>.txt
```

Both parts are matched exactly against the culture name and the set name, so
`fr-FR.Base.txt` serves the `fr-FR` culture and `fr.Base.txt` serves `fr`. A
missing file is not an error — the set simply contributes nothing for that
culture.

In Agility these live in the web application's `Strings` folder, alongside the
shipped `en.Base.txt`, `en.Scrum.txt` and friends. The set names Agility looks
for are listed in
[Template sets](/apis/loc-2-v1/template-sets#the-set-stack-agility-actually-uses).


## Encoding

**Files must be UTF-8.** A byte-order mark is optional, but any other encoding
is a hard failure — the reader is constructed with throw-on-invalid-bytes, so a
Latin-1 file raises a decoder exception rather than silently mangling accented
characters.

| Encoding | Result |
| -------- | ------ |
| UTF-8 without BOM | Works |
| UTF-8 with BOM | Works |
| ANSI / code page | Throws `DecoderFallbackException` |


## Syntax

One template per line, `tag=value`.

```text title="Every rule in one file"
;Comment=Lines starting with semicolon are ignored
LongText=First line_
Second line
Plural={1}s
Plural'Story=Stories
Command={1} {2}
Fixed=Never Change
This==has==equal==signs=Equal-sign free
Deleted=
```

### Comments

A line whose **first character** is `;` is skipped entirely. There are no
end-of-line comments — a `;` anywhere else is ordinary text.

Because the sample line above is a comment, the tag `Comment` is never defined,
and resolving it returns `Comment`.


### The Separator

The tag ends at the first `=` that is not doubled. Everything after it, to the
end of the line, is the value — including further `=` characters, which need no
escaping on the value side.

A line with no `=` at all is skipped, which is how blank lines are tolerated.

### Escaping `=` In a Tag

Double it. `==` in the tag portion means a literal `=`.

```text
This==has==equal==signs=Equal-sign free
```

defines the tag `This=has=equal=signs`.


### Multi-Line Values

A trailing underscore continues the value onto the next line, and the line break
is preserved in the output.

```text
LongText=First line_
Second line
```

resolves to:

```text
First line
Second line
```

Continuations chain: each line that ends with `_` pulls in one more. Note the
consequence — a value cannot end with a literal underscore.


### Empty Values Delete

```text
Plural'Story=
```

removes `Plural'Story` from this culture's map rather than setting it to an
empty string. This is covered in detail, with the reason it matters, in
[Template sets](/apis/loc-2-v1/template-sets#an-empty-value-deletes-it-does-not-blank).

### Braces in Values

`{` and `}` introduce [nested tags](/apis/loc-2-v1/nested-tags). To emit a
literal brace, double it: `{{` and `}}`.

### Whitespace

Neither the tag nor the value is trimmed at parse time, so leading whitespace
before a tag becomes part of the tag and will not match. Trailing whitespace on
a value is harmless because resolution trims the finished string — but only at
its edges. Keep lines flush-left.

## Ordering Within a File

Order does not matter for lookup: the file is loaded into a map, and
[tag resolution](/apis/loc-2-v1/tag-resolution) is driven by tag specificity,
not file position. Order only matters between *files*, per
[the set stack](/apis/loc-2-v1/template-sets#the-stacking-rule). A duplicate tag
later in the same file simply wins.

## Templates That Do Not Come from Files

Two of Agility's template sources are not files at all and therefore have no
file format:

- **Terminology overrides** are rows in the `Localization` database table
  (`Language`, `Key`, `Value`), filtered to the requested culture. Rows with an
  empty `Value` are excluded by the query, so the delete-by-empty-value trick is
  not available there.
- **System values** are computed in code — the only one is
  `Runtime'MaxUploadMb`.

They obey the same override rules as files; only the storage differs.


## Related Topics

- [Template sets and override order](/apis/loc-2-v1/template-sets)
- [Hot reload and caching](/apis/loc-2-v1/hot-reload) — making an edited file take effect
- [Nested tag substitution](/apis/loc-2-v1/nested-tags)
