---
id: authoring/dialects
title: Dialects
---

The locale model is pure BCP 47 — there is **no separate dialect taxonomy**. A *dialect* is just a locale tag with variant/private-use subtags layered on a base locale: `ar-SY-x-lattakia` = base locale **`ar-SY`** (Arabic, region Syria) + private-use block **`x-lattakia`** (the Lattakia dialect of Syrian Arabic).

## Reading a tag

```ts
import { parseLocale, hasDialect } from '@secundus-studio/stift-core'

parseLocale('ar-SY-x-lattakia')
// { tag: 'ar-SY-x-lattakia', language: 'ar', region: 'SY', variants: ['x-lattakia'], ... }
hasDialect('ar-SY-x-lattakia') // true — carries variant/private-use subtags
hasDialect('ar-SY') // false — plain base locale
```

Variant/private-use subtags are the dialect portion. The `u`/`t` Unicode extensions are **not** dialects — a numbering system is not a dialect, so don't create a locale file for one (e.g. `ar-EG-u-nu-arab` has a numbering system but no dialect).

## Registering dialects

List every tag with translations in `locales.supported`:

```ts
locales: { default: 'en', supported: ['en', 'ar', 'ar-SY', 'ar-SY-x-lattakia'] }
```

A registered dialect is a first-class locale: its own `locales/<tag>/<ns>.json` files, its own compiled packs, its own `RegisteredLocale` union entry. Dialects may be **sparse**: a dialect file can ship only some namespaces; the generator completes the rest from the chain. A dialect that isn't registered still works — it falls back to its most specific registered ancestor (`resolveSupportedLocale('en-GB', ['en', 'ar'])` → `'en'`).

## The inheritance chain

Every supported tag belongs to a chain from most-specific to least-specific, always terminated by the configured **default locale**:

```
ar-SY-x-lattakia  ← most specific (Lattakia dialect of Syrian Arabic)
  └─ ar-SY        ← Syrian Arabic
       └─ ar      ← Arabic (Modern Standard)
            └─ en ← default (the schema)
```

Two things consume this chain: **fallback resolution** (a missing key reads from the nearest registered ancestor) and **translation inheritance** (a value written at a less-specific level applies to every dialect below it unless overridden). Write only what differs per level:

```jsonc
// locales/ar/common.json — Modern Standard Arabic, applies to ar-SY and ar-SY-x-lattakia too
{ "greeting": "مرحباً، {name}!" }

// locales/ar-SY/common.json — Syrian Arabic
{ "greeting": "أهلاً، {name}!" }

// locales/ar-SY-x-lattakia/common.json — Lattakia dialect: override ONLY what differs
{ "greeting": "أهلين، {name}!" }
```

Don't copy the whole file down the chain — the generator inherits.

## Dialect-safe persistence

Persistence and cache ops are dialect-aware — wiping a base locale never wipes its dialects (a naive prefix match would treat `ar` as a prefix of `ar-SY` and delete them too):

```ts
Stift.pack.wipePersisted({ except: 'ar' }) // keeps ar-SY and ar-SY-x-lattakia packs
```

More pack lifecycle: [Offline & Persistence](../runtime/offline-persistence.md).

## Next

- Config `locales.supported` / `default` → [stift.config.ts](../configuration/config-file.md)
- Authoring rules (schema, orphans) → [Messages & Keys](./messages.md)
- Per-locale defaults for params and units inherit down this same chain → [Translating & Values](../react/translating.md), [Units](../react/units.md)
