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,40 @@
# locale-data-spec-conformance
- **Change ID**: locale-data-spec-conformance
- **Package**: `@suaveplan/locale-data`
- **Status**: PROPOSED
- **Type**: spec-conformance
- **Date**: 2026-04-26
## Why
The folded `openspec/specs/locale-data/spec.md` documented the public surface
under prose section headings (`### Countries`, `### Languages`, `### Errors`,
`### Types / Schemas`) with bullet lists of function signatures. The
conformance parser only sees `### Requirement: <name>` or `### CAP-NN —`
headings, so the audit reported "0 requirements parsed". This is a parsing
gap — the implementation already satisfies every clause.
## What changes
- Rewrite `openspec/specs/locale-data/spec.md` from prose-section headings
to four `### Requirement: <name>` blocks (country lookup, bulk country
accessors, language lookup, RTL detection) with `**Verified by:**` lines.
- No source changes; no public-surface changes; no new tests required —
every scenario is already pinned by `src/countries/countries.test.ts`
and `src/languages/languages.test.ts`.
## Out of scope
- New behaviour. Heading rewrite + verification annotation only.
- Source mapping table — `@suaveplan/locale-data` is genesis-original.
## Verification approach
Every requirement is pinned to existing `it(...)` titles in
`src/countries/countries.test.ts` (15 cases) and
`src/languages/languages.test.ts` (20 cases).
## Migration impact
None — additive metadata only. Public surface unchanged.

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

View File

@@ -0,0 +1,19 @@
# Tasks — locale-data-spec-conformance
> Heading rewrite + verification annotation only.
## 1. Spec rewrite
- [x] 1.1 Read `openspec/specs/locale-data/spec.md`
- [x] 1.2 Inventory existing tests in `src/countries/countries.test.ts` and `src/languages/languages.test.ts`
- [x] 1.3 Rewrite `openspec/specs/locale-data/spec.md` to four `### Requirement: <name>` blocks with `**Verified by:**` lines
## 2. Verification
- [x] 2.1 `bun scripts/openspec-conformance.ts @suaveplan/locale-data` — exit 0, no `error` findings
- [x] 2.2 `bunx turbo lint typecheck test --filter=@suaveplan/locale-data` — green
- [x] 2.3 `bunx turbo test:coverage --filter=@suaveplan/locale-data` — 100/100/100/100
## 3. Archive
- [x] 3.1 Move `openspec/changes/locale-data-spec-conformance/` → `openspec/archive/2026-04-26-locale-data-spec-conformance/`