# Fix i18next interpolation failing on an unescaped brace

## TL;DR

A stray curly brace in the translation string breaks the interpolation parser, so balance it or replace the literal brace. Grep the locale JSON for the key from the error, then fix the braces so every opener has a partner. The common case is a literal { in copy like a math expression that the parser reads as the start of a variable.

## The error

```text
i18next: interpolation: unescaped brace in translation value
key: "items_count", value: "{count} items ({max} max"
```

## Fix it

### Step 1: Find the key named in the error

```bash
grep -rn "items_count" public/locales/en/ | head -5
```

Expected: You see the translation string with the unbalanced brace.

### Step 2: Balance or remove the literal brace

```bash
node -e "const s='{count} items ({max} max'; const o=(s.match(/\{/g)||[]).length, c=(s.match(/\}/g)||[]).length; console.log('open:',o,'close:',c)"
```

Expected: Open and close counts match after your edit.

### Step 3: Reword so literal braces are not needed

```bash
node -e "console.log('example fix: change \"({max} max\" to \"max {max}\" or drop the parens')"
```

Expected: The string parses and the variable still interpolates.

### Step 4: Lint every locale file for unbalanced braces

```bash
node -e "const fs=require('fs'); const bad=[]; for (const f of fs.readdirSync('public/locales/en')) { const j=JSON.parse(fs.readFileSync('public/locales/en/'+f)); const walk=o=>Object.values(o).forEach(v=>typeof v==='string'?( ((v.match(/\{/g)||[]).length!==(v.match(/\}/g)||[]).length && bad.push(v)) : typeof v==='object'&&walk(v)); walk(j);} console.log(bad.length?bad:['all balanced'])"
```

Expected: The lint prints all balanced, or lists every string that still needs a fix.

## When to use this

- The console names a key with an interpolation or unescaped brace complaint
- Copy contains literal braces like math, code samples, or emoticons

## When NOT to use this

- The variable value itself contains braces, escape those in the value not the message
- You use ICU message format, that parser has its own brace rules

## Tool and version compatibility

- i18next v21 through v25 with default interpolation settings
- Does not apply to ICU-format messages

## Variant phrasings

### interpolation value prints as [object Object]

Different problem. You passed an object where a string was expected, stringify it or pick the field in the translation call.

### nested interpolation with format specifiers

Keep the format inside the braces, like count with a number format, and keep the outer braces balanced.

## Why it happens

i18next scans translation strings for its interpolation delimiters, double curly braces by default. A single literal brace makes the scanner think a variable started but never closed, so it aborts the whole lookup and logs the warning.

## Edge cases

- Translated strings from vendors are the usual source, lint every locale not just English
- If copy truly needs braces, put the braced text in the interpolation value instead of the message
- Custom interpolation delimiters change what counts as unbalanced, check your prefix and suffix settings

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_EZ6Q0jo2Gzj8PTTdJb0FiA
