@mailwoman/codex 7.2.0 → 7.2.1

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 (57) 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 +14 -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/de/bundesland.ts +102 -0
  19. package/de/index.ts +12 -0
  20. package/de/postleitzahl.ts +91 -0
  21. package/de/street-type.ts +83 -0
  22. package/fr/cedex.ts +56 -0
  23. package/fr/code-postal.ts +105 -0
  24. package/fr/departement.ts +142 -0
  25. package/fr/index.ts +14 -0
  26. package/fr/region.ts +93 -0
  27. package/fr/voie.ts +98 -0
  28. package/gb/country.ts +74 -0
  29. package/gb/index.ts +14 -0
  30. package/gb/postcode-area.ts +107 -0
  31. package/gb/postcode.ts +109 -0
  32. package/gb/street-type.ts +90 -0
  33. package/index.ts +38 -0
  34. package/jp/address-unit.ts +87 -0
  35. package/jp/index.ts +13 -0
  36. package/jp/postal-code.ts +93 -0
  37. package/jp/prefecture.ts +173 -0
  38. package/level-semantics.ts +623 -0
  39. package/nz/delivery-service.ts +211 -0
  40. package/nz/index.ts +12 -0
  41. package/nz/postcode.ts +42 -0
  42. package/package.json +81 -37
  43. package/postcode-systems.ts +68 -0
  44. package/tools/build-country-surface-lexicon.ts +166 -0
  45. package/tools/export-country-surfaces.ts +46 -0
  46. package/tools/generate-country-reference.ts +153 -0
  47. package/tools/generate-official-languages.ts +188 -0
  48. package/tools/index.ts +12 -0
  49. package/us/floor-designator.ts +119 -0
  50. package/us/index.ts +19 -0
  51. package/us/military-address.ts +199 -0
  52. package/us/po-box.ts +82 -0
  53. package/us/state.ts +156 -0
  54. package/us/street-directional.ts +220 -0
  55. package/us/street-suffix.ts +345 -0
  56. package/us/unit-designator.ts +223 -0
  57. 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,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
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * German street types (Straßentypen).
7
+ *
8
+ * The US analog is `us/street-suffix.ts`, and the structural contrast is the whole lesson of this
9
+ * file. A US street type is a separate trailing word with a USPS-standardized abbreviation (`Main
10
+ * Street` → `ST`). A German street type is overwhelmingly an **agglutinative suffix** fused onto
11
+ * the name (`Straußstraße`, `Karl-Liebknecht-Straße`), with only one abbreviation in real use
12
+ * (`Str.`). So detection is by suffix, not by a trailing token, and there is no Pub-28-style
13
+ * abbreviation table to salvage.
14
+ *
15
+ * The second lesson is the collision, the German cousin of the US `KY` = Key / Kentucky problem.
16
+ * Many German PLACE names end in what look like street suffixes — `-berg` (Nürnberg), `-burg`
17
+ * (Hamburg), `-dorf` (Düsseldorf), `-feld`, `-hof`, `-stadt`. If those counted as street markers,
18
+ * the city token in a `PLZ City` segment would masquerade as a street and wrongly flag the
19
+ * postcode as a house number. {@link DE_STREET_SUFFIXES} is therefore a curated, place-name-SAFE
20
+ * set — the suffixes that are distinctively streets and (almost) never the tail of a city name.
21
+ */
22
+
23
+ /**
24
+ * Canonical German street type → recognized written variants (including the `Str.` abbreviation and the `ß`/`ss`
25
+ * spelling split). The full reference table, used for synthesis/expansion. For the "is this token part of a street"
26
+ * test, use {@link DE_STREET_SUFFIXES} / {@link isGermanStreetToken}, which exclude the place-name-colliding suffixes.
27
+ */
28
+ export const DE_STREET_TYPE_VARIANTS = {
29
+ Straße: ["Str.", "Str", "Strasse"],
30
+ Weg: [],
31
+ Platz: ["Pl.", "Pl"],
32
+ Gasse: [],
33
+ Allee: [],
34
+ Ring: [],
35
+ Damm: [],
36
+ Ufer: [],
37
+ Steig: [],
38
+ Stieg: [],
39
+ Pfad: [],
40
+ Chaussee: ["Chaussée"],
41
+ Twiete: [],
42
+ } as const satisfies Record<string, readonly string[]>
43
+
44
+ /** A canonical German street type (e.g. `Straße`, `Weg`, `Platz`). */
45
+ export type GermanStreetType = keyof typeof DE_STREET_TYPE_VARIANTS
46
+
47
+ /**
48
+ * Place-name-SAFE street suffixes for "is this token part of a street" detection, lowercase, with the `ß`/`ss` split
49
+ * spelled out and `str` for the `Str.` abbreviation. Deliberately EXCLUDES the suffixes that also end German city names
50
+ * — `-berg`, `-burg`, `-dorf`, `-feld`, `-hof`, `-stadt`, `-heim`, `-bach`, `-tal` — so a city token in a `PLZ City`
51
+ * segment is not mistaken for a street.
52
+ */
53
+ export const DE_STREET_SUFFIXES = [
54
+ "straße",
55
+ "strasse",
56
+ "str",
57
+ "weg",
58
+ "platz",
59
+ "gasse",
60
+ "allee",
61
+ "ring",
62
+ "damm",
63
+ "ufer",
64
+ "steig",
65
+ "stieg",
66
+ "pfad",
67
+ "chaussee",
68
+ "twiete",
69
+ ] as const
70
+
71
+ /**
72
+ * True when a token reads as (part of) a German street: it ends with one of the place-name-safe
73
+ * {@link DE_STREET_SUFFIXES}. Handles both the fused compound (`Straußstraße` → ends `straße`) and the standalone type
74
+ * word or `Str.` abbreviation (`Platz`, `Str` → end `platz` / `str`).
75
+ */
76
+ export function isGermanStreetToken(token: unknown): boolean {
77
+ if (typeof token !== "string") return false
78
+ const t = token.toLowerCase().replace(/[^a-zà-ÿß]/g, "")
79
+
80
+ if (t.length < 3) return false
81
+
82
+ return DE_STREET_SUFFIXES.some((s) => t.endsWith(s))
83
+ }