locale
ISO language, country, currency, and IANA time-zone unions.
import type {
LanguageCode, CountryCode, CurrencyCode,
LocaleTag, TimeZone, IsLocaleTag,
} from '@gentleduck/ttest/locale'
Curated subsets of ISO and IANA codes useful for constraining user input, building dropdowns, or type-checking i18n keys. Widen to string at the consumer boundary when you need codes outside the curated list — these unions are deliberately a common-case subset, not the full registry.
LanguageCode
type LanguageCode = 'aa' | 'ab' | 'af' | 'am' | 'ar' | 'az' | ... | 'zh'
ISO 639-1 two-letter codes (lowercase). Roughly 80 of the most-used languages.
const a: LanguageCode = 'en'
const b: LanguageCode = 'ar'
const c: LanguageCode = 'EN' // Error — codes are lowercase
CountryCode
type CountryCode = 'AD' | 'AE' | 'AF' | 'AG' | ... | 'ZW'
ISO 3166-1 alpha-2 codes (uppercase). Roughly 130 of the most-used countries.
const a: CountryCode = 'US'
const b: CountryCode = 'JP'
const c: CountryCode = 'us' // Error — codes are uppercase
CurrencyCode
type CurrencyCode = 'USD' | 'EUR' | 'GBP' | 'JPY' | ... | 'BTC' | 'ETH'
ISO 4217 codes (uppercase) plus 'BTC' and 'ETH' for crypto.
function formatPrice(amount: number, currency: CurrencyCode) { /* ... */ }
formatPrice(10, 'USD') // ok
formatPrice(10, 'XYZ') // Error
LocaleTag
type LocaleTag = LanguageCode | `${LanguageCode}-${CountryCode}`
BCP-47 tag: a LanguageCode, optionally followed by - and a CountryCode. Covers the common-case shapes that Intl.DateTimeFormat and friends consume.
const a: LocaleTag = 'en' // language only
const b: LocaleTag = 'en-US' // language + region
const c: LocaleTag = 'fr-CA' // valid
const d: LocaleTag = 'en-us' // Error — region must be uppercase
Edge case: extended tags like 'zh-Hant-TW' or grandfathered tags like 'i-klingon' are not in the curated union. Widen to string at the consumer boundary if you need them.
TimeZone
type TimeZone = 'UTC' | 'GMT' | 'Europe/London' | 'Asia/Tokyo' | ... | 'America/Argentina/Buenos_Aires'
Common IANA time-zone identifiers — about 30 of the most-used. The full IANA tz database is much larger; widen to string for user-supplied values.
function inZone(d: Date, tz: TimeZone) { /* ... */ }
inZone(new Date(), 'Asia/Tokyo') // ok
inZone(new Date(), 'Pacific/Galapagos') // Error — not in curated set
IsLocaleTag
type IsLocaleTag<L extends string> = L extends LocaleTag ? true : false
true if L matches the curated LocaleTag shape.
import type { AssertTrue, AssertFalse } from '@gentleduck/ttest/assert'
type _ = [
AssertTrue <IsLocaleTag<'en-US'>, 'en-US is a tag'>,
AssertTrue <IsLocaleTag<'fr'>, 'fr is a tag'>,
AssertFalse<IsLocaleTag<'en_US'>, 'underscore not allowed'>,
AssertFalse<IsLocaleTag<'xx'>, 'xx not a language code'>,
]
Edge case: IsLocaleTag<string> is boolean — a wide string doesn't extend a finite union. Always narrow before checking.