@mailwoman/codex 7.2.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/address-system-conventions.ts +68 -0
  2. package/au/delivery-service.ts +179 -0
  3. package/au/index.ts +15 -0
  4. package/au/level-designator.ts +209 -0
  5. package/au/postcode.ts +51 -0
  6. package/au/state.ts +35 -0
  7. package/ca/index.ts +12 -0
  8. package/ca/postal-code.ts +121 -0
  9. package/ca/province.ts +99 -0
  10. package/ca/street-type.ts +167 -0
  11. package/country/codes.ts +534 -0
  12. package/country/country.ts +125 -0
  13. package/country/index.ts +15 -0
  14. package/country/names.ts +274 -0
  15. package/country/official-languages.ts +397 -0
  16. package/country/reference-data.ts +267 -0
  17. package/country/reference.ts +47 -0
  18. package/country/subdivision.ts +93 -0
  19. package/de/bundesland.ts +102 -0
  20. package/de/index.ts +12 -0
  21. package/de/postleitzahl.ts +91 -0
  22. package/de/street-type.ts +83 -0
  23. package/fr/cedex.ts +56 -0
  24. package/fr/code-postal.ts +105 -0
  25. package/fr/departement.ts +142 -0
  26. package/fr/index.ts +14 -0
  27. package/fr/region.ts +93 -0
  28. package/fr/voie.ts +98 -0
  29. package/gb/country.ts +74 -0
  30. package/gb/index.ts +14 -0
  31. package/gb/postcode-area.ts +107 -0
  32. package/gb/postcode.ts +109 -0
  33. package/gb/street-type.ts +90 -0
  34. package/index.ts +38 -0
  35. package/jp/address-unit.ts +87 -0
  36. package/jp/index.ts +13 -0
  37. package/jp/postal-code.ts +93 -0
  38. package/jp/prefecture.ts +173 -0
  39. package/level-semantics.ts +623 -0
  40. package/nz/delivery-service.ts +211 -0
  41. package/nz/index.ts +12 -0
  42. package/nz/postcode.ts +42 -0
  43. package/out/country/index.d.ts +1 -0
  44. package/out/country/index.d.ts.map +1 -1
  45. package/out/country/index.js +1 -0
  46. package/out/country/index.js.map +1 -1
  47. package/out/country/subdivision.d.ts +44 -0
  48. package/out/country/subdivision.d.ts.map +1 -0
  49. package/out/country/subdivision.js +76 -0
  50. package/out/country/subdivision.js.map +1 -0
  51. package/package.json +81 -37
  52. package/postcode-systems.ts +68 -0
  53. package/tools/build-country-surface-lexicon.ts +166 -0
  54. package/tools/export-country-surfaces.ts +46 -0
  55. package/tools/generate-country-reference.ts +153 -0
  56. package/tools/generate-official-languages.ts +188 -0
  57. package/tools/index.ts +12 -0
  58. package/us/floor-designator.ts +119 -0
  59. package/us/index.ts +19 -0
  60. package/us/military-address.ts +199 -0
  61. package/us/po-box.ts +82 -0
  62. package/us/state.ts +156 -0
  63. package/us/street-directional.ts +220 -0
  64. package/us/street-suffix.ts +345 -0
  65. package/us/unit-designator.ts +223 -0
  66. package/us/zipcode.ts +212 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * GENERATED — do not edit by hand. Country calling codes (E.164) + currencies (ISO 4217), derived
7
+ * from mledoze/countries (https://github.com/mledoze/countries, ODbL). NANP members map to 1.
8
+ * Regenerate with: mailwoman dev generate country-reference
9
+ */
10
+
11
+ /** Static per-country reference: calling code + currency. */
12
+ export interface CountryReference {
13
+ callingCode?: number
14
+ currency?: { isoCode: string; name?: string; symbol?: string }
15
+ }
16
+
17
+ /** ISO 3166-1 alpha-2 → reference. */
18
+ export const COUNTRY_REFERENCE: Record<string, CountryReference> = {
19
+ AD: { callingCode: 376, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
20
+ AE: { callingCode: 971, currency: { isoCode: "AED", name: "United Arab Emirates dirham", symbol: "د.إ" } },
21
+ AF: { callingCode: 93, currency: { isoCode: "AFN", name: "Afghan afghani", symbol: "؋" } },
22
+ AG: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
23
+ AI: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
24
+ AL: { callingCode: 355, currency: { isoCode: "ALL", name: "Albanian lek", symbol: "L" } },
25
+ AM: { callingCode: 374, currency: { isoCode: "AMD", name: "Armenian dram", symbol: "֏" } },
26
+ AO: { callingCode: 244, currency: { isoCode: "AOA", name: "Angolan kwanza", symbol: "Kz" } },
27
+ AR: { callingCode: 54, currency: { isoCode: "ARS", name: "Argentine peso", symbol: "$" } },
28
+ AS: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
29
+ AT: { callingCode: 43, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
30
+ AU: { callingCode: 61, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
31
+ AW: { callingCode: 297, currency: { isoCode: "AWG", name: "Aruban florin", symbol: "ƒ" } },
32
+ AX: { callingCode: 35818, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
33
+ AZ: { callingCode: 994, currency: { isoCode: "AZN", name: "Azerbaijani manat", symbol: "₼" } },
34
+ BA: { callingCode: 387, currency: { isoCode: "BAM", name: "Bosnia and Herzegovina convertible mark", symbol: "KM" } },
35
+ BB: { callingCode: 1, currency: { isoCode: "BBD", name: "Barbadian dollar", symbol: "$" } },
36
+ BD: { callingCode: 880, currency: { isoCode: "BDT", name: "Bangladeshi taka", symbol: "৳" } },
37
+ BE: { callingCode: 32, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
38
+ BF: { callingCode: 226, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
39
+ BG: { callingCode: 359, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
40
+ BH: { callingCode: 973, currency: { isoCode: "BHD", name: "Bahraini dinar", symbol: ".د.ب" } },
41
+ BI: { callingCode: 257, currency: { isoCode: "BIF", name: "Burundian franc", symbol: "Fr" } },
42
+ BJ: { callingCode: 229, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
43
+ BL: { callingCode: 590, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
44
+ BM: { callingCode: 1, currency: { isoCode: "BMD", name: "Bermudian dollar", symbol: "$" } },
45
+ BN: { callingCode: 673, currency: { isoCode: "BND", name: "Brunei dollar", symbol: "$" } },
46
+ BO: { callingCode: 591, currency: { isoCode: "BOB", name: "Bolivian boliviano", symbol: "Bs." } },
47
+ BQ: { callingCode: 599, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
48
+ BR: { callingCode: 55, currency: { isoCode: "BRL", name: "Brazilian real", symbol: "R$" } },
49
+ BS: { callingCode: 1, currency: { isoCode: "BSD", name: "Bahamian dollar", symbol: "$" } },
50
+ BT: { callingCode: 975, currency: { isoCode: "BTN", name: "Bhutanese ngultrum", symbol: "Nu." } },
51
+ BV: { callingCode: 47 },
52
+ BW: { callingCode: 267, currency: { isoCode: "BWP", name: "Botswana pula", symbol: "P" } },
53
+ BY: { callingCode: 375, currency: { isoCode: "BYN", name: "Belarusian ruble", symbol: "Br" } },
54
+ BZ: { callingCode: 501, currency: { isoCode: "BZD", name: "Belize dollar", symbol: "$" } },
55
+ CA: { callingCode: 1, currency: { isoCode: "CAD", name: "Canadian dollar", symbol: "$" } },
56
+ CC: { callingCode: 61, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
57
+ CD: { callingCode: 243, currency: { isoCode: "CDF", name: "Congolese franc", symbol: "FC" } },
58
+ CF: { callingCode: 236, currency: { isoCode: "XAF", name: "Central African CFA franc", symbol: "Fr" } },
59
+ CG: { callingCode: 242, currency: { isoCode: "XAF", name: "Central African CFA franc", symbol: "Fr" } },
60
+ CH: { callingCode: 41, currency: { isoCode: "CHF", name: "Swiss franc", symbol: "Fr." } },
61
+ CI: { callingCode: 225, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
62
+ CK: { callingCode: 682, currency: { isoCode: "CKD", name: "Cook Islands dollar", symbol: "$" } },
63
+ CL: { callingCode: 56, currency: { isoCode: "CLP", name: "Chilean peso", symbol: "$" } },
64
+ CM: { callingCode: 237, currency: { isoCode: "XAF", name: "Central African CFA franc", symbol: "Fr" } },
65
+ CN: { callingCode: 86, currency: { isoCode: "CNY", name: "Chinese yuan", symbol: "¥" } },
66
+ CO: { callingCode: 57, currency: { isoCode: "COP", name: "Colombian peso", symbol: "$" } },
67
+ CR: { callingCode: 506, currency: { isoCode: "CRC", name: "Costa Rican colón", symbol: "₡" } },
68
+ CU: { callingCode: 53, currency: { isoCode: "CUC", name: "Cuban convertible peso", symbol: "$" } },
69
+ CV: { callingCode: 238, currency: { isoCode: "CVE", name: "Cape Verdean escudo", symbol: "Esc" } },
70
+ CW: { callingCode: 599, currency: { isoCode: "ANG", name: "Netherlands Antillean guilder", symbol: "ƒ" } },
71
+ CX: { callingCode: 61, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
72
+ CY: { callingCode: 357, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
73
+ CZ: { callingCode: 420, currency: { isoCode: "CZK", name: "Czech koruna", symbol: "Kč" } },
74
+ DE: { callingCode: 49, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
75
+ DJ: { callingCode: 253, currency: { isoCode: "DJF", name: "Djiboutian franc", symbol: "Fr" } },
76
+ DK: { callingCode: 45, currency: { isoCode: "DKK", name: "Danish krone", symbol: "kr" } },
77
+ DM: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
78
+ DO: { callingCode: 1, currency: { isoCode: "DOP", name: "Dominican peso", symbol: "$" } },
79
+ DZ: { callingCode: 213, currency: { isoCode: "DZD", name: "Algerian dinar", symbol: "د.ج" } },
80
+ EC: { callingCode: 593, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
81
+ EE: { callingCode: 372, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
82
+ EG: { callingCode: 20, currency: { isoCode: "EGP", name: "Egyptian pound", symbol: "£" } },
83
+ EH: { callingCode: 2, currency: { isoCode: "DZD", name: "Algerian dinar", symbol: "دج" } },
84
+ ER: { callingCode: 291, currency: { isoCode: "ERN", name: "Eritrean nakfa", symbol: "Nfk" } },
85
+ ES: { callingCode: 34, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
86
+ ET: { callingCode: 251, currency: { isoCode: "ETB", name: "Ethiopian birr", symbol: "Br" } },
87
+ FI: { callingCode: 358, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
88
+ FJ: { callingCode: 679, currency: { isoCode: "FJD", name: "Fijian dollar", symbol: "$" } },
89
+ FK: { callingCode: 500, currency: { isoCode: "FKP", name: "Falkland Islands pound", symbol: "£" } },
90
+ FM: { callingCode: 691 },
91
+ FO: { callingCode: 298, currency: { isoCode: "DKK", name: "Danish krone", symbol: "kr" } },
92
+ FR: { callingCode: 33, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
93
+ GA: { callingCode: 241, currency: { isoCode: "XAF", name: "Central African CFA franc", symbol: "Fr" } },
94
+ GB: { callingCode: 44, currency: { isoCode: "GBP", name: "British pound", symbol: "£" } },
95
+ GD: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
96
+ GE: { callingCode: 995, currency: { isoCode: "GEL", name: "lari", symbol: "₾" } },
97
+ GF: { callingCode: 594, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
98
+ GG: { callingCode: 44, currency: { isoCode: "GBP", name: "British pound", symbol: "£" } },
99
+ GH: { callingCode: 233, currency: { isoCode: "GHS", name: "Ghanaian cedi", symbol: "₵" } },
100
+ GI: { callingCode: 350, currency: { isoCode: "GIP", name: "Gibraltar pound", symbol: "£" } },
101
+ GL: { callingCode: 299, currency: { isoCode: "DKK", name: "krone", symbol: "kr." } },
102
+ GM: { callingCode: 220, currency: { isoCode: "GMD", name: "dalasi", symbol: "D" } },
103
+ GN: { callingCode: 224, currency: { isoCode: "GNF", name: "Guinean franc", symbol: "Fr" } },
104
+ GP: { callingCode: 590, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
105
+ GQ: { callingCode: 240, currency: { isoCode: "XAF", name: "Central African CFA franc", symbol: "Fr" } },
106
+ GR: { callingCode: 30, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
107
+ GS: { callingCode: 500, currency: { isoCode: "SHP", name: "Saint Helena pound", symbol: "£" } },
108
+ GT: { callingCode: 502, currency: { isoCode: "GTQ", name: "Guatemalan quetzal", symbol: "Q" } },
109
+ GU: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
110
+ GW: { callingCode: 245, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
111
+ GY: { callingCode: 592, currency: { isoCode: "GYD", name: "Guyanese dollar", symbol: "$" } },
112
+ HK: { callingCode: 852, currency: { isoCode: "HKD", name: "Hong Kong dollar", symbol: "$" } },
113
+ HN: { callingCode: 504, currency: { isoCode: "HNL", name: "Honduran lempira", symbol: "L" } },
114
+ HR: { callingCode: 385, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
115
+ HT: { callingCode: 509, currency: { isoCode: "HTG", name: "Haitian gourde", symbol: "G" } },
116
+ HU: { callingCode: 36, currency: { isoCode: "HUF", name: "Hungarian forint", symbol: "Ft" } },
117
+ ID: { callingCode: 62, currency: { isoCode: "IDR", name: "Indonesian rupiah", symbol: "Rp" } },
118
+ IE: { callingCode: 353, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
119
+ IL: { callingCode: 972, currency: { isoCode: "ILS", name: "Israeli new shekel", symbol: "₪" } },
120
+ IM: { callingCode: 44, currency: { isoCode: "GBP", name: "British pound", symbol: "£" } },
121
+ IN: { callingCode: 91, currency: { isoCode: "INR", name: "Indian rupee", symbol: "₹" } },
122
+ IO: { callingCode: 246, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
123
+ IQ: { callingCode: 964, currency: { isoCode: "IQD", name: "Iraqi dinar", symbol: "ع.د" } },
124
+ IR: { callingCode: 98, currency: { isoCode: "IRR", name: "Iranian rial", symbol: "﷼" } },
125
+ IS: { callingCode: 354, currency: { isoCode: "ISK", name: "Icelandic króna", symbol: "kr" } },
126
+ IT: { callingCode: 39, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
127
+ JE: { callingCode: 44, currency: { isoCode: "GBP", name: "British pound", symbol: "£" } },
128
+ JM: { callingCode: 1, currency: { isoCode: "JMD", name: "Jamaican dollar", symbol: "$" } },
129
+ JO: { callingCode: 962, currency: { isoCode: "JOD", name: "Jordanian dinar", symbol: "د.ا" } },
130
+ JP: { callingCode: 81, currency: { isoCode: "JPY", name: "Japanese yen", symbol: "¥" } },
131
+ KE: { callingCode: 254, currency: { isoCode: "KES", name: "Kenyan shilling", symbol: "Sh" } },
132
+ KG: { callingCode: 996, currency: { isoCode: "KGS", name: "Kyrgyzstani som", symbol: "с" } },
133
+ KH: { callingCode: 855, currency: { isoCode: "KHR", name: "Cambodian riel", symbol: "៛" } },
134
+ KI: { callingCode: 686, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
135
+ KM: { callingCode: 269, currency: { isoCode: "KMF", name: "Comorian franc", symbol: "Fr" } },
136
+ KN: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
137
+ KP: { callingCode: 850, currency: { isoCode: "KPW", name: "North Korean won", symbol: "₩" } },
138
+ KR: { callingCode: 82, currency: { isoCode: "KRW", name: "South Korean won", symbol: "₩" } },
139
+ KW: { callingCode: 965, currency: { isoCode: "KWD", name: "Kuwaiti dinar", symbol: "د.ك" } },
140
+ KY: { callingCode: 1, currency: { isoCode: "KYD", name: "Cayman Islands dollar", symbol: "$" } },
141
+ KZ: { callingCode: 7, currency: { isoCode: "KZT", name: "Kazakhstani tenge", symbol: "₸" } },
142
+ LA: { callingCode: 856, currency: { isoCode: "LAK", name: "Lao kip", symbol: "₭" } },
143
+ LB: { callingCode: 961, currency: { isoCode: "LBP", name: "Lebanese pound", symbol: "ل.ل" } },
144
+ LC: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
145
+ LI: { callingCode: 423, currency: { isoCode: "CHF", name: "Swiss franc", symbol: "Fr" } },
146
+ LK: { callingCode: 94, currency: { isoCode: "LKR", name: "Sri Lankan rupee", symbol: "Rs රු" } },
147
+ LR: { callingCode: 231, currency: { isoCode: "LRD", name: "Liberian dollar", symbol: "$" } },
148
+ LS: { callingCode: 266, currency: { isoCode: "LSL", name: "Lesotho loti", symbol: "L" } },
149
+ LT: { callingCode: 370, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
150
+ LU: { callingCode: 352, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
151
+ LV: { callingCode: 371, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
152
+ LY: { callingCode: 218, currency: { isoCode: "LYD", name: "Libyan dinar", symbol: "ل.د" } },
153
+ MA: { callingCode: 212, currency: { isoCode: "MAD", name: "Moroccan dirham", symbol: "د.م." } },
154
+ MC: { callingCode: 377, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
155
+ MD: { callingCode: 373, currency: { isoCode: "MDL", name: "Moldovan leu", symbol: "L" } },
156
+ ME: { callingCode: 382, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
157
+ MF: { callingCode: 590, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
158
+ MG: { callingCode: 261, currency: { isoCode: "MGA", name: "Malagasy ariary", symbol: "Ar" } },
159
+ MH: { callingCode: 692, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
160
+ MK: { callingCode: 389, currency: { isoCode: "MKD", name: "denar", symbol: "den" } },
161
+ ML: { callingCode: 223, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
162
+ MM: { callingCode: 95, currency: { isoCode: "MMK", name: "Burmese kyat", symbol: "Ks" } },
163
+ MN: { callingCode: 976, currency: { isoCode: "MNT", name: "Mongolian tögrög", symbol: "₮" } },
164
+ MO: { callingCode: 853, currency: { isoCode: "MOP", name: "Macanese pataca", symbol: "P" } },
165
+ MP: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
166
+ MQ: { callingCode: 596, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
167
+ MR: { callingCode: 222, currency: { isoCode: "MRU", name: "Mauritanian ouguiya", symbol: "UM" } },
168
+ MS: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
169
+ MT: { callingCode: 356, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
170
+ MU: { callingCode: 230, currency: { isoCode: "MUR", name: "Mauritian rupee", symbol: "₨" } },
171
+ MV: { callingCode: 960, currency: { isoCode: "MVR", name: "Maldivian rufiyaa", symbol: ".ރ" } },
172
+ MW: { callingCode: 265, currency: { isoCode: "MWK", name: "Malawian kwacha", symbol: "MK" } },
173
+ MX: { callingCode: 52, currency: { isoCode: "MXN", name: "Mexican peso", symbol: "$" } },
174
+ MY: { callingCode: 60, currency: { isoCode: "MYR", name: "Malaysian ringgit", symbol: "RM" } },
175
+ MZ: { callingCode: 258, currency: { isoCode: "MZN", name: "Mozambican metical", symbol: "MT" } },
176
+ NA: { callingCode: 264, currency: { isoCode: "NAD", name: "Namibian dollar", symbol: "$" } },
177
+ NC: { callingCode: 687, currency: { isoCode: "XPF", name: "CFP franc", symbol: "₣" } },
178
+ NE: { callingCode: 227, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
179
+ NF: { callingCode: 672, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
180
+ NG: { callingCode: 234, currency: { isoCode: "NGN", name: "Nigerian naira", symbol: "₦" } },
181
+ NI: { callingCode: 505, currency: { isoCode: "NIO", name: "Nicaraguan córdoba", symbol: "C$" } },
182
+ NL: { callingCode: 31, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
183
+ NO: { callingCode: 47, currency: { isoCode: "NOK", name: "Norwegian krone", symbol: "kr" } },
184
+ NP: { callingCode: 977, currency: { isoCode: "NPR", name: "Nepalese rupee", symbol: "₨" } },
185
+ NR: { callingCode: 674, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
186
+ NU: { callingCode: 683, currency: { isoCode: "NZD", name: "New Zealand dollar", symbol: "$" } },
187
+ NZ: { callingCode: 64, currency: { isoCode: "NZD", name: "New Zealand dollar", symbol: "$" } },
188
+ OM: { callingCode: 968, currency: { isoCode: "OMR", name: "Omani rial", symbol: "ر.ع." } },
189
+ PA: { callingCode: 507, currency: { isoCode: "PAB", name: "Panamanian balboa", symbol: "B/." } },
190
+ PE: { callingCode: 51, currency: { isoCode: "PEN", name: "Peruvian sol", symbol: "S/." } },
191
+ PF: { callingCode: 689, currency: { isoCode: "XPF", name: "CFP franc", symbol: "₣" } },
192
+ PG: { callingCode: 675, currency: { isoCode: "PGK", name: "Papua New Guinean kina", symbol: "K" } },
193
+ PH: { callingCode: 63, currency: { isoCode: "PHP", name: "Philippine peso", symbol: "₱" } },
194
+ PK: { callingCode: 92, currency: { isoCode: "PKR", name: "Pakistani rupee", symbol: "₨" } },
195
+ PL: { callingCode: 48, currency: { isoCode: "PLN", name: "Polish złoty", symbol: "zł" } },
196
+ PM: { callingCode: 508, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
197
+ PN: { callingCode: 64, currency: { isoCode: "NZD", name: "New Zealand dollar", symbol: "$" } },
198
+ PR: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
199
+ PS: { callingCode: 970, currency: { isoCode: "EGP", name: "Egyptian pound", symbol: "E£" } },
200
+ PT: { callingCode: 351, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
201
+ PW: { callingCode: 680, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
202
+ PY: { callingCode: 595, currency: { isoCode: "PYG", name: "Paraguayan guaraní", symbol: "₲" } },
203
+ QA: { callingCode: 974, currency: { isoCode: "QAR", name: "Qatari riyal", symbol: "ر.ق" } },
204
+ RE: { callingCode: 262, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
205
+ RO: { callingCode: 40, currency: { isoCode: "RON", name: "Romanian leu", symbol: "lei" } },
206
+ RS: { callingCode: 381, currency: { isoCode: "RSD", name: "Serbian dinar", symbol: "дин." } },
207
+ RU: { callingCode: 7, currency: { isoCode: "RUB", name: "Russian ruble", symbol: "₽" } },
208
+ RW: { callingCode: 250, currency: { isoCode: "RWF", name: "Rwandan franc", symbol: "Fr" } },
209
+ SA: { callingCode: 966, currency: { isoCode: "SAR", name: "Saudi riyal", symbol: "ر.س" } },
210
+ SB: { callingCode: 677, currency: { isoCode: "SBD", name: "Solomon Islands dollar", symbol: "$" } },
211
+ SC: { callingCode: 248, currency: { isoCode: "SCR", name: "Seychellois rupee", symbol: "₨" } },
212
+ SD: { callingCode: 249, currency: { isoCode: "SDG", name: "Sudanese pound", symbol: "PT" } },
213
+ SE: { callingCode: 46, currency: { isoCode: "SEK", name: "Swedish krona", symbol: "kr" } },
214
+ SG: { callingCode: 65, currency: { isoCode: "SGD", name: "Singapore dollar", symbol: "$" } },
215
+ SH: { callingCode: 2, currency: { isoCode: "GBP", name: "Pound sterling", symbol: "£" } },
216
+ SI: { callingCode: 386, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
217
+ SJ: { callingCode: 4779, currency: { isoCode: "NOK", name: "krone", symbol: "kr" } },
218
+ SK: { callingCode: 421, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
219
+ SL: { callingCode: 232, currency: { isoCode: "SLL", name: "Sierra Leonean leone", symbol: "Le" } },
220
+ SM: { callingCode: 378, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
221
+ SN: { callingCode: 221, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
222
+ SO: { callingCode: 252, currency: { isoCode: "SOS", name: "Somali shilling", symbol: "Sh" } },
223
+ SR: { callingCode: 597, currency: { isoCode: "SRD", name: "Surinamese dollar", symbol: "$" } },
224
+ SS: { callingCode: 211, currency: { isoCode: "SSP", name: "South Sudanese pound", symbol: "£" } },
225
+ ST: { callingCode: 239, currency: { isoCode: "STN", name: "São Tomé and Príncipe dobra", symbol: "Db" } },
226
+ SV: { callingCode: 503, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
227
+ SX: { callingCode: 1, currency: { isoCode: "ANG", name: "Netherlands Antillean guilder", symbol: "ƒ" } },
228
+ SY: { callingCode: 963, currency: { isoCode: "SYP", name: "Syrian pound", symbol: "£" } },
229
+ SZ: { callingCode: 268, currency: { isoCode: "SZL", name: "Swazi lilangeni", symbol: "L" } },
230
+ TC: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
231
+ TD: { callingCode: 235, currency: { isoCode: "XAF", name: "Central African CFA franc", symbol: "Fr" } },
232
+ TF: { callingCode: 262, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
233
+ TG: { callingCode: 228, currency: { isoCode: "XOF", name: "West African CFA franc", symbol: "Fr" } },
234
+ TH: { callingCode: 66, currency: { isoCode: "THB", name: "Thai baht", symbol: "฿" } },
235
+ TJ: { callingCode: 992, currency: { isoCode: "TJS", name: "Tajikistani somoni", symbol: "ЅМ" } },
236
+ TK: { callingCode: 690, currency: { isoCode: "NZD", name: "New Zealand dollar", symbol: "$" } },
237
+ TL: { callingCode: 670, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
238
+ TM: { callingCode: 993, currency: { isoCode: "TMT", name: "Turkmenistan manat", symbol: "m" } },
239
+ TN: { callingCode: 216, currency: { isoCode: "TND", name: "Tunisian dinar", symbol: "د.ت" } },
240
+ TO: { callingCode: 676, currency: { isoCode: "TOP", name: "Tongan paʻanga", symbol: "T$" } },
241
+ TR: { callingCode: 90, currency: { isoCode: "TRY", name: "Turkish lira", symbol: "₺" } },
242
+ TT: { callingCode: 1, currency: { isoCode: "TTD", name: "Trinidad and Tobago dollar", symbol: "$" } },
243
+ TV: { callingCode: 688, currency: { isoCode: "AUD", name: "Australian dollar", symbol: "$" } },
244
+ TW: { callingCode: 886, currency: { isoCode: "TWD", name: "New Taiwan dollar", symbol: "$" } },
245
+ TZ: { callingCode: 255, currency: { isoCode: "TZS", name: "Tanzanian shilling", symbol: "Sh" } },
246
+ UA: { callingCode: 380, currency: { isoCode: "UAH", name: "Ukrainian hryvnia", symbol: "₴" } },
247
+ UG: { callingCode: 256, currency: { isoCode: "UGX", name: "Ugandan shilling", symbol: "Sh" } },
248
+ UM: { callingCode: 268, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
249
+ US: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
250
+ UY: { callingCode: 598, currency: { isoCode: "UYU", name: "Uruguayan peso", symbol: "$" } },
251
+ UZ: { callingCode: 998, currency: { isoCode: "UZS", name: "Uzbekistani soʻm", symbol: "so'm" } },
252
+ VA: { callingCode: 3, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
253
+ VC: { callingCode: 1, currency: { isoCode: "XCD", name: "Eastern Caribbean dollar", symbol: "$" } },
254
+ VE: { callingCode: 58, currency: { isoCode: "VES", name: "Venezuelan bolívar soberano", symbol: "Bs.S." } },
255
+ VG: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
256
+ VI: { callingCode: 1, currency: { isoCode: "USD", name: "United States dollar", symbol: "$" } },
257
+ VN: { callingCode: 84, currency: { isoCode: "VND", name: "Vietnamese đồng", symbol: "₫" } },
258
+ VU: { callingCode: 678, currency: { isoCode: "VUV", name: "Vanuatu vatu", symbol: "Vt" } },
259
+ WF: { callingCode: 681, currency: { isoCode: "XPF", name: "CFP franc", symbol: "₣" } },
260
+ WS: { callingCode: 685, currency: { isoCode: "WST", name: "Samoan tālā", symbol: "T" } },
261
+ XK: { callingCode: 383, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
262
+ YE: { callingCode: 967, currency: { isoCode: "YER", name: "Yemeni rial", symbol: "﷼" } },
263
+ YT: { callingCode: 262, currency: { isoCode: "EUR", name: "Euro", symbol: "€" } },
264
+ ZA: { callingCode: 27, currency: { isoCode: "ZAR", name: "South African rand", symbol: "R" } },
265
+ ZM: { callingCode: 260, currency: { isoCode: "ZMW", name: "Zambian kwacha", symbol: "ZK" } },
266
+ ZW: { callingCode: 263, currency: { isoCode: "BWP", name: "Botswana pula", symbol: "P" } },
267
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Country-reference annotators. The flag emoji is a pure function of the ISO 3166-1 alpha-2 code
7
+ * (two Regional Indicator Symbols); calling code + currency come from {@link COUNTRY_REFERENCE}, a
8
+ * table generated from mledoze/countries (provenance-tracked, not hand-typed — the
9
+ * no-load-bearing-trivia rule).
10
+ */
11
+
12
+ import type { AnnotationSet, Annotator } from "@mailwoman/annotations"
13
+
14
+ import { COUNTRY_REFERENCE } from "./reference-data.ts"
15
+
16
+ const REGIONAL_INDICATOR_BASE = 0x1f1e6
17
+ const A_UPPER = "A".charCodeAt(0)
18
+
19
+ /**
20
+ * The flag emoji for an ISO 3166-1 alpha-2 country code, formed from the two Regional Indicator Symbols (`US` → 🇺🇸).
21
+ * Returns `""` for anything that is not two ASCII letters.
22
+ */
23
+ export function countryFlag(alpha2: string): string {
24
+ const code = alpha2.toUpperCase()
25
+
26
+ if (!/^[A-Z]{2}$/.test(code)) return ""
27
+
28
+ return String.fromCodePoint(...[...code].map((c) => REGIONAL_INDICATOR_BASE + c.charCodeAt(0) - A_UPPER))
29
+ }
30
+
31
+ /**
32
+ * Fill the country-reference slice (ISO 3166 alpha-2, flag, calling code, currency) from a resolved country code.
33
+ * Abstains on anything that isn't two ASCII letters.
34
+ */
35
+ export const countryReferenceAnnotator: Annotator = ({ countryCode }): Partial<AnnotationSet> => {
36
+ if (!countryCode || !/^[A-Za-z]{2}$/.test(countryCode)) return {}
37
+ const alpha2 = countryCode.toUpperCase()
38
+ const ref = COUNTRY_REFERENCE[alpha2]
39
+ const flag = countryFlag(alpha2)
40
+
41
+ return {
42
+ iso3166: { alpha2 },
43
+ ...(flag ? { flag } : {}),
44
+ ...(ref?.callingCode != null ? { callingCode: ref.callingCode } : {}),
45
+ ...(ref?.currency ? { currency: ref.currency } : {}),
46
+ }
47
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * ISO 3166-2 subdivision → country reference, the cross-country complement to `country.ts`'s
7
+ * ISO 3166-1 `matchCountry`. A subdivision token ("QC", "Ontario", "Illinois") names a first-level
8
+ * admin unit whose COUNTRY is the piece the resolver needs when a region qualifier is the only
9
+ * signal that the locale-inferred default country is wrong ("Montreal QC" under a US locale).
10
+ *
11
+ * Scope is deliberately minimal: the two subdivision systems whose two-letter codes people write
12
+ * ON THE ADDRESS LINE and whose homonymous localities collide across the border — US states
13
+ * (`us/state.ts`) and Canadian provinces (`ca/province.ts`). Both directions are covered: the ISO
14
+ * code (`QC` → Quebec) and the full name (`Quebec` / `Québec` → the `QC` record), so a resolver
15
+ * can expand the abbreviation the gazetteer FTS index lacks ("QC" is not an alt-name of Québec)
16
+ * into the full name it does carry. This is a soft prior, not a routing decision — the gazetteer
17
+ * still does the geographic confirmation (per the registry-backed-soft-prior doctrine).
18
+ *
19
+ * The US and Canadian code sets are disjoint (no two-letter code, and no full name, collides
20
+ * between the two), so the combined lookup below is unambiguous. `CA` resolves to California the
21
+ * US state (Canada's provinces carry no `CA` subdivision code), never to Canada the country —
22
+ * country recognition stays with `matchCountry`.
23
+ *
24
+ * Source: the underlying `US_STATE_BY_ABBREVIATION` (USPS Publication 28, Appendix B) and
25
+ * `CA_PROVINCES` (ISO 3166-2:CA) tables. No new provenance is introduced here — this module only
26
+ * re-keys those two existing tables into one subdivision→country view.
27
+ */
28
+
29
+ import { CA_PROVINCES } from "../ca/province.ts"
30
+ import { US_STATE_BY_ABBREVIATION } from "../us/state.ts"
31
+
32
+ /** A resolved subdivision: its ISO 3166-2 code (sans country prefix), canonical English name, and ISO 3166-1 country. */
33
+ export interface SubdivisionMatch {
34
+ /** ISO 3166-2 subdivision code without the country prefix (e.g. `QC` for `CA-QC`, `IL` for `US-IL`). */
35
+ code: string
36
+ /** Canonical English name (e.g. `Quebec`, `Illinois`). */
37
+ name: string
38
+ /** ISO 3166-1 alpha-2 country the subdivision belongs to (`CA`, `US`). */
39
+ country: string
40
+ }
41
+
42
+ /** Strip diacritics + lowercase so `Québec`, `Quebec`, and `quebec` all key alike (mirrors `ca/province.ts`). */
43
+ function foldName(s: string): string {
44
+ return s
45
+ .toLowerCase()
46
+ .normalize("NFD")
47
+ .replace(/[\u0300-\u036f]/g, "")
48
+ .replace(/[^a-z0-9]+/g, " ")
49
+ .trim()
50
+ }
51
+
52
+ /**
53
+ * Folded surface form (ISO code, English name, or — for CA — co-official French name) → subdivision. Built once. US
54
+ * states are inserted first and never overwritten, so on the (currently empty) event of a future code/name collision
55
+ * the US entry wins deterministically; today the two sets are disjoint.
56
+ */
57
+ const SUBDIVISION_LOOKUP: ReadonlyMap<string, SubdivisionMatch> = (() => {
58
+ const out = new Map<string, SubdivisionMatch>()
59
+ const put = (key: string, match: SubdivisionMatch): void => {
60
+ const folded = foldName(key)
61
+
62
+ if (folded.length > 0 && !out.has(folded)) {
63
+ out.set(folded, match)
64
+ }
65
+ }
66
+
67
+ for (const [code, name] of Object.entries(US_STATE_BY_ABBREVIATION)) {
68
+ const match: SubdivisionMatch = { code, name, country: "US" }
69
+ put(code, match)
70
+ put(name, match)
71
+ }
72
+
73
+ for (const info of Object.values(CA_PROVINCES)) {
74
+ const match: SubdivisionMatch = { code: info.code, name: info.name, country: "CA" }
75
+ put(info.code, match)
76
+ put(info.name, match)
77
+ put(info.french, match)
78
+ }
79
+
80
+ return out
81
+ })()
82
+
83
+ /**
84
+ * Resolve a first-level subdivision surface form (ISO 3166-2 code, English name, or co-official French name for CA;
85
+ * accents optional) to its `{ code, name, country }`. Case- and diacritic-insensitive. Returns null for anything that
86
+ * isn't a US state or Canadian province/territory — including bare country tokens (use {@link matchCountry} for
87
+ * those).
88
+ */
89
+ export function matchSubdivision(token: string | null | undefined): SubdivisionMatch | null {
90
+ if (!token || typeof token !== "string") return null
91
+
92
+ return SUBDIVISION_LOOKUP.get(foldName(token)) ?? null
93
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The 16 German federal states (Bundesländer), keyed by their ISO 3166-2:DE subdivision code.
7
+ *
8
+ * The US analog is `us/state.ts`. The informative difference: a US state's two-letter code (`CA`)
9
+ * is the USPS abbreviation people actually write in an address, whereas a German address almost
10
+ * never carries the Bundesland at all — it is `PLZ City`, and the state is inferred. So these
11
+ * codes matter for resolver region-matching and display, not for parsing the surface string.
12
+ */
13
+
14
+ /** Per-state record: ISO 3166-2:DE code, native German name, and the common English exonym. */
15
+ export interface GermanStateInfo {
16
+ /** ISO 3166-2:DE subdivision code without the `DE-` prefix (e.g. `BY` for `DE-BY`). */
17
+ code: string
18
+ /** Native German name (e.g. `Bayern`). */
19
+ name: string
20
+ /** Common English name (e.g. `Bavaria`). */
21
+ english: string
22
+ }
23
+
24
+ /**
25
+ * ISO 3166-2:DE code → state info, for all 16 Bundesländer. Codes are the official subdivision codes minus the `DE-`
26
+ * prefix.
27
+ */
28
+ export const DE_BUNDESLAENDER = {
29
+ BW: { code: "BW", name: "Baden-Württemberg", english: "Baden-Württemberg" },
30
+ BY: { code: "BY", name: "Bayern", english: "Bavaria" },
31
+ BE: { code: "BE", name: "Berlin", english: "Berlin" },
32
+ BB: { code: "BB", name: "Brandenburg", english: "Brandenburg" },
33
+ HB: { code: "HB", name: "Bremen", english: "Bremen" },
34
+ HH: { code: "HH", name: "Hamburg", english: "Hamburg" },
35
+ HE: { code: "HE", name: "Hessen", english: "Hesse" },
36
+ MV: { code: "MV", name: "Mecklenburg-Vorpommern", english: "Mecklenburg-Western Pomerania" },
37
+ NI: { code: "NI", name: "Niedersachsen", english: "Lower Saxony" },
38
+ NW: { code: "NW", name: "Nordrhein-Westfalen", english: "North Rhine-Westphalia" },
39
+ RP: { code: "RP", name: "Rheinland-Pfalz", english: "Rhineland-Palatinate" },
40
+ SL: { code: "SL", name: "Saarland", english: "Saarland" },
41
+ SN: { code: "SN", name: "Sachsen", english: "Saxony" },
42
+ ST: { code: "ST", name: "Sachsen-Anhalt", english: "Saxony-Anhalt" },
43
+ SH: { code: "SH", name: "Schleswig-Holstein", english: "Schleswig-Holstein" },
44
+ TH: { code: "TH", name: "Thüringen", english: "Thuringia" },
45
+ } as const satisfies Record<string, GermanStateInfo>
46
+
47
+ /** An ISO 3166-2:DE state code (`BW`, `BY`, `BE`, …). */
48
+ export type GermanStateCode = keyof typeof DE_BUNDESLAENDER
49
+
50
+ const STATE_CODE_SET: ReadonlySet<string> = new Set(Object.keys(DE_BUNDESLAENDER))
51
+
52
+ /** Type-predicate for an ISO 3166-2:DE state code. Case-insensitive. */
53
+ export function isGermanStateCode(input: unknown): input is GermanStateCode {
54
+ return typeof input === "string" && STATE_CODE_SET.has(input.toUpperCase())
55
+ }
56
+
57
+ /**
58
+ * Name (native German, English exonym, or common alias) → ISO 3166-2:DE code, lowercase-keyed. Includes the everyday
59
+ * aliases a parser actually meets: `NRW` for Nordrhein-Westfalen, `Bavaria` for Bayern, `Saxony` for Sachsen. The point
60
+ * is resolver region-matching: a German parse emits a region surface form, and the eval needs to map it to a code
61
+ * without a US-USPS-shaped matcher.
62
+ */
63
+ export const DE_STATE_NAME_TO_CODE: ReadonlyMap<string, GermanStateCode> = (() => {
64
+ const out = new Map<string, GermanStateCode>()
65
+
66
+ for (const code of Object.keys(DE_BUNDESLAENDER) as GermanStateCode[]) {
67
+ const info = DE_BUNDESLAENDER[code]
68
+ out.set(info.name.toLowerCase(), code)
69
+ out.set(info.english.toLowerCase(), code)
70
+ out.set(code.toLowerCase(), code)
71
+ }
72
+ // Everyday aliases that are neither the ISO code nor the canonical name.
73
+ const aliases: Record<string, GermanStateCode> = {
74
+ nrw: "NW",
75
+ "nordrhein westfalen": "NW",
76
+ "baden wurttemberg": "BW",
77
+ "baden-wuerttemberg": "BW",
78
+ "baden wuerttemberg": "BW",
79
+ thueringen: "TH",
80
+ "freie hansestadt bremen": "HB",
81
+ "freie und hansestadt hamburg": "HH",
82
+ }
83
+
84
+ for (const [alias, code] of Object.entries(aliases)) {
85
+ out.set(alias, code)
86
+ }
87
+
88
+ return out
89
+ })()
90
+
91
+ /**
92
+ * Resolve a German state surface form (code, German name, English name, or common alias) to its ISO 3166-2:DE code.
93
+ * Returns null when unrecognized.
94
+ */
95
+ export function lookupGermanState(input: string | null | undefined): GermanStateCode | null {
96
+ if (!input || typeof input !== "string") return null
97
+ const key = input.trim().toLowerCase()
98
+
99
+ if (STATE_CODE_SET.has(key.toUpperCase())) return key.toUpperCase() as GermanStateCode
100
+
101
+ return DE_STATE_NAME_TO_CODE.get(key) ?? null
102
+ }
package/de/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The German address system (Deutsche Post / ISO 3166-2:DE): street types, postal codes
7
+ * (Postleitzahl), and the federal states (Bundesländer).
8
+ */
9
+
10
+ export * from "./bundesland.ts"
11
+ export * from "./postleitzahl.ts"
12
+ export * from "./street-type.ts"
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * German postal codes (Postleitzahl, PLZ): the branded type, the shape, normalization, and the
7
+ * first-digit → Leitzone geographic prior.
8
+ *
9
+ * The US analog is `us/zipcode.ts`, and the contrast is the informative part. A US ZIP's first
10
+ * digit maps cleanly to a band of states (`StateAbbreviationZipCodePrefixRecord`). A German PLZ's
11
+ * first digit maps to a **Leitzone** — a postal routing region that deliberately **crosses
12
+ * Bundesland borders** (Leitzone 6 covers Frankfurt in Hessen, Saarbrücken in Saarland, and Mainz
13
+ * in Rheinland-Pfalz). So the PLZ prior narrows geography, but it does NOT narrow the state the
14
+ * way a US ZIP does — a lesson for any code that tries to derive a German region from a postcode
15
+ * alone.
16
+ */
17
+
18
+ import type { Tagged } from "type-fest"
19
+
20
+ /**
21
+ * A German postal code: five digits since the 1993 reform (`12623`). A bare 5-digit string, same shape as a US ZIP or a
22
+ * French code postal — disambiguation is the parser's job, not the shape's.
23
+ *
24
+ * @category Postal
25
+ * @type string
26
+ * @title Postleitzahl
27
+ * @pattern ^\d{5}$
28
+ */
29
+ export type Postleitzahl = Tagged<string, "Postleitzahl">
30
+
31
+ /** The PLZ shape: exactly five digits. */
32
+ export const PLZ_PATTERN = /^\d{5}$/
33
+
34
+ /**
35
+ * Normalize a PLZ surface form to the bare five digits: strip the `D-` / `DE-` country courtesy prefix and surrounding
36
+ * whitespace (`D-68161` → `68161`). Returns null if the result is not a PLZ.
37
+ */
38
+ export function normalizePLZ(raw: unknown): Postleitzahl | null {
39
+ if (typeof raw !== "string") return null
40
+ const s = raw.trim().toUpperCase().replace(/^DE?-/, "")
41
+
42
+ return PLZ_PATTERN.test(s) ? (s as Postleitzahl) : null
43
+ }
44
+
45
+ /** Type-predicate for a (normalized) German postal code. */
46
+ export function isPostleitzahl(input: unknown): input is Postleitzahl {
47
+ return typeof input === "string" && PLZ_PATTERN.test(input)
48
+ }
49
+
50
+ /** A PLZ Leitzone first digit. */
51
+ export type LeitzoneDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
52
+
53
+ /** Per-Leitzone descriptor: the routing region and a few anchor cities (coarse, postal not admin). */
54
+ export interface LeitzoneInfo {
55
+ digit: LeitzoneDigit
56
+ /** Coarse routing-region label. */
57
+ region: string
58
+ /** Well-known anchor cities in the zone (illustrative, not exhaustive). */
59
+ cities: readonly string[]
60
+ }
61
+
62
+ /**
63
+ * PLZ first digit → Leitzone. A coarse, postal-routing prior: these zones cross Bundesland borders, so the label is
64
+ * "which corner of Germany", not "which state". Anchor cities are the safe, well-known way to pin a zone without
65
+ * over-claiming a boundary the routing geography does not actually follow.
66
+ */
67
+ export const PLZ_LEITZONEN = {
68
+ 0: { digit: 0, region: "Sachsen / Ostthüringen", cities: ["Leipzig", "Dresden", "Chemnitz"] },
69
+ 1: { digit: 1, region: "Berlin / Brandenburg / Mecklenburg-Vorpommern", cities: ["Berlin", "Potsdam", "Rostock"] },
70
+ 2: { digit: 2, region: "Hamburg / Schleswig-Holstein / Bremen", cities: ["Hamburg", "Kiel", "Bremen"] },
71
+ 3: { digit: 3, region: "Niedersachsen / Nordhessen", cities: ["Hannover", "Braunschweig", "Kassel"] },
72
+ 4: { digit: 4, region: "nördliches Nordrhein-Westfalen", cities: ["Düsseldorf", "Dortmund", "Münster"] },
73
+ 5: { digit: 5, region: "südliches NRW / nördliches Rheinland-Pfalz", cities: ["Köln", "Bonn", "Aachen"] },
74
+ 6: {
75
+ digit: 6,
76
+ region: "Südhessen / Rheinland-Pfalz / Saarland",
77
+ cities: ["Frankfurt am Main", "Mainz", "Saarbrücken"],
78
+ },
79
+ 7: { digit: 7, region: "Baden-Württemberg", cities: ["Stuttgart", "Karlsruhe", "Freiburg"] },
80
+ 8: { digit: 8, region: "südliches Bayern", cities: ["München", "Augsburg", "Ingolstadt"] },
81
+ 9: { digit: 9, region: "nördliches Bayern / Oberpfalz", cities: ["Nürnberg", "Würzburg", "Regensburg"] },
82
+ } as const satisfies Record<LeitzoneDigit, LeitzoneInfo>
83
+
84
+ /** The Leitzone of a PLZ (its first digit's routing region), or null if the input is not a PLZ. */
85
+ export function leitzoneOf(plz: unknown): LeitzoneInfo | null {
86
+ const normalized = normalizePLZ(plz)
87
+
88
+ if (!normalized) return null
89
+
90
+ return PLZ_LEITZONEN[Number(normalized[0]) as LeitzoneDigit]
91
+ }