---
id: nested-tags
title: 'Nested Tag Substitution'
description: 'How {} placeholders inside a localization template are resolved recursively, how numeric term indices work, and how literal braces are escaped.'
sidebar_label: 'Nested Tag Substitution'
sidebar_position: 4
tags: ['api', 'localization', 'concepts']
---


Finding a template is only half of resolution. The template **body** is then
expanded: anything between `{` and `}` is itself a localization tag, resolved
against the same localizer, and its result is spliced in. This is what makes a
handful of templates cover thousands of tags.

## Numeric Terms Are Indices into the Tag Being Resolved

Inside `{}`, a term that is a whole number is replaced by the term at that
zero-based index **of the tag currently being resolved**. Everything else is
taken literally.

```text
Plural={1}s
```

Resolving `Plural'Story`:

1. Terms are `["Plural", "Story"]`.
2. `{1}` → term 1 → `Story`.
3. The nested tag `Story` is resolved. With no template for `Story`, it
   resolves to itself.
4. Result: `Storys`.


`{0}` is the whole first term; an index past the end of the tag expands to the
empty string, which is the mechanism behind optional trailing terms described in
[Tag resolution](/apis/loc-2-v1/tag-resolution#results-are-trimmed-and-cached).

## The Substituted Value Is Resolved, Not Re-Resolved

The value spliced in is the _resolution_ of the nested tag. It is not fed back
through the resolver a second time. This matters when a translated value happens
to collide with another tag:

```text
Add=Add {1}
Story=Backlog Item
Backlog Item=bogus
```

`Add'Story` yields `Add Backlog Item`, not `Add bogus`.


## Literal Tags Nest Too

A nested tag does not have to reference an index. It can name another tag
outright, and that tag's template is expanded in turn — recursively, until no
braces remain.

```text
Add'Story=Add {Story}
Story=Backlog Item
```

`Add'Story` → `Add Backlog Item`.


Indices and literals combine freely:

```text
Plural={1}s
Report2=Report {Plural'1}/{Plural'2}
Plural'Story=Stories
Scope=Project
```

`Report2'Scope'Story` → `Report Projects/Stories`.


## Braces Nest, Innermost First

Expansion runs repeatedly until no `{…}` pairs remain, and the innermost pair is
always matched first. That lets you build a tag out of another tag's result:

```text
Plural={1}s
Plural'AssetType={Plural'{1'2}'x}
AssetType'Story=Backlog Item
```

Resolving `Plural'AssetType'Story`, whose terms are
`["Plural", "AssetType", "Story"]`:

1. Innermost `{1'2}` → `AssetType'Story` → resolves to `Backlog Item`.
2. The body is now `{Plural'Backlog Item'x}`.
3. That tag's stack walk finds `Plural`, whose `{1}` picks term 1 of
   `Plural'Backlog Item'x` — `Backlog Item`.
4. Result: `Backlog Items`.

The literal `'x` term is padding: it keeps `Backlog Item` at index 1 so the
generic `Plural` template still sees it in the right slot.


## Literal Braces Are Doubled

To emit a `{` or `}` in the output, double it. Doubled braces are pulled out
before expansion starts and restored afterwards, so they never participate in
tag matching.

```text
open=open {{ brace
close=close }} brace
match=open {{and}} close
nest=open {{{and}}} close
and=xyz
```

| Tag     | Result             | Why                                                     |
| ------- | ------------------ | -------------------------------------------------------- |
| `open`  | `open { brace`     | `{{` is a literal `{`                                     |
| `close` | `close } brace`    | `}}` is a literal `}`                                     |
| `match` | `open {and} close` | Both braces are literal, so `and` is plain text           |
| `nest`  | `open {xyz} close` | The outer braces are literal; the inner pair is a real tag |


`{}` — an empty pair — resolves the empty tag, which is pre-seeded to the empty
string, so it emits nothing.


## Recursion Is Guarded, Not Forbidden

The localizer keeps a stack of the tags it is currently resolving. If expansion
reaches a tag already on that stack, the tag is emitted **literally** instead of
being resolved again. There is no exception and no stack overflow.

```text
bogus=recursive {0} recursive
```

`bogus` → `recursive bogus recursive`. `{0}` is `bogus`, which is already being
resolved, so it is written out as text.


:::warning Cycles fail silently
A cycle between two templates does not raise an error — it produces output
containing a raw tag. If a `loc-2.v1` response contains an unexpected internal
name embedded mid-sentence, suspect a cycle in the templates rather than a
transport problem.
:::

## Reading a Real Response

Putting it together, this is why the following is a perfectly ordinary result on
an instance where the `Story` asset type has been renamed:

```bash
curl "https://www.example.com/VersionOne/loc-2.v1?[Plural'Story,Command'Delete'Story]"
```

```json
{
  "Plural'Story": "Backlog Items",
  "Command'Delete'Story": "Remove Backlog Items"
}
```

`Command'Delete` is defined as `Remove {Plural'2}`. Term 2 of
`Command'Delete'Story` is `Story`, so the nested tag is `Plural'Story`, which
resolves through its own stack walk. One request, three template lookups, and
none of the strings involved is stored anywhere in that final form.

## Related Topics

- [Tag resolution](/apis/loc-2-v1/tag-resolution) — how the template is found in the first place
- [Culture fallback](/apis/loc-2-v1/culture-fallback) — which localizer nested tags resolve against
- [Template file format](/apis/loc-2-v1/template-file-format) — writing templates that contain braces
