> ## Documentation Index
> Fetch the complete documentation index at: https://docs.astilba.com/llms.txt
> Use this file to discover all available pages before exploring further.

# i18next-v4 adapter

> The native i18next-v4 syntax adapter that lives in the astilba repo and drives the test suite. Not yet published as its own package.

The native i18next-v4 syntax adapter (`@astilba/adapter-i18next-v4`) is the adapter that
plugs i18next into `@astilba/core`. It supplies the `FormatAdapter` (parse/export) and the
`RenderOracle` (vector enumeration + render) that the round-trip driver needs.

<Callout type="warning">
  **Status: in-tree, not yet published.** The adapter lives in the repository at
  [`packages/adapter-i18next-v4`](https://github.com/astilbahq/astilba/tree/main/packages/adapter-i18next-v4)
  and is marked `"private": true` in its `package.json`. It drives the fidelity test suite,
  but it is **not** yet released to npm as its own installable package. The public, installable
  package today is [`@astilba/core`](https://www.npmjs.com/package/@astilba/core).
</Callout>

## What it does

The adapter maps i18next resource bundles to and from the [canonical model](/concepts/canonical-model):

* **`parse`** — flattens nested JSON to dotted keys, rejects ICU values loudly, peels
  plural suffixes language-awarely, resolves base/context per the disambiguation rule, and
  assembles structural `PluralSet`s. Optionally validates `$t()` nesting refs resolve.
* **`exportModel`** — writes the model back to nested JSON, **re-deriving the suffix set from
  the target language** and writing `Value.raw` byte-exact. Output is sorted and may
  renormalise (nested ↔ flat, key ordering) — byte equality is not the fidelity test.
* **`render`** — a faithful, host-independent re-implementation of i18next's native v4
  lookup + interpolation + nesting, used as the round-trip render oracle.
* **`roundTrip` / `assertLossless`** — the ergonomic i18next binding of the generic
  `runRoundTrip` driver.
* **`mask` / `validatePlaceholders`** — the i18next tokenizer pre-bound to core's masking
  utilities.

## Parsing options

`parse` takes a `ParseInput` with a `language`, the `resources` (namespace → nested JSON),
and an optional `options` object:

| Option                     | Default | Effect                                                                          |
| -------------------------- | ------- | ------------------------------------------------------------------------------- |
| `keySeparator`             | `"."`   | i18next key separator. `false` = flat keys, dots are literal.                   |
| `declaredContexts`         | `{}`    | Declared context values per namespace (preferred disambiguation).               |
| `strictNesting`            | `false` | Opt in to dangling `$t()` reference detection.                                  |
| `inferContexts`            | `false` | Opt in to context-set inference (off by default — see note).                    |
| `allowUnsupportedLanguage` | `false` | Parse a language with no vendored CLDR rules as an opaque `other`-only variant. |

<Note>
  `inferContexts` is off by default. On real corpora, shared-prefix keys (like
  `enter_password` / `enter_username`) are indistinguishable from contexts and would be
  mis-inferred and falsely rejected. Without declared contexts, every non-plural leaf is its
  own atomic key — which is still lossless.
</Note>

## The disambiguation rule

i18next overloaded the `_suffix` slot for both plural categories and context values, so the
parser must decide, for a group of sibling keys sharing a parent path, where the base/context
boundary lies. It does this with a three-step rule, language-awarely (the peeling step uses
the vendored CLDR table). When it cannot choose safely — for example a context value that
collides with a CLDR category name — it **rejects loudly** (`AMBIGUOUS_KEY` or
`CONTEXT_CATEGORY_COLLISION`) and asks you to declare the contexts in project config.

## Error codes

The adapter defines these `AstilbaError` codes (each factory names the offending key and
explains the fix):

| Code                         | Raised when                                                       |
| ---------------------------- | ----------------------------------------------------------------- |
| `ICU_NOT_SUPPORTED`          | A value uses ICU `MessageFormat`. Phase 0 is native-only.         |
| `AMBIGUOUS_KEY`              | Set-inference cannot resolve a base/context boundary.             |
| `CONTEXT_CATEGORY_COLLISION` | A context value equals a CLDR category name.                      |
| `DANGLING_NESTING_REF`       | A `$t()` ref resolves to no key (opt-in via `strictNesting`).     |
| `INVALID_RESOURCE`           | e.g. a non-string value, or both cardinal and ordinal on one key. |
| `UNSUPPORTED_LANGUAGE`       | A language with no vendored CLDR rules, not opted into.           |

## Known Phase-0 boundaries

* **One plural *kind* per (key, context).** A key used as both cardinal and ordinal is valid
  i18next but rejected here (`INVALID_RESOURCE`), because the model holds one kind per cell.
* **Vendored CLDR coverage** is the corpus-plan set; other languages reject loudly unless
  `allowUnsupportedLanguage` is set.

## Running it locally

Because the adapter is in the repo, you can use it by cloning and linking:

```bash theme={null}
git clone https://github.com/astilbahq/astilba.git
cd astilba
vp install
vp test run
```

The test suite *is* the fidelity matrix — it exercises `parse` / `exportModel` / `render`
end-to-end through `assertLossless`. See [`CONTRIBUTING.md`](https://github.com/astilbahq/astilba/blob/main/CONTRIBUTING.md)
for the full dev loop.

## Related

* [@astilba/core API](/reference/core-api) — the contracts this adapter implements.
* [Round-trip fidelity](/concepts/fidelity) — the driver it binds to.
