first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
# @suaveplan/locale-data - Static ISO 3166-1 country data and ISO 639-1 language data with typed lookup functions, OTel instrumentation, and Zod schemas
## Summary
Static ISO 3166-1 country data and ISO 639-1 language data with typed lookup functions,
OTel instrumentation, and Zod schemas.
<!-- begin:auto-docs -->
## Documentation tree
- [countries](./src/countries/countries.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [countries.data](./src/countries/countries.data.md) — Contains the static ISO 3166-1 alpha-2 country dataset as a single frozen TypeScript constant (`COUNTRIES_DATA`). This…
- [countries](./src/countries/countries.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [errors](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [locale-data-error](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [languages.data](./src/languages/languages.data.md) — Contains the static ISO 639-1 language dataset as two frozen TypeScript constants: `LANGUAGES_DATA` (130+ language entr…
- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
<!-- end:auto-docs --> - [countries.data](./src/countries/countries.data.md) — Contains the static ISO 3166-1 alpha-2 country dataset as a single frozen TypeScript constant (`COUNTRIES_DATA`). This…
- [countries](./src/countries/countries.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [locale-data-error](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages.data](./src/languages/languages.data.md) — Contains the static ISO 639-1 language dataset as two frozen TypeScript constants: `LANGUAGES_DATA` (130+ language entr…
- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [countries.data](./src/countries/countries.data.md) — Contains the static ISO 3166-1 alpha-2 country dataset as a single frozen TypeScript constant (`COUNTRIES_DATA`). This…
- [countries](./src/countries/countries.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [locale-data-error](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages.data](./src/languages/languages.data.md) — Contains the static ISO 639-1 language dataset as two frozen TypeScript constants: `LANGUAGES_DATA` (130+ language entr…
- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [countries.data](./src/countries/countries.data.md) — Contains the static ISO 3166-1 alpha-2 country dataset as a single frozen TypeScript constant (`COUNTRIES_DATA`). This…
- [countries](./src/countries/countries.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [locale-data-error](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages.data](./src/languages/languages.data.md) — Contains the static ISO 639-1 language dataset as two frozen TypeScript constants: `LANGUAGES_DATA` (130+ language entr…
- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_- [countries.data](./src/countries/countries.data.md) — Contains the static ISO 3166-1 alpha-2 country dataset as a single frozen TypeScript constant (`COUNTRIES_DATA`). This…
- [countries](./src/countries/countries.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [locale-data-error](./src/errors/locale-data-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_ - [languages.data](./src/languages/languages.data.md) — Contains the static ISO 639-1 language dataset as two frozen TypeScript constants: `LANGUAGES_DATA` (130+ language entr…
- [languages](./src/languages/languages.md) — <!-- begin:auto-docs --> _(no submodules)_- [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_ - [types](./src/types/types.md) — <!-- begin:auto-docs --> _(no submodules)_
## Install
```bash
bun add @suaveplan/locale-data
```
## Quick Start
```ts
import { getCountry, getLanguage, isRtlLanguage } from "@suaveplan/locale-data";
// Country lookup
const us = getCountry("US");
console.log(us.name); // "United States"
console.log(us.phone); // "1"
console.log(us.emoji); // "🇺🇸"
// Language lookup
const ar = getLanguage("ar");
console.log(ar.name); // "Arabic"
console.log(ar.native); // "العربية"
// RTL detection
console.log(isRtlLanguage("ar")); // true
console.log(isRtlLanguage("ar-SA")); // true (BCP-47 tag)
console.log(isRtlLanguage("en")); // false
```
## Modules
| Subpath | Description |
|---|---|
| `@suaveplan/locale-data` | Root barrel — re-exports all public symbols |
| `@suaveplan/locale-data/countries` | Country lookups + Zod schemas |
| `@suaveplan/locale-data/languages` | Language lookups + RTL detection + Zod schemas |
| `@suaveplan/locale-data/errors` | `LocaleDataError` + `LOCALE_DATA_CODES` |
## Modules Documentation
- [countries.md](src/countries/countries.md) — `getCountry`, `getAllCountries`, `getCountriesByContinent`, `getCountriesByCurrency`
- [countries.data.md](src/countries/countries.data.md) — Static ISO 3166-1 dataset (209 entries)
- [languages.md](src/languages/languages.md) — `getLanguage`, `getLanguages`, `getRtlLanguages`, `isRtlLanguage`
- [languages.data.md](src/languages/languages.data.md) — Static ISO 639-1 dataset + RTL codes
- [locale-data-error.md](src/errors/locale-data-error.md) — `LocaleDataError`, `LOCALE_DATA_CODES`
- [types.md](src/types/types.md) — `CountryItemSchema`, `LanguageItemSchema`
## API Overview
### Countries
```ts
import {
getCountry, // O(1) lookup by ISO 3166-1 alpha-2 code
getAllCountries, // Returns full 209-entry Record<string, CountryItem>
getCountriesByContinent, // Filter by continent code ("EU", "AS", "AF", etc.)
getCountriesByCurrency, // Filter by ISO 4217 currency code (e.g. "EUR")
} from "@suaveplan/locale-data/countries";
```
### Languages
```ts
import {
getLanguage, // O(1) lookup by ISO 639-1 code
getLanguages, // Returns full 130+ entry Record<string, LanguageItem>
getRtlLanguages, // Returns list of RTL language codes
isRtlLanguage, // BCP-47 aware RTL detection
} from "@suaveplan/locale-data/languages";
```
### Error Handling
```ts
import { LocaleDataError, LOCALE_DATA_CODES } from "@suaveplan/locale-data/errors";
try {
getCountry("ZZ");
} catch (err) {
if (err instanceof LocaleDataError) {
console.log(err.code); // "LOCALE_DATA/COUNTRY.NOT_FOUND"
}
}
```
## OTel Instrumentation
The package emits two OTel counters:
| Metric | Attributes |
|---|---|
| `locale_data.country.lookups` | `result: "found" \| "not_found"` |
| `locale_data.language.lookups` | `result: "found" \| "not_found"` |

View File

@@ -0,0 +1,69 @@
# countries.data
## Purpose
Contains the static ISO 3166-1 alpha-2 country dataset as a single frozen TypeScript constant
(`COUNTRIES_DATA`). This file holds 209 country entries sourced from the ISO standard and is
intentionally separated from the lookup logic in `countries.ts` to keep both files under the
300-line hard limit enforced by the project style guide. The data here serves as the single source
of truth for all country lookups, filtering, and validation in the `@suaveplan/locale-data` package.
## Features
- **209 ISO 3166-1 alpha-2 entries** — includes all UN member states and selected territories.
- **9 fields per entry** — `name` (English), `native` (local script), `phone` (dialling code),
`continent` (2-letter code), `capital`, `currency` (ISO 4217, comma-separated for multi-currency
countries), `languages` (array of ISO 639-1 codes), `emoji` (flag emoji), `emojiU` (Unicode
code-point notation).
- **Typed as `Record<string, CountryItem>`** — ensures each entry satisfies the `CountryItem`
shape defined in the types module.
- **Multi-currency support** — countries like Switzerland (`CHE,CHF,CHW`), Bolivia (`BOB,BOV`),
and Cuba (`CUC,CUP`) store all currencies as a comma-separated string, consistent with how
`getCountriesByCurrency` splits and matches entries.
- **Continent codes** — `EU` (Europe), `AS` (Asia), `AF` (Africa), `NA` (North America),
`SA` (South America), `OC` (Oceania), `AN` (Antarctica).
## Usage: Basic Example
```ts
// Imported indirectly via the countries module:
import { getAllCountries } from "@suaveplan/locale-data/countries";
const data = getAllCountries(); // returns COUNTRIES_DATA reference
console.log(data["US"].name); // "United States"
```
## Usage: Advanced Example
```ts
// The data object is consumed by the lookup functions in countries.ts:
import { getCountry, getCountriesByCurrency } from "@suaveplan/locale-data/countries";
// Direct lookup (O(1))
const ch = getCountry("CH");
console.log(ch.currency); // "CHE,CHF,CHW"
// Filter by currency (splits on comma internally)
const chfCountries = getCountriesByCurrency("CHF");
console.log(chfCountries.map((c) => c.name)); // includes "Switzerland"
```
## API Reference
### `COUNTRIES_DATA`
```ts
const COUNTRIES_DATA: Record<string, CountryItem>
```
A frozen constant record mapping ISO 3166-1 alpha-2 codes to `CountryItem` objects. Keys are
uppercase two-letter codes (e.g. `"US"`, `"DE"`, `"JP"`). Do not import or mutate this directly
from outside the countries module — use the public lookup functions instead.
## Implementation Notes
The data file is excluded from test coverage measurement because it contains no executable logic —
only a plain `export const` declaration. All entries were derived from the ISO standard data in the
reference project at `packages/universal/utils/src/countries/countries.ts` and include the same
209 keys present in the `countries` named export. The file deliberately omits entries not present
in the main `countries` object (such as `AQ`, `AG`, `AS`, `BL`, etc.) to stay consistent with the
reference dataset.

View File

@@ -0,0 +1,120 @@
# countries
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides typed lookup functions over the static ISO 3166-1 alpha-2 country dataset (209 entries).
The module is designed for high-frequency read access — data is held in a single frozen object
(`COUNTRIES_DATA`) and all lookups run in O(1) or O(n) time with zero I/O. Every `getCountry`
call emits an OTel counter increment, enabling production observability for cache-hit analysis
and anomaly detection on invalid code injection attempts.
## Features
- **`getCountry(code)`** — O(1) lookup by ISO 3166-1 alpha-2 code; throws a typed
`LocaleDataError` for unknown codes.
- **`getAllCountries()`** — Returns the full `Record<string, CountryItem>` reference; O(1),
no copy allocation.
- **`getCountriesByContinent(continent)`** — Filters by two-letter continent code (`"EU"`, `"AS"`,
`"AF"`, `"NA"`, `"SA"`, `"OC"`, `"AN"`); returns empty array for unknown codes.
- **`getCountriesByCurrency(currency)`** — Matches against comma-separated ISO 4217 currency codes
in each entry (e.g. `"CHE,CHF,CHW"`); returns empty array for unknown currencies.
- **OTel instrumentation** — `locale_data.country.lookups` counter with `result: "found" | "not_found"`.
- **Zod schemas** — `CountryCodeSchema` (2-char uppercase) and re-exported `CountryItemSchema`.
## Usage: Basic Example
```ts
import { getCountry, getAllCountries } from "@suaveplan/locale-data/countries";
// Look up a single country
const us = getCountry("US");
console.log(us.name); // "United States"
console.log(us.phone); // "1"
console.log(us.emoji); // "🇺🇸"
// Get all countries
const all = getAllCountries();
console.log(Object.keys(all).length); // 209
```
## Usage: Advanced Example
```ts
import {
getCountry,
getCountriesByContinent,
getCountriesByCurrency,
} from "@suaveplan/locale-data/countries";
import { LocaleDataError, LOCALE_DATA_CODES } from "@suaveplan/locale-data/errors";
// Error handling
try {
const country = getCountry("ZZ");
} catch (err) {
if (err instanceof LocaleDataError && err.code === LOCALE_DATA_CODES["COUNTRY.NOT_FOUND"]) {
console.warn("Unknown country code");
}
}
// Filter by continent
const europeanCountries = getCountriesByContinent("EU");
console.log(europeanCountries.length); // ~40
// Find all Eurozone members (use single-currency entries + multi-currency)
const eurozoneMembers = getCountriesByCurrency("EUR");
console.log(eurozoneMembers.map((c) => c.name));
// Group by continent
import { getAllCountries } from "@suaveplan/locale-data/countries";
const byContinent = Object.entries(getAllCountries()).reduce<Record<string, string[]>>(
(acc, [code, c]) => {
(acc[c.continent] ??= []).push(code);
return acc;
},
{}
);
```
## API Reference
### `getCountry(code: string): CountryItem`
Look up a country by its ISO 3166-1 alpha-2 code.
- **Parameters:** `code` — case-sensitive two-letter code (e.g. `"US"`, `"DE"`).
- **Returns:** The matching `CountryItem`.
- **Throws:** `LocaleDataError` (`LOCALE_DATA/COUNTRY.NOT_FOUND`) when the code is not found.
- **OTel:** Increments `locale_data.country.lookups` with `result: "found"` or `"not_found"`.
### `getAllCountries(): Record<string, CountryItem>`
Return the full 209-entry country map.
- **Returns:** A `Record<string, CountryItem>` — the same reference each call.
- **Note:** Do not mutate the returned object.
### `getCountriesByContinent(continent: string): CountryItem[]`
Filter countries by continent code.
- **Parameters:** `continent` — `"EU"` | `"AS"` | `"AF"` | `"NA"` | `"SA"` | `"OC"` | `"AN"`.
- **Returns:** Array of matching `CountryItem` values; empty array for unknown codes.
### `getCountriesByCurrency(currency: string): CountryItem[]`
Filter countries by ISO 4217 currency code.
- **Parameters:** `currency` — e.g. `"EUR"`, `"USD"`, `"CHF"`.
- **Returns:** Array of matching `CountryItem` values; handles comma-separated multi-currency
entries; empty array if no match.
## Implementation Notes
Data is stored in `countries.data.ts` as a plain `Record<string, CountryItem>` const to keep the
implementation file under the 300-line hard limit. The data file is excluded from coverage
measurement because it contains no executable logic — only `export const`. The `getCountriesByCurrency`
function splits on commas to support countries with composite currency fields like Switzerland
(`"CHE,CHF,CHW"`), Bolivia (`"BOB,BOV"`), and Cuba (`"CUC,CUP"`).

View File

@@ -0,0 +1,100 @@
# locale-data-error
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the typed error class and stable code registry for `@suaveplan/locale-data`. All errors
thrown by this package extend `SuaveplanError`, ensuring consistent structured error handling across
the project — with machine-readable codes, cause-chain propagation, and OTel span-event support
inherited from `@suaveplan/error`.
## Features
- **Stable code registry** via `defineCodes("LOCALE_DATA", [...])` — codes are namespaced under
`LOCALE_DATA` and never change once published, ensuring forward compatibility for error consumers.
- **Two error codes**: `COUNTRY.NOT_FOUND` for unknown ISO 3166-1 alpha-2 codes and
`LANGUAGE.NOT_FOUND` for unknown ISO 639-1 language codes.
- **Full `SuaveplanError` inheritance**: cause chaining, redaction, wire-format serialisation,
and OTel span-event emission are all available on every `LocaleDataError` instance.
- **Typed generic**: `LocaleDataError` is parameterised on `LocaleDataCode`, so TypeScript narrows
`.code` to the union of defined codes in `catch` blocks.
## Usage: Basic Example
```ts
import { LocaleDataError, LOCALE_DATA_CODES } from "@suaveplan/locale-data/errors";
throw new LocaleDataError(
LOCALE_DATA_CODES["COUNTRY.NOT_FOUND"],
"No country found for code: ZZ"
);
```
## Usage: Advanced Example
```ts
import { LocaleDataError, LOCALE_DATA_CODES } from "@suaveplan/locale-data/errors";
import { isSuaveplanError } from "@suaveplan/error";
try {
const country = getCountry("ZZ");
} catch (err) {
if (isSuaveplanError(err) && err.code === LOCALE_DATA_CODES["COUNTRY.NOT_FOUND"]) {
console.error("Country lookup failed:", err.message);
// err.cause, err.context, err.toJSON() all available
}
throw err;
}
```
## API Reference
### `LOCALE_DATA_CODES`
```ts
const LOCALE_DATA_CODES: {
"COUNTRY.NOT_FOUND": "LOCALE_DATA/COUNTRY.NOT_FOUND";
"LANGUAGE.NOT_FOUND": "LOCALE_DATA/LANGUAGE.NOT_FOUND";
}
```
A frozen record produced by `defineCodes`. Use these values as the `code` argument to
`LocaleDataError` and for equality checks in catch blocks.
### `LocaleDataCode`
```ts
type LocaleDataCode = "LOCALE_DATA/COUNTRY.NOT_FOUND" | "LOCALE_DATA/LANGUAGE.NOT_FOUND";
```
The union type of all valid error codes for this package.
### `LocaleDataError`
```ts
class LocaleDataError extends SuaveplanError<LocaleDataCode> {
public constructor(
code: LocaleDataCode,
message: string,
options?: SuaveplanErrorOptions
);
}
```
**Parameters:**
- `code` — one of `LOCALE_DATA_CODES`; narrows the `.code` property type.
- `message` — human-readable description, suitable for logging.
- `options` — optional `{ cause?: unknown; context?: Record<string, unknown> }`.
**Throws:** Never (is the thrown value itself).
## Implementation Notes
The constructor is otherwise a no-op pass-through to `SuaveplanError` — the
`// biome-ignore lint/complexity/noUselessConstructor` comment is required because Biome would flag
the empty constructor. We keep it explicitly `public` to widen the inherited `protected` visibility
and to document the intended API surface. Error code strings are of the form
`"LOCALE_DATA/<CATEGORY>.<KEY>"` as produced by `defineCodes`.

View File

@@ -0,0 +1,78 @@
# languages.data
## Purpose
Contains the static ISO 639-1 language dataset as two frozen TypeScript constants: `LANGUAGES_DATA`
(130+ language entries) and `RTL_LANGUAGES` (the list of right-to-left script codes). This file is
intentionally separated from the lookup logic in `languages.ts` to keep both files under the
300-line hard limit. The data serves as the single source of truth for all language lookups and
RTL detection in the `@suaveplan/locale-data` package.
## Features
- **130+ ISO 639-1 language entries** — covers the full set of two-letter language codes in the
ISO standard, from Afar (`aa`) to Zulu (`zu`).
- **Two fields per entry** — `name` (English name) and `native` (name in the language's own script,
e.g. `"Deutsch"` for German, `"العربية"` for Arabic).
- **`RTL_LANGUAGES` constant** — readonly array of 15 ISO 639-1 codes whose primary script is
right-to-left: `ar`, `az`, `ba`, `dv`, `fa`, `he`, `iu`, `ks`, `ku`, `ps`, `sd`, `sr`, `tg`,
`ur`, `yi`.
- **Typed as `Record<string, LanguageItem>`** — ensures each entry satisfies the `LanguageItem`
shape defined in the types module.
- **Unicode-safe** — native names include characters from Arabic, Hebrew, Cyrillic, Devanagari,
Georgian, Thai, Chinese, Japanese, Korean, and other scripts.
## Usage: Basic Example
```ts
// Imported indirectly via the languages module:
import { getLanguages } from "@suaveplan/locale-data/languages";
const all = getLanguages(); // returns LANGUAGES_DATA reference
console.log(all["ja"].name); // "Japanese"
console.log(all["ja"].native); // "日本語"
```
## Usage: Advanced Example
```ts
import { getRtlLanguages, isRtlLanguage } from "@suaveplan/locale-data/languages";
// Get all RTL codes
const rtlCodes = getRtlLanguages();
console.log(rtlCodes); // ["ar", "az", "ba", "dv", "fa", "he", "iu", "ks", "ku", "ps", "sd", "sr", "tg", "ur", "yi"]
// BCP-47 tag detection
console.log(isRtlLanguage("ar-EG")); // true (Arabic, Egypt)
console.log(isRtlLanguage("zh-TW")); // false (Traditional Chinese)
```
## API Reference
### `LANGUAGES_DATA`
```ts
const LANGUAGES_DATA: Record<string, LanguageItem>
```
A frozen constant record mapping ISO 639-1 two-letter codes to `LanguageItem` objects.
Do not import or mutate this directly from outside the languages module — use the public lookup
functions instead.
### `RTL_LANGUAGES`
```ts
const RTL_LANGUAGES: readonly string[]
```
A readonly array of ISO 639-1 codes for right-to-left scripts. Used by `getRtlLanguages()` and
`isRtlLanguage()`. Do not mutate.
## Implementation Notes
The data file is excluded from test coverage measurement because it contains no executable logic —
only plain `export const` declarations. The RTL language list was derived from the reference
implementation at `packages/universal/utils/src/languages/languages.ts` in the reference project.
The list includes `az` (Azerbaijani) and `sr` (Serbian) because both languages historically use
RTL scripts in some contexts, consistent with the reference data. The `isRtlLanguage` function
imports `RTL_LANGUAGES` as a readonly array and uses `Array.prototype.includes` for O(n) membership
testing — with only 15 RTL codes, this is negligible overhead compared to a `Set` construction.

View File

@@ -0,0 +1,119 @@
# languages
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Provides typed lookup functions over the static ISO 639-1 language dataset (130+ entries),
including right-to-left (RTL) detection for Arabic, Hebrew, Persian, Urdu, and other scripts.
The module is designed for zero-allocation read access — data is held in a frozen constant and
all lookups return in O(1) or O(n) time with no I/O. Every `getLanguage` call emits an OTel
counter increment, enabling observability of lookup patterns and invalid code injection attempts
in production environments.
## Features
- **`getLanguages()`** — Returns the full `Record<string, LanguageItem>` reference in O(1); no
copy allocation.
- **`getLanguage(code)`** — O(1) lookup by ISO 639-1 code; throws a typed `LocaleDataError` for
unknown codes.
- **`getRtlLanguages()`** — Returns the readonly list of RTL language codes (15 codes).
- **`isRtlLanguage(code)`** — Returns `true` / `false` for any language code or BCP-47 tag;
strips region and script subtags before checking.
- **OTel instrumentation** — `locale_data.language.lookups` counter with `result: "found" | "not_found"`.
- **Zod schemas** — `LanguageCodeSchema` (2–3 char lowercase) and re-exported `LanguageItemSchema`.
## Usage: Basic Example
```ts
import { getLanguage, getLanguages } from "@suaveplan/locale-data/languages";
// Look up a single language
const fr = getLanguage("fr");
console.log(fr.name); // "French"
console.log(fr.native); // "Français"
// Get all languages
const all = getLanguages();
console.log(Object.keys(all).length); // 130+
```
## Usage: Advanced Example
```ts
import {
getLanguage,
getRtlLanguages,
isRtlLanguage,
} from "@suaveplan/locale-data/languages";
import { LocaleDataError, LOCALE_DATA_CODES } from "@suaveplan/locale-data/errors";
// Error handling for unknown codes
try {
const lang = getLanguage("xx");
} catch (err) {
if (err instanceof LocaleDataError && err.code === LOCALE_DATA_CODES["LANGUAGE.NOT_FOUND"]) {
console.warn("Unknown language code");
}
}
// RTL detection with BCP-47 tags
const isArabicRtl = isRtlLanguage("ar-SA"); // true
const isFrenchRtl = isRtlLanguage("fr-FR"); // false
// Get all RTL language codes
const rtlCodes = getRtlLanguages();
console.log(rtlCodes); // ["ar", "az", "ba", "dv", "fa", "he", ...]
// Build a map of RTL languages with their names
import { getLanguages } from "@suaveplan/locale-data/languages";
const allLangs = getLanguages();
const rtlLanguages = getRtlLanguages().map((code) => ({
code,
...allLangs[code],
}));
```
## API Reference
### `getLanguages(): Record<string, LanguageItem>`
Return the full language map.
- **Returns:** `Record<string, LanguageItem>` — the same reference each call.
- **Note:** Do not mutate the returned object.
### `getLanguage(code: string): LanguageItem`
Look up a language by its ISO 639-1 code.
- **Parameters:** `code` — two-letter ISO 639-1 code (e.g. `"en"`, `"fr"`, `"ar"`).
- **Returns:** The matching `LanguageItem` with `name` and `native` properties.
- **Throws:** `LocaleDataError` (`LOCALE_DATA/LANGUAGE.NOT_FOUND`) when the code is not found.
- **OTel:** Increments `locale_data.language.lookups` with `result: "found"` or `"not_found"`.
### `getRtlLanguages(): readonly string[]`
Return the list of RTL language codes.
- **Returns:** A readonly array of ISO 639-1 codes that use right-to-left script.
- **Included codes:** `ar`, `az`, `ba`, `dv`, `fa`, `he`, `iu`, `ks`, `ku`, `ps`, `sd`,
`sr`, `tg`, `ur`, `yi`.
### `isRtlLanguage(languageCode: string): boolean`
Determine whether a language code represents a RTL language.
- **Parameters:** `languageCode` — ISO 639-1 code or BCP-47 tag (e.g. `"ar"`, `"ar-SA"`,
`"fa_IR"`).
- **Returns:** `true` if the primary language subtag is in the RTL list, `false` otherwise.
- **Note:** Always returns `false` for unknown codes; never throws.
## Implementation Notes
Data is stored in `languages.data.ts` as plain `export const` declarations to keep the
implementation file under the 300-line hard limit. The data file is excluded from coverage
measurement because it contains no executable logic. The `isRtlLanguage` function normalises
BCP-47 tags by lowercasing and splitting on `_` or `-` before checking the primary subtag,
matching the same behaviour as the reference implementation in the original project.

View File

@@ -0,0 +1,112 @@
# types
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines the shared TypeScript types and Zod validation schemas used throughout `@suaveplan/locale-data`.
All runtime type shapes — `CountryItem` and `LanguageItem` — are declared here as Zod schemas, and
their inferred TypeScript types are derived via `z.infer`. This single-source-of-truth approach
ensures that runtime validation and static typing are always in sync, eliminating drift between
schema and type definitions across the package.
## Features
- **`CountryItemSchema`** — Zod object schema for a country entry with 9 fields: `name`, `native`,
`phone`, `continent`, `capital`, `currency`, `languages` (array), `emoji`, and `emojiU`.
- **`CountryItem`** — TypeScript type inferred from `CountryItemSchema`; used throughout the
countries module for typed lookups and filter results.
- **`LanguageItemSchema`** — Zod object schema for a language entry with 2 fields: `name` and
`native`.
- **`LanguageItem`** — TypeScript type inferred from `LanguageItemSchema`; used throughout the
languages module for typed lookups and RTL detection.
- **Zero runtime dependencies beyond Zod** — types are pure schema definitions with no side effects.
## Usage: Basic Example
```ts
import { CountryItemSchema, type CountryItem } from "@suaveplan/locale-data";
// Validate an unknown object as a CountryItem
const result = CountryItemSchema.safeParse(someObject);
if (result.success) {
const country: CountryItem = result.data;
console.log(country.name, country.emoji);
}
```
## Usage: Advanced Example
```ts
import { LanguageItemSchema, type LanguageItem } from "@suaveplan/locale-data";
// Parse and transform language data from an external API
const raw = await fetchLanguageFromApi("fr");
const language = LanguageItemSchema.parse(raw) satisfies LanguageItem;
console.log(language.name); // "French"
console.log(language.native); // "Français"
// Use with Zod's record schema for batch validation
import z from "zod";
import { CountryItemSchema } from "@suaveplan/locale-data";
const CountryMapSchema = z.record(z.string().length(2), CountryItemSchema);
const validated = CountryMapSchema.parse(externalData);
```
## API Reference
### `CountryItemSchema`
```ts
const CountryItemSchema: z.ZodObject<{
name: z.ZodString;
native: z.ZodString;
phone: z.ZodString;
continent: z.ZodString;
capital: z.ZodString;
currency: z.ZodString;
languages: z.ZodArray<z.ZodString>;
emoji: z.ZodString;
emojiU: z.ZodString;
}>;
```
Zod schema for a country entry. All fields are required strings except `languages` which is an
array of ISO 639-1 language codes.
### `CountryItem`
```ts
type CountryItem = z.infer<typeof CountryItemSchema>;
```
Static TypeScript type for a country record derived from `CountryItemSchema`.
### `LanguageItemSchema`
```ts
const LanguageItemSchema: z.ZodObject<{
name: z.ZodString;
native: z.ZodString;
}>;
```
Zod schema for a language entry with its English name and native script name.
### `LanguageItem`
```ts
type LanguageItem = z.infer<typeof LanguageItemSchema>;
```
Static TypeScript type for a language record derived from `LanguageItemSchema`.
## Implementation Notes
Schemas are defined using Zod's `z.object()` constructor, which produces a runtime validator that
can be used for both `parse` (throws on invalid input) and `safeParse` (returns a discriminated
union). The `CountryItem` and `LanguageItem` types are intentionally inferred rather than
hand-written to prevent drift. The `languages` field in `CountryItem` uses `z.array(z.string())`
because some countries have zero, one, or multiple official languages (e.g. South Africa has 11).