@escape-game-over/atlas 0.1.73 → 0.1.75
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/package.json +2 -2
- package/src/address-formats.ts +769 -0
- package/src/contact.ts +93 -2
- package/src/index.ts +5 -0
- package/src/jsonld/place.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escape-game-over/atlas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.75",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
|
|
6
6
|
"private": false,
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@biomejs/biome": "2.5.15",
|
|
61
|
-
"@types/node": "26.6.
|
|
61
|
+
"@types/node": "26.6.4",
|
|
62
62
|
"@vitest/coverage-istanbul": "5.0.3",
|
|
63
63
|
"astro": "7.3.5",
|
|
64
64
|
"typescript": "6.0.3",
|
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
import type { CountryCode } from "./countries.ts";
|
|
2
|
+
|
|
3
|
+
/** A part of `PostalAddress` an address layout can print. */
|
|
4
|
+
export type AddressField = "street" | "locality" | "region" | "postalCode";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One piece of a line: a field, or text the country always prints.
|
|
8
|
+
*
|
|
9
|
+
* A field brings its own punctuation, so a missing field takes it with it and
|
|
10
|
+
* nothing is left to clean up:
|
|
11
|
+
*
|
|
12
|
+
* - `separator` sits between this field and whatever the line already holds,
|
|
13
|
+
* and is dropped when the field opens the line: `, ` before a US state.
|
|
14
|
+
* - `prefix` belongs to the value itself and goes wherever it goes: the `CH-`
|
|
15
|
+
* of a Swiss postal code.
|
|
16
|
+
*/
|
|
17
|
+
export type AddressPart =
|
|
18
|
+
| {
|
|
19
|
+
readonly field: AddressField;
|
|
20
|
+
readonly separator?: string;
|
|
21
|
+
readonly prefix?: string;
|
|
22
|
+
}
|
|
23
|
+
| { readonly text: string };
|
|
24
|
+
|
|
25
|
+
/** A country's address, line by line. */
|
|
26
|
+
export type AddressLayout = readonly (readonly AddressPart[])[];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The script an address is written in: the country's own, or Latin letters.
|
|
30
|
+
* Some countries order the two differently: `東京都` before the street, but
|
|
31
|
+
* `Tokyo` after it.
|
|
32
|
+
*/
|
|
33
|
+
export type AddressScript = "local" | "latin";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* How a country lays out an address written in `script`: its Latin-script
|
|
37
|
+
* layout where it has one of its own, else the local one.
|
|
38
|
+
*/
|
|
39
|
+
export function addressLayout(
|
|
40
|
+
country: CountryCode,
|
|
41
|
+
script: AddressScript = "local"
|
|
42
|
+
): AddressLayout {
|
|
43
|
+
return (
|
|
44
|
+
(script === "latin" ? latinAddressLayout(country) : undefined) ??
|
|
45
|
+
localAddressLayout(country)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* How a country lays out an address in its own script.
|
|
51
|
+
*
|
|
52
|
+
* Converted once from Google's address metadata, the dataset behind
|
|
53
|
+
* libaddressinput (Apache License 2.0), fetched 2026-10-02 one country at a
|
|
54
|
+
* time: <https://chromium-i18n.appspot.com/ssl-address/data/DE>, the `fmt`
|
|
55
|
+
* field. The bare `/data/` path answers `{}`. Google writes each layout as a
|
|
56
|
+
* format string, `%A%n%C, %S %Z`; this is the same layout as data, so nothing
|
|
57
|
+
* parses a string at run time.
|
|
58
|
+
*
|
|
59
|
+
* The conversion:
|
|
60
|
+
* - `%A` street
|
|
61
|
+
* - `%C` locality
|
|
62
|
+
* - `%S` region
|
|
63
|
+
* - `%Z` postal code
|
|
64
|
+
* became fields; text before a line's first field became that field's
|
|
65
|
+
* `prefix`, and text between two fields the second one's `separator`; a line of
|
|
66
|
+
* text alone became a `text` part. The recipient lines (`%N`, `%O`) and the
|
|
67
|
+
* fields `PostalAddress` does not carry — district, sorting code, India's
|
|
68
|
+
* extra lines — were dropped, with the punctuation that belonged to them.
|
|
69
|
+
*
|
|
70
|
+
* Countries that share a layout share a branch, in order of their first code.
|
|
71
|
+
* To update one, take its format from that address again and convert it the
|
|
72
|
+
* same way. A country listed twice is caught by Biome's `noDuplicateCase`.
|
|
73
|
+
*/
|
|
74
|
+
function localAddressLayout(country: CountryCode): AddressLayout {
|
|
75
|
+
switch (country) {
|
|
76
|
+
case "AD":
|
|
77
|
+
case "AT":
|
|
78
|
+
case "BA":
|
|
79
|
+
case "BE":
|
|
80
|
+
case "BG":
|
|
81
|
+
case "BL":
|
|
82
|
+
case "CY":
|
|
83
|
+
case "CZ":
|
|
84
|
+
case "DE":
|
|
85
|
+
case "DK":
|
|
86
|
+
case "DO":
|
|
87
|
+
case "DZ":
|
|
88
|
+
case "EH":
|
|
89
|
+
case "ET":
|
|
90
|
+
case "FR":
|
|
91
|
+
case "GE":
|
|
92
|
+
case "GF":
|
|
93
|
+
case "GL":
|
|
94
|
+
case "GP":
|
|
95
|
+
case "GR":
|
|
96
|
+
case "GW":
|
|
97
|
+
case "IS":
|
|
98
|
+
case "KG":
|
|
99
|
+
case "KW":
|
|
100
|
+
case "LA":
|
|
101
|
+
case "LR":
|
|
102
|
+
case "MA":
|
|
103
|
+
case "ME":
|
|
104
|
+
case "MF":
|
|
105
|
+
case "MG":
|
|
106
|
+
case "MK":
|
|
107
|
+
case "MQ":
|
|
108
|
+
case "NC":
|
|
109
|
+
case "NE":
|
|
110
|
+
case "NL":
|
|
111
|
+
case "NO":
|
|
112
|
+
case "PL":
|
|
113
|
+
case "PM":
|
|
114
|
+
case "PT":
|
|
115
|
+
case "PY":
|
|
116
|
+
case "RE":
|
|
117
|
+
case "RS":
|
|
118
|
+
case "SJ":
|
|
119
|
+
case "SK":
|
|
120
|
+
case "SM":
|
|
121
|
+
case "SN":
|
|
122
|
+
case "TJ":
|
|
123
|
+
case "TM":
|
|
124
|
+
case "TN":
|
|
125
|
+
case "TZ":
|
|
126
|
+
case "VA":
|
|
127
|
+
case "WF":
|
|
128
|
+
case "XK":
|
|
129
|
+
case "YT":
|
|
130
|
+
case "ZM":
|
|
131
|
+
return [
|
|
132
|
+
[{ field: "street" }],
|
|
133
|
+
[
|
|
134
|
+
{ field: "postalCode" },
|
|
135
|
+
{ field: "locality", separator: " " },
|
|
136
|
+
],
|
|
137
|
+
];
|
|
138
|
+
case "AE":
|
|
139
|
+
case "NR":
|
|
140
|
+
return [[{ field: "street" }], [{ field: "region" }]];
|
|
141
|
+
case "AF":
|
|
142
|
+
case "AI":
|
|
143
|
+
case "FK":
|
|
144
|
+
case "GB":
|
|
145
|
+
case "GS":
|
|
146
|
+
case "IM":
|
|
147
|
+
case "IO":
|
|
148
|
+
case "KE":
|
|
149
|
+
case "LK":
|
|
150
|
+
case "NA":
|
|
151
|
+
case "PN":
|
|
152
|
+
case "SD":
|
|
153
|
+
case "SH":
|
|
154
|
+
case "SZ":
|
|
155
|
+
case "TC":
|
|
156
|
+
case "TT":
|
|
157
|
+
case "VG":
|
|
158
|
+
case "ZA":
|
|
159
|
+
return [
|
|
160
|
+
[{ field: "street" }],
|
|
161
|
+
[{ field: "locality" }],
|
|
162
|
+
[{ field: "postalCode" }],
|
|
163
|
+
];
|
|
164
|
+
case "AL":
|
|
165
|
+
case "EC":
|
|
166
|
+
case "MU":
|
|
167
|
+
case "OM":
|
|
168
|
+
return [
|
|
169
|
+
[{ field: "street" }],
|
|
170
|
+
[{ field: "postalCode" }],
|
|
171
|
+
[{ field: "locality" }],
|
|
172
|
+
];
|
|
173
|
+
case "AM":
|
|
174
|
+
return [
|
|
175
|
+
[{ field: "street" }],
|
|
176
|
+
[{ field: "postalCode" }],
|
|
177
|
+
[{ field: "locality" }],
|
|
178
|
+
[{ field: "region" }],
|
|
179
|
+
];
|
|
180
|
+
case "AR":
|
|
181
|
+
case "CL":
|
|
182
|
+
case "CV":
|
|
183
|
+
case "MY":
|
|
184
|
+
case "UZ":
|
|
185
|
+
return [
|
|
186
|
+
[{ field: "street" }],
|
|
187
|
+
[
|
|
188
|
+
{ field: "postalCode" },
|
|
189
|
+
{ field: "locality", separator: " " },
|
|
190
|
+
],
|
|
191
|
+
[{ field: "region" }],
|
|
192
|
+
];
|
|
193
|
+
case "AS":
|
|
194
|
+
case "AU":
|
|
195
|
+
case "CA":
|
|
196
|
+
case "CC":
|
|
197
|
+
case "CX":
|
|
198
|
+
case "FM":
|
|
199
|
+
case "HM":
|
|
200
|
+
case "MH":
|
|
201
|
+
case "MP":
|
|
202
|
+
case "NF":
|
|
203
|
+
case "PW":
|
|
204
|
+
case "UM":
|
|
205
|
+
case "VI":
|
|
206
|
+
return [
|
|
207
|
+
[{ field: "street" }],
|
|
208
|
+
[
|
|
209
|
+
{ field: "locality" },
|
|
210
|
+
{ field: "region", separator: " " },
|
|
211
|
+
{ field: "postalCode", separator: " " },
|
|
212
|
+
],
|
|
213
|
+
];
|
|
214
|
+
case "AX":
|
|
215
|
+
return [
|
|
216
|
+
[{ field: "street" }],
|
|
217
|
+
[
|
|
218
|
+
{ field: "postalCode", prefix: "AX-" },
|
|
219
|
+
{ field: "locality", separator: " " },
|
|
220
|
+
],
|
|
221
|
+
[{ text: "ÅLAND" }],
|
|
222
|
+
];
|
|
223
|
+
case "AZ":
|
|
224
|
+
return [
|
|
225
|
+
[{ field: "street" }],
|
|
226
|
+
[
|
|
227
|
+
{ field: "postalCode", prefix: "AZ " },
|
|
228
|
+
{ field: "locality", separator: " " },
|
|
229
|
+
],
|
|
230
|
+
];
|
|
231
|
+
case "BB":
|
|
232
|
+
case "SO":
|
|
233
|
+
case "US":
|
|
234
|
+
return [
|
|
235
|
+
[{ field: "street" }],
|
|
236
|
+
[
|
|
237
|
+
{ field: "locality" },
|
|
238
|
+
{ field: "region", separator: ", " },
|
|
239
|
+
{ field: "postalCode", separator: " " },
|
|
240
|
+
],
|
|
241
|
+
];
|
|
242
|
+
case "BD":
|
|
243
|
+
return [
|
|
244
|
+
[{ field: "street" }],
|
|
245
|
+
[
|
|
246
|
+
{ field: "locality" },
|
|
247
|
+
{ field: "postalCode", separator: " - " },
|
|
248
|
+
],
|
|
249
|
+
];
|
|
250
|
+
case "BH":
|
|
251
|
+
case "BM":
|
|
252
|
+
case "BN":
|
|
253
|
+
case "BT":
|
|
254
|
+
case "GU":
|
|
255
|
+
case "IL":
|
|
256
|
+
case "JO":
|
|
257
|
+
case "KH":
|
|
258
|
+
case "LB":
|
|
259
|
+
case "LS":
|
|
260
|
+
case "MT":
|
|
261
|
+
case "MV":
|
|
262
|
+
case "NP":
|
|
263
|
+
case "NZ":
|
|
264
|
+
case "SA":
|
|
265
|
+
case "VC":
|
|
266
|
+
return [
|
|
267
|
+
[{ field: "street" }],
|
|
268
|
+
[
|
|
269
|
+
{ field: "locality" },
|
|
270
|
+
{ field: "postalCode", separator: " " },
|
|
271
|
+
],
|
|
272
|
+
];
|
|
273
|
+
case "BR":
|
|
274
|
+
return [
|
|
275
|
+
[{ field: "street" }],
|
|
276
|
+
[{ field: "locality" }, { field: "region", separator: "-" }],
|
|
277
|
+
[{ field: "postalCode" }],
|
|
278
|
+
];
|
|
279
|
+
case "BS":
|
|
280
|
+
case "KN":
|
|
281
|
+
return [
|
|
282
|
+
[{ field: "street" }],
|
|
283
|
+
[{ field: "locality" }, { field: "region", separator: ", " }],
|
|
284
|
+
];
|
|
285
|
+
case "BY":
|
|
286
|
+
return [
|
|
287
|
+
[{ field: "street" }],
|
|
288
|
+
[
|
|
289
|
+
{ field: "postalCode" },
|
|
290
|
+
{ field: "locality", separator: ", " },
|
|
291
|
+
],
|
|
292
|
+
[{ field: "region" }],
|
|
293
|
+
];
|
|
294
|
+
case "CH":
|
|
295
|
+
return [
|
|
296
|
+
[{ field: "street" }],
|
|
297
|
+
[
|
|
298
|
+
{ field: "postalCode", prefix: "CH-" },
|
|
299
|
+
{ field: "locality", separator: " " },
|
|
300
|
+
],
|
|
301
|
+
];
|
|
302
|
+
case "CI":
|
|
303
|
+
return [
|
|
304
|
+
[{ field: "street" }, { field: "locality", separator: " " }],
|
|
305
|
+
];
|
|
306
|
+
// No space between region and town, as both write them.
|
|
307
|
+
case "CN":
|
|
308
|
+
case "TW":
|
|
309
|
+
return [
|
|
310
|
+
[{ field: "postalCode" }],
|
|
311
|
+
[{ field: "region" }, { field: "locality" }],
|
|
312
|
+
[{ field: "street" }],
|
|
313
|
+
];
|
|
314
|
+
case "CO":
|
|
315
|
+
return [
|
|
316
|
+
[{ field: "street" }],
|
|
317
|
+
[
|
|
318
|
+
{ field: "locality" },
|
|
319
|
+
{ field: "region", separator: ", " },
|
|
320
|
+
{ field: "postalCode", separator: ", " },
|
|
321
|
+
],
|
|
322
|
+
];
|
|
323
|
+
case "CR":
|
|
324
|
+
return [
|
|
325
|
+
[{ field: "street" }],
|
|
326
|
+
[{ field: "region" }, { field: "locality", separator: ", " }],
|
|
327
|
+
[{ field: "postalCode" }],
|
|
328
|
+
];
|
|
329
|
+
case "CU":
|
|
330
|
+
return [
|
|
331
|
+
[{ field: "street" }],
|
|
332
|
+
[{ field: "locality" }, { field: "region", separator: " " }],
|
|
333
|
+
[{ field: "postalCode" }],
|
|
334
|
+
];
|
|
335
|
+
case "EE":
|
|
336
|
+
case "ES":
|
|
337
|
+
case "IT":
|
|
338
|
+
case "PF":
|
|
339
|
+
case "UY":
|
|
340
|
+
return [
|
|
341
|
+
[{ field: "street" }],
|
|
342
|
+
[
|
|
343
|
+
{ field: "postalCode" },
|
|
344
|
+
{ field: "locality", separator: " " },
|
|
345
|
+
{ field: "region", separator: " " },
|
|
346
|
+
],
|
|
347
|
+
];
|
|
348
|
+
case "EG":
|
|
349
|
+
case "IE":
|
|
350
|
+
case "RU":
|
|
351
|
+
case "UA":
|
|
352
|
+
return [
|
|
353
|
+
[{ field: "street" }],
|
|
354
|
+
[{ field: "locality" }],
|
|
355
|
+
[{ field: "region" }],
|
|
356
|
+
[{ field: "postalCode" }],
|
|
357
|
+
];
|
|
358
|
+
case "FI":
|
|
359
|
+
return [
|
|
360
|
+
[{ field: "street" }],
|
|
361
|
+
[
|
|
362
|
+
{ field: "postalCode", prefix: "FI-" },
|
|
363
|
+
{ field: "locality", separator: " " },
|
|
364
|
+
],
|
|
365
|
+
];
|
|
366
|
+
case "FO":
|
|
367
|
+
return [
|
|
368
|
+
[{ field: "street" }],
|
|
369
|
+
[
|
|
370
|
+
{ field: "postalCode", prefix: "FO" },
|
|
371
|
+
{ field: "locality", separator: " " },
|
|
372
|
+
],
|
|
373
|
+
];
|
|
374
|
+
case "GG":
|
|
375
|
+
return [
|
|
376
|
+
[{ field: "street" }],
|
|
377
|
+
[{ field: "locality" }],
|
|
378
|
+
[{ text: "GUERNSEY" }],
|
|
379
|
+
[{ field: "postalCode" }],
|
|
380
|
+
];
|
|
381
|
+
case "GI":
|
|
382
|
+
return [
|
|
383
|
+
[{ field: "street" }],
|
|
384
|
+
[{ text: "GIBRALTAR" }],
|
|
385
|
+
[{ field: "postalCode" }],
|
|
386
|
+
];
|
|
387
|
+
case "GN":
|
|
388
|
+
return [
|
|
389
|
+
[
|
|
390
|
+
{ field: "postalCode" },
|
|
391
|
+
{ field: "street", separator: " " },
|
|
392
|
+
{ field: "locality", separator: " " },
|
|
393
|
+
],
|
|
394
|
+
];
|
|
395
|
+
case "GT":
|
|
396
|
+
return [
|
|
397
|
+
[{ field: "street" }],
|
|
398
|
+
[
|
|
399
|
+
{ field: "postalCode" },
|
|
400
|
+
{ field: "locality", separator: "- " },
|
|
401
|
+
],
|
|
402
|
+
];
|
|
403
|
+
case "HK":
|
|
404
|
+
return [
|
|
405
|
+
[{ field: "region" }],
|
|
406
|
+
[{ field: "locality" }],
|
|
407
|
+
[{ field: "street" }],
|
|
408
|
+
];
|
|
409
|
+
case "HN":
|
|
410
|
+
case "IQ":
|
|
411
|
+
return [
|
|
412
|
+
[{ field: "street" }],
|
|
413
|
+
[{ field: "locality" }, { field: "region", separator: ", " }],
|
|
414
|
+
[{ field: "postalCode" }],
|
|
415
|
+
];
|
|
416
|
+
case "HR":
|
|
417
|
+
return [
|
|
418
|
+
[{ field: "street" }],
|
|
419
|
+
[
|
|
420
|
+
{ field: "postalCode", prefix: "HR-" },
|
|
421
|
+
{ field: "locality", separator: " " },
|
|
422
|
+
],
|
|
423
|
+
];
|
|
424
|
+
case "HT":
|
|
425
|
+
return [
|
|
426
|
+
[{ field: "street" }],
|
|
427
|
+
[
|
|
428
|
+
{ field: "postalCode", prefix: "HT" },
|
|
429
|
+
{ field: "locality", separator: " " },
|
|
430
|
+
],
|
|
431
|
+
];
|
|
432
|
+
case "HU":
|
|
433
|
+
return [
|
|
434
|
+
[{ field: "locality" }],
|
|
435
|
+
[{ field: "street" }],
|
|
436
|
+
[{ field: "postalCode" }],
|
|
437
|
+
];
|
|
438
|
+
case "ID":
|
|
439
|
+
case "MN":
|
|
440
|
+
case "TH":
|
|
441
|
+
case "VN":
|
|
442
|
+
return [
|
|
443
|
+
[{ field: "street" }],
|
|
444
|
+
[{ field: "locality" }],
|
|
445
|
+
[{ field: "region" }, { field: "postalCode", separator: " " }],
|
|
446
|
+
];
|
|
447
|
+
case "IN":
|
|
448
|
+
case "NG":
|
|
449
|
+
case "PE":
|
|
450
|
+
return [
|
|
451
|
+
[{ field: "street" }],
|
|
452
|
+
[
|
|
453
|
+
{ field: "locality" },
|
|
454
|
+
{ field: "postalCode", separator: " " },
|
|
455
|
+
],
|
|
456
|
+
[{ field: "region" }],
|
|
457
|
+
];
|
|
458
|
+
case "IR":
|
|
459
|
+
return [
|
|
460
|
+
[{ field: "region" }],
|
|
461
|
+
[{ field: "locality" }],
|
|
462
|
+
[{ field: "street" }],
|
|
463
|
+
[{ field: "postalCode" }],
|
|
464
|
+
];
|
|
465
|
+
case "JE":
|
|
466
|
+
return [
|
|
467
|
+
[{ field: "street" }],
|
|
468
|
+
[{ field: "locality" }],
|
|
469
|
+
[{ text: "JERSEY" }],
|
|
470
|
+
[{ field: "postalCode" }],
|
|
471
|
+
];
|
|
472
|
+
case "JM":
|
|
473
|
+
case "PA":
|
|
474
|
+
case "SC":
|
|
475
|
+
case "SR":
|
|
476
|
+
case "TV":
|
|
477
|
+
return [
|
|
478
|
+
[{ field: "street" }],
|
|
479
|
+
[{ field: "locality" }],
|
|
480
|
+
[{ field: "region" }],
|
|
481
|
+
];
|
|
482
|
+
case "JP":
|
|
483
|
+
return [
|
|
484
|
+
[{ field: "postalCode", prefix: "〒" }],
|
|
485
|
+
[{ field: "region" }],
|
|
486
|
+
[{ field: "street" }],
|
|
487
|
+
];
|
|
488
|
+
case "KI":
|
|
489
|
+
return [
|
|
490
|
+
[{ field: "street" }],
|
|
491
|
+
[{ field: "region" }],
|
|
492
|
+
[{ field: "locality" }],
|
|
493
|
+
];
|
|
494
|
+
case "KP":
|
|
495
|
+
case "KZ":
|
|
496
|
+
return [
|
|
497
|
+
[{ field: "postalCode" }],
|
|
498
|
+
[{ field: "region" }],
|
|
499
|
+
[{ field: "locality" }],
|
|
500
|
+
[{ field: "street" }],
|
|
501
|
+
];
|
|
502
|
+
case "KR":
|
|
503
|
+
return [
|
|
504
|
+
[{ field: "region" }, { field: "locality", separator: " " }],
|
|
505
|
+
[{ field: "street" }],
|
|
506
|
+
[{ field: "postalCode" }],
|
|
507
|
+
];
|
|
508
|
+
case "KY":
|
|
509
|
+
return [
|
|
510
|
+
[{ field: "street" }],
|
|
511
|
+
[{ field: "region" }, { field: "postalCode", separator: " " }],
|
|
512
|
+
];
|
|
513
|
+
case "LI":
|
|
514
|
+
return [
|
|
515
|
+
[{ field: "street" }],
|
|
516
|
+
[
|
|
517
|
+
{ field: "postalCode", prefix: "FL-" },
|
|
518
|
+
{ field: "locality", separator: " " },
|
|
519
|
+
],
|
|
520
|
+
];
|
|
521
|
+
case "LT":
|
|
522
|
+
return [
|
|
523
|
+
[{ field: "street" }],
|
|
524
|
+
[
|
|
525
|
+
{ field: "postalCode", prefix: "LT-" },
|
|
526
|
+
{ field: "locality", separator: " " },
|
|
527
|
+
{ field: "region", separator: " " },
|
|
528
|
+
],
|
|
529
|
+
];
|
|
530
|
+
case "LU":
|
|
531
|
+
return [
|
|
532
|
+
[{ field: "street" }],
|
|
533
|
+
[
|
|
534
|
+
{ field: "postalCode", prefix: "L-" },
|
|
535
|
+
{ field: "locality", separator: " " },
|
|
536
|
+
],
|
|
537
|
+
];
|
|
538
|
+
case "LV":
|
|
539
|
+
return [
|
|
540
|
+
[{ field: "street" }],
|
|
541
|
+
[{ field: "region" }],
|
|
542
|
+
[
|
|
543
|
+
{ field: "locality" },
|
|
544
|
+
{ field: "postalCode", separator: ", " },
|
|
545
|
+
],
|
|
546
|
+
];
|
|
547
|
+
case "MC":
|
|
548
|
+
return [
|
|
549
|
+
[{ field: "street" }],
|
|
550
|
+
[
|
|
551
|
+
{ field: "postalCode", prefix: "MC-" },
|
|
552
|
+
{ field: "locality", separator: " " },
|
|
553
|
+
],
|
|
554
|
+
];
|
|
555
|
+
case "MD":
|
|
556
|
+
return [
|
|
557
|
+
[{ field: "street" }],
|
|
558
|
+
[
|
|
559
|
+
{ field: "postalCode", prefix: "MD-" },
|
|
560
|
+
{ field: "locality", separator: " " },
|
|
561
|
+
],
|
|
562
|
+
];
|
|
563
|
+
case "MM":
|
|
564
|
+
return [
|
|
565
|
+
[{ field: "street" }],
|
|
566
|
+
[
|
|
567
|
+
{ field: "locality" },
|
|
568
|
+
{ field: "postalCode", separator: ", " },
|
|
569
|
+
],
|
|
570
|
+
];
|
|
571
|
+
case "MO":
|
|
572
|
+
return [[{ field: "street" }]];
|
|
573
|
+
case "MX":
|
|
574
|
+
return [
|
|
575
|
+
[{ field: "street" }],
|
|
576
|
+
[
|
|
577
|
+
{ field: "postalCode" },
|
|
578
|
+
{ field: "locality", separator: " " },
|
|
579
|
+
{ field: "region", separator: ", " },
|
|
580
|
+
],
|
|
581
|
+
];
|
|
582
|
+
case "MZ":
|
|
583
|
+
return [
|
|
584
|
+
[{ field: "street" }],
|
|
585
|
+
[
|
|
586
|
+
{ field: "postalCode" },
|
|
587
|
+
{ field: "locality", separator: " " },
|
|
588
|
+
{ field: "region" },
|
|
589
|
+
],
|
|
590
|
+
];
|
|
591
|
+
case "NI":
|
|
592
|
+
return [
|
|
593
|
+
[{ field: "street" }],
|
|
594
|
+
[{ field: "postalCode" }],
|
|
595
|
+
[{ field: "locality" }, { field: "region", separator: ", " }],
|
|
596
|
+
];
|
|
597
|
+
case "PG":
|
|
598
|
+
return [
|
|
599
|
+
[{ field: "street" }],
|
|
600
|
+
[
|
|
601
|
+
{ field: "locality" },
|
|
602
|
+
{ field: "postalCode", separator: " " },
|
|
603
|
+
{ field: "region", separator: " " },
|
|
604
|
+
],
|
|
605
|
+
];
|
|
606
|
+
case "PH":
|
|
607
|
+
return [
|
|
608
|
+
[{ field: "street" }],
|
|
609
|
+
[{ field: "locality" }],
|
|
610
|
+
[{ field: "postalCode" }, { field: "region", separator: " " }],
|
|
611
|
+
];
|
|
612
|
+
case "PK":
|
|
613
|
+
return [
|
|
614
|
+
[{ field: "street" }],
|
|
615
|
+
[
|
|
616
|
+
{ field: "locality" },
|
|
617
|
+
{ field: "postalCode", separator: "-" },
|
|
618
|
+
],
|
|
619
|
+
];
|
|
620
|
+
case "PR":
|
|
621
|
+
return [
|
|
622
|
+
[{ field: "street" }],
|
|
623
|
+
[
|
|
624
|
+
{ field: "locality" },
|
|
625
|
+
{ field: "postalCode", separator: " PR " },
|
|
626
|
+
],
|
|
627
|
+
];
|
|
628
|
+
case "RO":
|
|
629
|
+
return [
|
|
630
|
+
[{ field: "street" }],
|
|
631
|
+
[
|
|
632
|
+
{ field: "postalCode" },
|
|
633
|
+
{ field: "region", separator: " " },
|
|
634
|
+
{ field: "locality", separator: " " },
|
|
635
|
+
],
|
|
636
|
+
];
|
|
637
|
+
case "SE":
|
|
638
|
+
return [
|
|
639
|
+
[{ field: "street" }],
|
|
640
|
+
[
|
|
641
|
+
{ field: "postalCode", prefix: "SE-" },
|
|
642
|
+
{ field: "locality", separator: " " },
|
|
643
|
+
],
|
|
644
|
+
];
|
|
645
|
+
case "SG":
|
|
646
|
+
return [
|
|
647
|
+
[{ field: "street" }],
|
|
648
|
+
[{ field: "postalCode", prefix: "SINGAPORE " }],
|
|
649
|
+
];
|
|
650
|
+
case "SI":
|
|
651
|
+
return [
|
|
652
|
+
[{ field: "street" }],
|
|
653
|
+
[
|
|
654
|
+
{ field: "postalCode", prefix: "SI-" },
|
|
655
|
+
{ field: "locality", separator: " " },
|
|
656
|
+
],
|
|
657
|
+
];
|
|
658
|
+
case "SV":
|
|
659
|
+
return [
|
|
660
|
+
[{ field: "street" }],
|
|
661
|
+
[
|
|
662
|
+
{ field: "postalCode" },
|
|
663
|
+
{ field: "locality", separator: "-" },
|
|
664
|
+
],
|
|
665
|
+
[{ field: "region" }],
|
|
666
|
+
];
|
|
667
|
+
case "TR":
|
|
668
|
+
return [
|
|
669
|
+
[{ field: "street" }],
|
|
670
|
+
[
|
|
671
|
+
{ field: "postalCode" },
|
|
672
|
+
{ field: "locality", separator: " " },
|
|
673
|
+
{ field: "region", separator: "/" },
|
|
674
|
+
],
|
|
675
|
+
];
|
|
676
|
+
case "VE":
|
|
677
|
+
return [
|
|
678
|
+
[{ field: "street" }],
|
|
679
|
+
[
|
|
680
|
+
{ field: "locality" },
|
|
681
|
+
{ field: "postalCode", separator: " " },
|
|
682
|
+
{ field: "region", separator: ", " },
|
|
683
|
+
],
|
|
684
|
+
];
|
|
685
|
+
// Google's default: BF and MW, and every country it gives no layout of
|
|
686
|
+
// its own.
|
|
687
|
+
default:
|
|
688
|
+
return [[{ field: "street" }], [{ field: "locality" }]];
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* How a country lays out an address in Latin letters, where that is not its
|
|
694
|
+
* local layout; `undefined` where it is.
|
|
695
|
+
*
|
|
696
|
+
* Google's `lfmt` field, from the same metadata and converted the same way as
|
|
697
|
+
* `localAddressLayout`. Fourteen countries have one; only these six differ from
|
|
698
|
+
* their `fmt` once the fields `PostalAddress` does not carry are dropped. The
|
|
699
|
+
* other eight (AE, AM, EG, MO, RU, TH, UA, VN) fall through to the local one.
|
|
700
|
+
*/
|
|
701
|
+
function latinAddressLayout(country: CountryCode): AddressLayout | undefined {
|
|
702
|
+
switch (country) {
|
|
703
|
+
case "CN":
|
|
704
|
+
case "KP":
|
|
705
|
+
return [
|
|
706
|
+
[{ field: "street" }],
|
|
707
|
+
[{ field: "locality" }],
|
|
708
|
+
[{ field: "region" }, { field: "postalCode", separator: ", " }],
|
|
709
|
+
];
|
|
710
|
+
case "HK":
|
|
711
|
+
return [
|
|
712
|
+
[{ field: "street" }],
|
|
713
|
+
[{ field: "locality" }],
|
|
714
|
+
[{ field: "region" }],
|
|
715
|
+
];
|
|
716
|
+
case "JP":
|
|
717
|
+
return [
|
|
718
|
+
[{ field: "street" }, { field: "region", separator: ", " }],
|
|
719
|
+
[{ field: "postalCode" }],
|
|
720
|
+
];
|
|
721
|
+
case "KR":
|
|
722
|
+
return [
|
|
723
|
+
[{ field: "street" }],
|
|
724
|
+
[{ field: "locality" }],
|
|
725
|
+
[{ field: "region" }],
|
|
726
|
+
[{ field: "postalCode" }],
|
|
727
|
+
];
|
|
728
|
+
case "TW":
|
|
729
|
+
return [
|
|
730
|
+
[{ field: "street" }],
|
|
731
|
+
[
|
|
732
|
+
{ field: "locality" },
|
|
733
|
+
{ field: "region", separator: ", " },
|
|
734
|
+
{ field: "postalCode", separator: " " },
|
|
735
|
+
],
|
|
736
|
+
];
|
|
737
|
+
default:
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* How a country writes its address on one line: the groups `addressLine`
|
|
744
|
+
* joins, in the order a sentence or a listing runs them.
|
|
745
|
+
*
|
|
746
|
+
* `addressLayout` for every country but those below, where a line of text runs
|
|
747
|
+
* in another order than the envelope. Google's metadata has envelope layouts
|
|
748
|
+
* only, so these are written by hand; each says why.
|
|
749
|
+
*/
|
|
750
|
+
export function inlineAddressLayout(
|
|
751
|
+
country: CountryCode,
|
|
752
|
+
script: AddressScript = "local"
|
|
753
|
+
): AddressLayout {
|
|
754
|
+
switch (country) {
|
|
755
|
+
// The envelope goes town, street, postal code, each on its own line; a
|
|
756
|
+
// line of text goes postal code and town first, the way the company
|
|
757
|
+
// register writes it: `1132 Budapest, Victor Hugo utca 16`.
|
|
758
|
+
case "HU":
|
|
759
|
+
return [
|
|
760
|
+
[
|
|
761
|
+
{ field: "postalCode" },
|
|
762
|
+
{ field: "locality", separator: " " },
|
|
763
|
+
],
|
|
764
|
+
[{ field: "street" }],
|
|
765
|
+
];
|
|
766
|
+
default:
|
|
767
|
+
return addressLayout(country, script);
|
|
768
|
+
}
|
|
769
|
+
}
|
package/src/contact.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AddressLayout,
|
|
3
|
+
type AddressScript,
|
|
4
|
+
addressLayout,
|
|
5
|
+
inlineAddressLayout,
|
|
6
|
+
} from "./address-formats.ts";
|
|
1
7
|
import type { CountryCode } from "./countries.ts";
|
|
2
8
|
import type { Digit } from "./types.ts";
|
|
3
9
|
import { warn } from "./warn.ts";
|
|
@@ -300,8 +306,12 @@ export interface PostalAddress {
|
|
|
300
306
|
readonly street: string;
|
|
301
307
|
/** Town or city, in its own language: `"Roma"`. */
|
|
302
308
|
readonly locality: string;
|
|
303
|
-
/**
|
|
304
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Postal or ZIP code, as written locally. None where the country has no
|
|
311
|
+
* postal codes, as in the UAE: left out rather than sent empty, like
|
|
312
|
+
* `region`.
|
|
313
|
+
*/
|
|
314
|
+
readonly postalCode?: string;
|
|
305
315
|
/**
|
|
306
316
|
* The country, as its two-letter code: `"IT"`, not `"Italy"`.
|
|
307
317
|
*
|
|
@@ -313,3 +323,84 @@ export interface PostalAddress {
|
|
|
313
323
|
/** State, province or region, where the country uses one. */
|
|
314
324
|
readonly region?: string;
|
|
315
325
|
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The record written out in its country's postal order, one string per line:
|
|
329
|
+
* `["Via Nazionale 100", "00184 Roma"]`, `["1 Example Ave", "Austin, TX
|
|
330
|
+
* 78701"]`.
|
|
331
|
+
*
|
|
332
|
+
* Still the canonical address — `Roma`, on every page — so it suits a list of
|
|
333
|
+
* venues or an envelope, not a sentence; a page that phrases the address for
|
|
334
|
+
* its reader writes it in the catalog, like the rest of its copy.
|
|
335
|
+
*
|
|
336
|
+
* The order is Google's address metadata, converted once into
|
|
337
|
+
* `addressLayout`, not a rule per country written here. A field the address does not have prints
|
|
338
|
+
* nothing, and its punctuation goes with it, because each field carries its
|
|
339
|
+
* own: no postal code, no `CH-`. Letter case is the caller's: some countries
|
|
340
|
+
* want the town in capitals on an envelope, which is not how a page should
|
|
341
|
+
* print it.
|
|
342
|
+
*
|
|
343
|
+
* `script` is the script the address is written in, which the caller knows and
|
|
344
|
+
* the record does not: a Tokyo address in Latin letters runs street first,
|
|
345
|
+
* `["1-1 Chiyoda, Tokyo", "100-0001"]`, where in Japanese it runs prefecture
|
|
346
|
+
* first. Only the six countries Google gives a different Latin layout care.
|
|
347
|
+
*/
|
|
348
|
+
export function addressLines(
|
|
349
|
+
address: PostalAddress,
|
|
350
|
+
options: AddressFormatOptions = {}
|
|
351
|
+
): readonly string[] {
|
|
352
|
+
return render(address, addressLayout(address.country, options.script));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** How `addressLines` writes an address. */
|
|
356
|
+
export interface AddressFormatOptions {
|
|
357
|
+
/** The script the address is written in. Defaults to `"local"`. */
|
|
358
|
+
readonly script?: AddressScript;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** How `addressLine` writes an address. */
|
|
362
|
+
export interface AddressLineOptions extends AddressFormatOptions {
|
|
363
|
+
/** Between the groups the line joins. Defaults to `", "`. */
|
|
364
|
+
readonly separator?: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The address on one line, for where there is room for only one: a venue card,
|
|
369
|
+
* an `llms.txt` entry.
|
|
370
|
+
*
|
|
371
|
+
* Not always `addressLines` joined: where a country writes a line of text in
|
|
372
|
+
* another order than an envelope, it follows the line of text. Hungary's
|
|
373
|
+
* envelope goes `Budapest` / `Victor Hugo utca 16` / `1132`; this gives `1132
|
|
374
|
+
* Budapest, Victor Hugo utca 16`.
|
|
375
|
+
*/
|
|
376
|
+
export function addressLine(
|
|
377
|
+
address: PostalAddress,
|
|
378
|
+
options: AddressLineOptions = {}
|
|
379
|
+
): string {
|
|
380
|
+
const { script, separator = ", " } = options;
|
|
381
|
+
return render(address, inlineAddressLayout(address.country, script)).join(
|
|
382
|
+
separator
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function render(
|
|
387
|
+
address: PostalAddress,
|
|
388
|
+
layout: AddressLayout
|
|
389
|
+
): readonly string[] {
|
|
390
|
+
const lines: string[] = [];
|
|
391
|
+
for (const parts of layout) {
|
|
392
|
+
let line = "";
|
|
393
|
+
for (const part of parts) {
|
|
394
|
+
if ("text" in part) {
|
|
395
|
+
line += part.text;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const value = address[part.field]?.trim();
|
|
399
|
+
if (!value) continue;
|
|
400
|
+
if (line !== "") line += part.separator ?? "";
|
|
401
|
+
line += (part.prefix ?? "") + value;
|
|
402
|
+
}
|
|
403
|
+
if (line !== "") lines.push(line);
|
|
404
|
+
}
|
|
405
|
+
return lines;
|
|
406
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ```
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
export type { AddressScript } from "./address-formats.ts";
|
|
15
16
|
export {
|
|
16
17
|
CONSENT_CATEGORIES,
|
|
17
18
|
CONSENT_MONTHS,
|
|
@@ -51,6 +52,10 @@ export type {
|
|
|
51
52
|
SiteConfigShape,
|
|
52
53
|
} from "./config.ts";
|
|
53
54
|
export {
|
|
55
|
+
type AddressFormatOptions,
|
|
56
|
+
type AddressLineOptions,
|
|
57
|
+
addressLine,
|
|
58
|
+
addressLines,
|
|
54
59
|
assertCoordinates,
|
|
55
60
|
type CallingCode,
|
|
56
61
|
type Coordinates,
|
package/src/jsonld/place.ts
CHANGED
|
@@ -37,7 +37,9 @@ export function postalAddress(address: PostalAddress): JsonLdNode {
|
|
|
37
37
|
"@type": "PostalAddress",
|
|
38
38
|
streetAddress: address.street,
|
|
39
39
|
addressLocality: address.locality,
|
|
40
|
-
|
|
40
|
+
...(address.postalCode === undefined
|
|
41
|
+
? {}
|
|
42
|
+
: { postalCode: address.postalCode }),
|
|
41
43
|
addressCountry: address.country,
|
|
42
44
|
...(address.region === undefined
|
|
43
45
|
? {}
|