@escape-game-over/atlas 0.1.4 → 0.1.5

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.
package/docs/NOT-BUILT.md CHANGED
@@ -180,20 +180,28 @@ exactly the set displayed — never a number typed in beside a shorter list.
180
180
 
181
181
  ### Should `CurrencyCode` and `CountryCode` spell out the ISO lists?
182
182
 
183
- **Not now.**
183
+ **`CountryCode` now does. `CurrencyCode` still does not, for the reason that
184
+ used to cover both.**
184
185
 
185
- Both are shapes rather than vocabularies — `` `${Letter}${Letter}${Letter}` ``
186
- and `` `${Letter}${Letter}` `` — so `"XYZ"` and `"ZZ"` type-check. ISO 4217 has
186
+ Both were shapes rather than vocabularies — `` `${Letter}${Letter}${Letter}` ``
187
+ and `` `${Letter}${Letter}` `` — so `"XYZ"` and `"ZZ"` type-checked. ISO 4217 has
187
188
  around 180 active codes and ISO 3166-1 alpha-2 has 249; both are small, closed
188
189
  and effectively frozen, so enumerating them is possible in a way the [IANA
189
190
  language registry](https://www.iana.org/assignments/language-subtag-registry) is
190
191
  not — which is the case `LanguageTag` in `config.ts` declines and says why.
191
192
 
192
- Left as shapes because the mistake is not one anyone has made: a currency is
193
- written once per project, beside the prices it applies to, and a wrong one is
194
- visible the first time a page renders. That is a different risk from a typo in a
195
- directive nobody reads. Worth revisiting if a project ever computes a code
196
- rather than writing it.
193
+ What moved is not the risk, it is the reason to hold the list at all. A country
194
+ menu needs the codes *as data* `countriesFor` renders and sorts them per
195
+ locale and once the list exists, deriving the type from it is free and the
196
+ shape is strictly worse: it admitted 676 strings for 250 countries, including
197
+ the withdrawn codes (`SU`, `YU`, `AN`) that look plausible enough to be typed by
198
+ accident. So `CountryCode` is now `(typeof COUNTRY_CODES)[number]`, defined in
199
+ `countries.ts` and re-exported from `contact.ts` beside `PostalAddress`.
200
+
201
+ `CurrencyCode` stays a shape, because nothing needs a currency *list*: a
202
+ currency is written once per project, beside the prices it applies to, and a
203
+ wrong one is visible the first time a page renders. Enumerate it the day
204
+ something renders a currency picker, and not before.
197
205
 
198
206
  ### Should `@type` be checked against schema.org's vocabulary?
199
207
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
package/src/contact.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { Digit, Letter } from "./types.ts";
1
+ import type { CountryCode } from "./countries.ts";
2
+ import type { Digit } from "./types.ts";
2
3
  import { warn } from "./warn.ts";
3
4
 
4
5
  /**
@@ -129,13 +130,13 @@ export function mailtoHref(email: EmailAddress): string {
129
130
  }
130
131
 
131
132
  /**
132
- * An ISO 3166-1 alpha-2 country code: `"IT"`, `"GR"`, `"RO"`.
133
+ * Re-exported so an address and its country code stay one import.
133
134
  *
134
- * Two uppercase letters, which is what `addressCountry` is read as. A name
135
- * `"Italy"`, `"Ιταλία"` is a translation of a country rather than an
136
- * identifier for one, and belongs in the sentence a page prints, not here.
135
+ * Defined beside the list it is read from see `countries.ts`. Imported at the
136
+ * top as well: `export … from` forwards a name without binding it here, and
137
+ * `PostalAddress` below needs it in scope.
137
138
  */
138
- export type CountryCode = `${Letter}${Letter}`;
139
+ export type { CountryCode };
139
140
 
140
141
  /**
141
142
  * Where a place is, to the precision a map needs.
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Every country a form can offer, as ISO 3166-1 alpha-2.
3
+ *
4
+ * Codes rather than names, which is the point on a multilingual site:
5
+ * `countriesFor` turns each one into the reader's own word for it and sorts
6
+ * them the way that language sorts. A list of English names would put "Czech
7
+ * Republic" in front of a Romanian reader and file it under C.
8
+ *
9
+ * The code is also what a form should submit, so an enquiry from Germany files
10
+ * the same whether it was picked as "Deutschland" or "Germany".
11
+ *
12
+ * Derived from ICU's region list rather than typed out: every two-letter code
13
+ * it answers to, less the withdrawn and transitional reservations it still
14
+ * knows (`SU`, `YU`, `DD`, `ZR`, `AN`, `UK`…) and the aggregates that are not
15
+ * countries (`EU`, `UN`, `QO`…). 250 entries: the 249 ISO assigns, plus `XK`
16
+ * for Kosovo, which is user-assigned and in common use.
17
+ *
18
+ * **This is a list, not a policy.** It says which codes are well-formed, not
19
+ * which countries a business will trade with — that is a per-project answer and
20
+ * belongs in the project, by filtering this.
21
+ */
22
+ export const COUNTRY_CODES = [
23
+ "AD",
24
+ "AE",
25
+ "AF",
26
+ "AG",
27
+ "AI",
28
+ "AL",
29
+ "AM",
30
+ "AO",
31
+ "AQ",
32
+ "AR",
33
+ "AS",
34
+ "AT",
35
+ "AU",
36
+ "AW",
37
+ "AX",
38
+ "AZ",
39
+ "BA",
40
+ "BB",
41
+ "BD",
42
+ "BE",
43
+ "BF",
44
+ "BG",
45
+ "BH",
46
+ "BI",
47
+ "BJ",
48
+ "BL",
49
+ "BM",
50
+ "BN",
51
+ "BO",
52
+ "BQ",
53
+ "BR",
54
+ "BS",
55
+ "BT",
56
+ "BV",
57
+ "BW",
58
+ "BY",
59
+ "BZ",
60
+ "CA",
61
+ "CC",
62
+ "CD",
63
+ "CF",
64
+ "CG",
65
+ "CH",
66
+ "CI",
67
+ "CK",
68
+ "CL",
69
+ "CM",
70
+ "CN",
71
+ "CO",
72
+ "CR",
73
+ "CU",
74
+ "CV",
75
+ "CW",
76
+ "CX",
77
+ "CY",
78
+ "CZ",
79
+ "DE",
80
+ "DJ",
81
+ "DK",
82
+ "DM",
83
+ "DO",
84
+ "DZ",
85
+ "EC",
86
+ "EE",
87
+ "EG",
88
+ "EH",
89
+ "ER",
90
+ "ES",
91
+ "ET",
92
+ "FI",
93
+ "FJ",
94
+ "FK",
95
+ "FM",
96
+ "FO",
97
+ "FR",
98
+ "GA",
99
+ "GB",
100
+ "GD",
101
+ "GE",
102
+ "GF",
103
+ "GG",
104
+ "GH",
105
+ "GI",
106
+ "GL",
107
+ "GM",
108
+ "GN",
109
+ "GP",
110
+ "GQ",
111
+ "GR",
112
+ "GS",
113
+ "GT",
114
+ "GU",
115
+ "GW",
116
+ "GY",
117
+ "HK",
118
+ "HM",
119
+ "HN",
120
+ "HR",
121
+ "HT",
122
+ "HU",
123
+ "ID",
124
+ "IE",
125
+ "IL",
126
+ "IM",
127
+ "IN",
128
+ "IO",
129
+ "IQ",
130
+ "IR",
131
+ "IS",
132
+ "IT",
133
+ "JE",
134
+ "JM",
135
+ "JO",
136
+ "JP",
137
+ "KE",
138
+ "KG",
139
+ "KH",
140
+ "KI",
141
+ "KM",
142
+ "KN",
143
+ "KP",
144
+ "KR",
145
+ "KW",
146
+ "KY",
147
+ "KZ",
148
+ "LA",
149
+ "LB",
150
+ "LC",
151
+ "LI",
152
+ "LK",
153
+ "LR",
154
+ "LS",
155
+ "LT",
156
+ "LU",
157
+ "LV",
158
+ "LY",
159
+ "MA",
160
+ "MC",
161
+ "MD",
162
+ "ME",
163
+ "MF",
164
+ "MG",
165
+ "MH",
166
+ "MK",
167
+ "ML",
168
+ "MM",
169
+ "MN",
170
+ "MO",
171
+ "MP",
172
+ "MQ",
173
+ "MR",
174
+ "MS",
175
+ "MT",
176
+ "MU",
177
+ "MV",
178
+ "MW",
179
+ "MX",
180
+ "MY",
181
+ "MZ",
182
+ "NA",
183
+ "NC",
184
+ "NE",
185
+ "NF",
186
+ "NG",
187
+ "NI",
188
+ "NL",
189
+ "NO",
190
+ "NP",
191
+ "NR",
192
+ "NU",
193
+ "NZ",
194
+ "OM",
195
+ "PA",
196
+ "PE",
197
+ "PF",
198
+ "PG",
199
+ "PH",
200
+ "PK",
201
+ "PL",
202
+ "PM",
203
+ "PN",
204
+ "PR",
205
+ "PS",
206
+ "PT",
207
+ "PW",
208
+ "PY",
209
+ "QA",
210
+ "RE",
211
+ "RO",
212
+ "RS",
213
+ "RU",
214
+ "RW",
215
+ "SA",
216
+ "SB",
217
+ "SC",
218
+ "SD",
219
+ "SE",
220
+ "SG",
221
+ "SH",
222
+ "SI",
223
+ "SJ",
224
+ "SK",
225
+ "SL",
226
+ "SM",
227
+ "SN",
228
+ "SO",
229
+ "SR",
230
+ "SS",
231
+ "ST",
232
+ "SV",
233
+ "SX",
234
+ "SY",
235
+ "SZ",
236
+ "TC",
237
+ "TD",
238
+ "TF",
239
+ "TG",
240
+ "TH",
241
+ "TJ",
242
+ "TK",
243
+ "TL",
244
+ "TM",
245
+ "TN",
246
+ "TO",
247
+ "TR",
248
+ "TT",
249
+ "TV",
250
+ "TW",
251
+ "TZ",
252
+ "UA",
253
+ "UG",
254
+ "UM",
255
+ "US",
256
+ "UY",
257
+ "UZ",
258
+ "VA",
259
+ "VC",
260
+ "VE",
261
+ "VG",
262
+ "VI",
263
+ "VN",
264
+ "VU",
265
+ "WF",
266
+ "WS",
267
+ "XK",
268
+ "YE",
269
+ "YT",
270
+ "ZA",
271
+ "ZM",
272
+ "ZW",
273
+ ] as const;
274
+
275
+ /**
276
+ * An ISO 3166-1 alpha-2 country code: `"IT"`, `"GR"`, `"RO"`.
277
+ *
278
+ * Read off the list above rather than described as a shape. `${Letter}${Letter}`
279
+ * admits all 676 two-letter strings, of which some 400 are not countries, so it
280
+ * accepted `"XX"` and every typo that happened to be two capitals. This accepts
281
+ * the 250 that exist and nothing else — including refusing the withdrawn codes
282
+ * (`SU`, `YU`, `AN`), which look plausible and are the ones worth catching.
283
+ *
284
+ * A name — `"Italy"`, `"Ιταλία"` — is a translation of a country rather than an
285
+ * identifier for one, and belongs in the sentence a page prints. `countriesFor`
286
+ * is what turns one of these into that.
287
+ */
288
+ export type CountryCode = (typeof COUNTRY_CODES)[number];
289
+
290
+ /** One country, in one language. */
291
+ export interface NamedCountry {
292
+ readonly code: CountryCode;
293
+ readonly name: string;
294
+ }
295
+
296
+ /**
297
+ * The list as one locale reads it: each code with its own name, A to Z in that
298
+ * language.
299
+ *
300
+ * Both halves are locale-dependent, so neither can be cached across locales —
301
+ * "Ägypten" sorts second in German and "Egypt" fifth in English, and a build
302
+ * that sorted once would ship one language's order to all of them.
303
+ *
304
+ * `Intl.DisplayNames` falls back to the code itself for anything it does not
305
+ * know, which is what keeps this total: an option always has a label.
306
+ */
307
+ export function countriesFor(locale: string): readonly NamedCountry[] {
308
+ const names = new Intl.DisplayNames([locale], { type: "region" });
309
+ const collator = new Intl.Collator(locale);
310
+ return COUNTRY_CODES.map((code) => ({
311
+ code,
312
+ name: names.of(code) ?? code,
313
+ })).sort((a, b) => collator.compare(a.name, b.name));
314
+ }
package/src/index.ts CHANGED
@@ -60,6 +60,11 @@ export {
60
60
  type MailEndpoint,
61
61
  sendContactMessage,
62
62
  } from "./contact-form.ts";
63
+ export {
64
+ COUNTRY_CODES,
65
+ countriesFor,
66
+ type NamedCountry,
67
+ } from "./countries.ts";
63
68
  export type { GeneratedFile } from "./file.ts";
64
69
  export type { PublicFilePath, PublicFileRegistry } from "./files.ts";
65
70
  export {