Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
# Spec: @suaveplan/locale-data
**Package:** `@suaveplan/locale-data`
**Domain:** `i18n/locale-data`
**Version:** `0.1.0`
**Status:** Published. Spec heading conformance: `locale-data-spec-conformance` (2026-04-26).
## Overview
Static ISO 3166-1 country data (209 entries) and ISO 639-1 language data (130+ entries) with typed lookup functions, OTel instrumentation, and Zod schemas.
## Requirements
### Requirement: Country lookup by ISO 3166-1 alpha-2 code
The package SHALL export `getCountry(code: string): CountryItem` performing an O(1) lookup over the static country map. Unknown codes MUST throw `LocaleDataError` with code `LOCALE_DATA.COUNTRY.NOT_FOUND`. The function MUST emit an OTel counter `locale_data.country.lookups` with attribute `result: "found" | "not_found"`.
#### Scenario: Known code returns the country record
- **WHEN** `getCountry("US")`, `getCountry("DE")`, or `getCountry("JP")` is called
- **THEN** the corresponding `CountryItem` is returned with all 9 fields populated
#### Scenario: Unknown code throws LocaleDataError
- **WHEN** `getCountry` is called with a code not in the registry
- **THEN** a `LocaleDataError` is thrown whose code is `LOCALE_DATA.COUNTRY.NOT_FOUND` AND whose message names the unknown code
**Verified by:** `src/countries/countries.test.ts::"returns a known country by ISO code"` AND `src/countries/countries.test.ts::"returns Germany correctly"` AND `src/countries/countries.test.ts::"returns Japan correctly"` AND `src/countries/countries.test.ts::"throws LocaleDataError for unknown code"` AND `src/countries/countries.test.ts::"throws with NOT_FOUND message for unknown code"`
### Requirement: Bulk country accessors
The package SHALL export `getAllCountries(): Record<string, CountryItem>` returning the entire registry by stable reference (no copy), `getCountriesByContinent(continent: string): CountryItem[]` filtering by 2-letter continent code, and `getCountriesByCurrency(currency: string): CountryItem[]` filtering by currency code with multi-currency entries split on comma.
#### Scenario: getAllCountries returns the full registry by reference
- **WHEN** `getAllCountries()` is called twice
- **THEN** both calls return the same object reference (no defensive copy)
- **AND** the registry includes US, DE, JP, FR
#### Scenario: Continent filter narrows by 2-letter code
- **WHEN** `getCountriesByContinent("EU")` or `getCountriesByContinent("AS")` is called
- **THEN** only countries on that continent are returned
- **WHEN** an unknown continent is given
- **THEN** an empty array is returned
#### Scenario: Currency filter splits comma-separated entries
- **WHEN** `getCountriesByCurrency("EUR")` or `getCountriesByCurrency("USD")` is called
- **THEN** every country whose `currency` field contains the code (including in a comma-separated list) is returned
- **WHEN** an unknown currency is given
- **THEN** an empty array is returned
**Verified by:** `src/countries/countries.test.ts::"returns all countries as a record"` AND `src/countries/countries.test.ts::"includes US, DE, JP, FR"` AND `src/countries/countries.test.ts::"returns the same reference each call (no copy)"` AND `src/countries/countries.test.ts::"returns only European countries for EU"` AND `src/countries/countries.test.ts::"returns Asian countries for AS"` AND `src/countries/countries.test.ts::"returns empty array for unknown continent"` AND `src/countries/countries.test.ts::"returns countries using EUR"` AND `src/countries/countries.test.ts::"returns countries using USD"` AND `src/countries/countries.test.ts::"returns empty array for unknown currency"` AND `src/countries/countries.test.ts::"supports multi-currency entries (comma-separated)"`
### Requirement: Language lookup and bulk accessor
The package SHALL export `getLanguage(code: string): LanguageItem` (O(1)) and `getLanguages(): Record<string, LanguageItem>` returning the same reference each call. Unknown codes MUST throw `LocaleDataError` with code `LOCALE_DATA.LANGUAGE.NOT_FOUND`. The lookup MUST emit an OTel counter `locale_data.language.lookups` with `result: "found" | "not_found"`.
#### Scenario: getLanguages returns the full registry by reference
- **WHEN** `getLanguages()` is called twice
- **THEN** both calls return the same object reference
- **AND** the registry includes common languages with native names populated
#### Scenario: Known language returns its record
- **WHEN** `getLanguage("en")` or `getLanguage("ar")` is called
- **THEN** the language record is returned with the correct native name
#### Scenario: Unknown language throws
- **WHEN** `getLanguage` is called with an unknown code
- **THEN** a `LocaleDataError(LOCALE_DATA.LANGUAGE.NOT_FOUND)` is thrown with the code in the message
**Verified by:** `src/languages/languages.test.ts::"returns all languages"` AND `src/languages/languages.test.ts::"returns the same reference each call"` AND `src/languages/languages.test.ts::"includes common languages"` AND `src/languages/languages.test.ts::"includes native names"` AND `src/languages/languages.test.ts::"returns a known language"` AND `src/languages/languages.test.ts::"returns English with correct native name"` AND `src/languages/languages.test.ts::"returns Arabic with correct native name"` AND `src/languages/languages.test.ts::"throws for unknown language code"` AND `src/languages/languages.test.ts::"throws with code in message for unknown language"`
### Requirement: RTL language detection
The package SHALL export `getRtlLanguages(): readonly string[]` listing the canonical RTL language codes, and `isRtlLanguage(code: string): boolean` which is BCP-47 aware (subtags MUST be stripped before lookup, so `"ar-EG"` matches `"ar"`).
#### Scenario: RTL list includes the canonical RTL languages
- **WHEN** `getRtlLanguages()` is called
- **THEN** it returns a non-empty array containing at least `"ar"`, `"he"`, `"fa"`, `"ur"`
- **AND** every code in the list resolves via `getLanguage`
#### Scenario: isRtlLanguage classifies known RTL languages
- **WHEN** `isRtlLanguage("ar")`, `isRtlLanguage("he")`, `isRtlLanguage("fa")`, or `isRtlLanguage("ur")` is called
- **THEN** the result is `true`
#### Scenario: isRtlLanguage classifies non-RTL and unknown codes
- **WHEN** `isRtlLanguage("en")`, `isRtlLanguage("fr")`, or `isRtlLanguage` is called with an unknown code
- **THEN** the result is `false`
**Verified by:** `src/languages/languages.test.ts::"includes Arabic, Hebrew, Persian, Urdu"` AND `src/languages/languages.test.ts::"all returned codes exist in language map"` AND `src/languages/languages.test.ts::"returns a non-empty array"` AND `src/languages/languages.test.ts::"returns true for Arabic"` AND `src/languages/languages.test.ts::"returns true for Hebrew"` AND `src/languages/languages.test.ts::"returns true for Persian"` AND `src/languages/languages.test.ts::"returns true for Urdu"` AND `src/languages/languages.test.ts::"returns false for English"` AND `src/languages/languages.test.ts::"returns false for French"` AND `src/languages/languages.test.ts::"returns false for unknown code"`
## Errors
- `LOCALE_DATA_CODES` — `defineCodes("LOCALE_DATA", ["COUNTRY.NOT_FOUND", "LANGUAGE.NOT_FOUND"])`.
- `LocaleDataError extends SuaveplanError<LocaleDataCode>`.
## Types / Schemas
- `CountryItemSchema` (Zod) — 9 fields: name, native, phone, continent, capital, currency, languages[], emoji, emojiU.
- `LanguageItemSchema` (Zod) — 2 fields: name, native.
- `CountryCodeSchema` — `z.string().length(2).toUpperCase()`.
- `LanguageCodeSchema` — `z.string().min(2).max(3).toLowerCase()`.
## Data Coverage
- **Countries:** 209 ISO 3166-1 alpha-2 entries, 7 continent codes.
- **Languages:** 130+ ISO 639-1 entries, 15 RTL codes (`ar`, `az`, `ba`, `dv`, `fa`, `he`, `iu`, `ks`, `ku`, `ps`, `sd`, `sr`, `tg`, `ur`, `yi`).
## Quality Gates
- 100% Istanbul coverage (all 4 thresholds)
- 100% docs coverage (6/6 implementation files)
- Biome lint clean
- TypeScript clean