@escape-game-over/atlas 0.1.99 → 0.1.100

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/hours.ts +37 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.99",
3
+ "version": "0.1.100",
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/hours.ts CHANGED
@@ -226,17 +226,41 @@ export function formatHours(
226
226
  }));
227
227
  }
228
228
 
229
+ /** How to say a week in a sentence: `HoursFormat` without its separators. */
230
+ export type HoursSentenceFormat = Omit<HoursFormat, "and">;
231
+
229
232
  /**
230
- * How to say a week in a sentence: `HoursFormat` without the separators it
231
- * picks from `Intl`, plus the one `Intl` has no word for.
233
+ * Each language's semicolon, between one group of days and the next. `Intl`
234
+ * has no list form for it, and it is punctuation rather than copy, so it lives
235
+ * here. A language missing from this table throws rather than borrowing the
236
+ * English one: add it here.
232
237
  */
233
- export interface HoursSentenceFormat extends Omit<HoursFormat, "and"> {
234
- /**
235
- * Between one group of days and the next: `"; "` in English, `"؛ "` in
236
- * Arabic. Copy, so the caller translates it; `Intl` has no list form
237
- * for it.
238
- */
239
- readonly separator: string;
238
+ const SEMICOLONS: Readonly<Record<string, string>> = {
239
+ ar: "؛ ",
240
+ de: "; ",
241
+ el: "· ",
242
+ en: "; ",
243
+ es: "; ",
244
+ // A narrow no-break space before it, as French sets it.
245
+ fr: "\u202F; ",
246
+ hu: "; ",
247
+ it: "; ",
248
+ pt: "; ",
249
+ ru: "; ",
250
+ sv: "; ",
251
+ };
252
+
253
+ function semicolonFor(locale: string): string {
254
+ const { language } = new Intl.Locale(locale);
255
+ const semicolon = Object.hasOwn(SEMICOLONS, language)
256
+ ? SEMICOLONS[language]
257
+ : undefined;
258
+ if (semicolon === undefined) {
259
+ throw new Error(
260
+ `hoursSentence has no semicolon for "${language}" (${locale}). Add it to SEMICOLONS in hours.ts.`
261
+ );
262
+ }
263
+ return semicolon;
240
264
  }
241
265
 
242
266
  /**
@@ -245,13 +269,15 @@ export interface HoursSentenceFormat extends Omit<HoursFormat, "and"> {
245
269
  *
246
270
  * Days and times join with the locale's own "and" (`Intl.ListFormat`), so a
247
271
  * sentence in Arabic reads `الاثنين–الخميس والأحد` rather than borrowing an
248
- * English comma. No closing full stop: the copy around it decides that.
272
+ * English comma; groups with the language's semicolon (`؛` in Arabic). No
273
+ * closing full stop: the copy around it decides that.
249
274
  */
250
275
  export function hoursSentence(
251
276
  hours: WeeklyHours,
252
277
  format: HoursSentenceFormat
253
278
  ): string {
254
279
  const between = format.between ?? "–";
280
+ const semicolon = semicolonFor(format.locale);
255
281
  const { dayName, time } = printer(format);
256
282
  const list = new Intl.ListFormat(format.locale, {
257
283
  type: "conjunction",
@@ -275,7 +301,7 @@ export function hoursSentence(
275
301
  );
276
302
  return `${days} ${times}`;
277
303
  })
278
- .join(format.separator);
304
+ .join(semicolon);
279
305
  }
280
306
 
281
307
  /** Day names and times as `format` prints them. */