@acms-subir/core 0.3.62 → 0.3.63

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.
@@ -5,6 +5,8 @@ import { bookingLinkUrl, bookingsEnabled, CALENDAR } from '../../../lib/calendar
5
5
  import { availabilityContext } from '../../../lib/booking/service';
6
6
  import { describeLink, nextOpenings } from '../../../lib/booking/availability';
7
7
  import { fmt } from '../../../lib/booking/view';
8
+ import { linkWords, roomName } from '../../../lib/schema';
9
+ import { localePath, requestLocale } from '../../../lib/i18n';
8
10
 
9
11
  // A room (or person, or van) and its booking links (mockup C): each link is a
10
12
  // card with its rules, an Instant / Approval badge, and its next openings —
@@ -30,34 +32,38 @@ const chip = (start: number, link: (typeof links)[number]) => {
30
32
  return link.mode === 'slots' ? `${day} ${f.time.format(start)}` : day;
31
33
  };
32
34
  const facts = [resource.capacity ? `Up to ${resource.capacity}` : '', ...resource.amenities].filter(Boolean);
35
+ // the room and its links named in the page's language, and links that stay in it
36
+ const locale = requestLocale(Astro.url, Astro.locals);
37
+ const room = roomName(resource, locale);
38
+ const here = (path: string) => localePath(path, locale);
33
39
  ---
34
40
 
35
- <Site title={resource.name} description={resource.description || `Book ${resource.name}.`} image={resource.photo || undefined} noindex={resource.visibility === 'unlisted'}>
41
+ <Site title={room} description={resource.description || `Book ${room}.`} image={resource.photo || undefined} noindex={resource.visibility === 'unlisted'}>
36
42
  <div class="pc-res">
37
43
  <div class="pc-res-about">
38
44
  {resource.photo ? <img class="pc-res-photo" src={resource.photo} alt="" /> : null}
39
45
  <span class="pc-res-kind">{resource.kind}</span>
40
- <h1 class="pc-res-title">{resource.name}</h1>
46
+ <h1 class="pc-res-title">{room}</h1>
41
47
  {facts.length > 0 && <ul class="pc-res-facts">{facts.map((x) => <li>{x}</li>)}</ul>}
42
48
  {resource.description && <p class="pc-res-desc">{resource.description}</p>}
43
49
  </div>
44
50
  <div class="pc-res-links">
45
- {cards.length === 0 && <p class="pc-bk-empty">Online booking isn’t open for {resource.name} right now.</p>}
51
+ {cards.length === 0 && <p class="pc-bk-empty">Online booking isn’t open for {room} right now.</p>}
46
52
  {cards.map(({ link, next }) => (
47
53
  <article class="pc-res-link">
48
54
  <div class="pc-res-linkhead">
49
- <h2><a href={bookingLinkUrl(resource.slug, link.slug)}>{link.name}</a></h2>
55
+ <h2><a href={here(bookingLinkUrl(resource.slug, link.slug))}>{linkWords(link, locale).name}</a></h2>
50
56
  <span class={`pc-res-badge${link.autoConfirm ? ' is-instant' : ''}`}>{link.autoConfirm ? 'Instant' : 'Approval'}</span>
51
57
  </div>
52
- <p>{[describeLink(link), link.description].filter(Boolean).join(' · ')}</p>
58
+ <p>{[describeLink(link), linkWords(link, locale).description].filter(Boolean).join(' · ')}</p>
53
59
  <div class="pc-res-next">
54
- {next.map((s) => <a href={`${bookingLinkUrl(resource.slug, link.slug)}?date=${new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(s.start)}&start=${s.start}&len=${s.minutes}`}>{chip(s.start, link)}</a>)}
60
+ {next.map((s) => <a href={`${here(bookingLinkUrl(resource.slug, link.slug))}?date=${new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).format(s.start)}&start=${s.start}&len=${s.minutes}`}>{chip(s.start, link)}</a>)}
55
61
  {next.length === 0 && <span class="pc-res-none">No openings soon</span>}
56
- <a class="pc-res-more" href={bookingLinkUrl(resource.slug, link.slug)}>{next.length ? 'More times ›' : 'See calendar ›'}</a>
62
+ <a class="pc-res-more" href={here(bookingLinkUrl(resource.slug, link.slug))}>{next.length ? 'More times ›' : 'See calendar ›'}</a>
57
63
  </div>
58
64
  </article>
59
65
  ))}
60
- <p class="pc-bk-foot"><a href={CALENDAR.bookBase}>All rooms and people</a></p>
66
+ <p class="pc-bk-foot"><a href={here(CALENDAR.bookBase)}>All rooms and people</a></p>
61
67
  </div>
62
68
  </div>
63
69
  </Site>
@@ -2,6 +2,8 @@
2
2
  import Site from '../../layouts/Site.astro';
3
3
  import { getRepo } from '../../lib/repo';
4
4
  import { CALENDAR, bookingsEnabled, resourceUrl, bookingLinkUrl } from '../../lib/calendar/config';
5
+ import { linkWords, roomName } from '../../lib/schema';
6
+ import { localePath, requestLocale } from '../../lib/i18n';
5
7
 
6
8
  // Directory of everything bookable: public resources grouped by kind, each
7
9
  // with its public booking links.
@@ -13,6 +15,9 @@ const byKind = new Map<string, typeof listed>();
13
15
  for (const r of listed) byKind.set(r.kind, [...(byKind.get(r.kind) ?? []), r]);
14
16
  const linksOf = (id: string) => links.filter((l) => l.resourceId === id && l.active && l.visibility === 'public');
15
17
  const plural = (k: string) => (k.endsWith('s') ? k : k.endsWith('y') ? `${k.slice(0, -1)}ies` : `${k}s`);
18
+ // rooms and links named in the page's language, and links that stay in it
19
+ const locale = requestLocale(Astro.url, Astro.locals);
20
+ const here = (path: string) => localePath(path, locale);
16
21
  ---
17
22
 
18
23
  <Site title="Book" description={`Book rooms and time at ${settings.siteTitle}.`}>
@@ -32,11 +37,11 @@ const plural = (k: string) => (k.endsWith('s') ? k : k.endsWith('y') ? `${k.slic
32
37
  <div class="pc-dir-grid">
33
38
  {list.map((r) => (
34
39
  <article class="pc-dir-card">
35
- <a class="pc-dir-media" href={resourceUrl(r.slug)} tabindex="-1" aria-hidden="true">{r.photo ? <img src={r.photo} alt="" loading="lazy" /> : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21V9l9-5 9 5v12M9 21v-6h6v6" /></svg>}</a>
40
+ <a class="pc-dir-media" href={here(resourceUrl(r.slug))} tabindex="-1" aria-hidden="true">{r.photo ? <img src={r.photo} alt="" loading="lazy" /> : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21V9l9-5 9 5v12M9 21v-6h6v6" /></svg>}</a>
36
41
  <div class="pc-dir-body">
37
- <h3><a href={resourceUrl(r.slug)}>{r.name}</a></h3>
42
+ <h3><a href={here(resourceUrl(r.slug))}>{roomName(r, locale)}</a></h3>
38
43
  <p>{[r.capacity ? `Up to ${r.capacity}` : '', ...r.amenities.slice(0, 3)].filter(Boolean).join(' · ')}</p>
39
- <div class="pc-dir-links">{linksOf(r.id).map((l) => <a href={bookingLinkUrl(r.slug, l.slug)}>{l.name}</a>)}</div>
44
+ <div class="pc-dir-links">{linksOf(r.id).map((l) => <a href={here(bookingLinkUrl(r.slug, l.slug))}>{linkWords(l, locale).name}</a>)}</div>
40
45
  </div>
41
46
  </article>
42
47
  ))}
@@ -5,6 +5,8 @@ import { CALENDAR, bookingLinkUrl, bookingsEnabled, eventUrl, resourceUrl } from
5
5
  import { confirmOffer, guestCancel, guestChanges, waitlistPosition, BookingError, WAITLIST_HOLD } from '../../../lib/booking/service';
6
6
  import { bookingIcs, whenText } from '../../../lib/booking/emails';
7
7
  import { resourceZone } from '../../../lib/booking/availability';
8
+ import { bookingTitle } from '../../../lib/booking/view';
9
+ import { roomName } from '../../../lib/schema';
8
10
  import { eventTimeZone, locationLine } from '../../../lib/calendar/events';
9
11
  import { siteOrigin } from '../../../lib/seo';
10
12
  import { waitUntilFrom } from '../../../lib/webhooks';
@@ -43,8 +45,8 @@ const lang = locale || siteLang(settings);
43
45
  const bw = bookingStrings(lang).page;
44
46
  const w = isRsvp ? rsvpStrings(lang) : { ...rsvpStrings(lang), ...bw };
45
47
  const event = rawEvent && localizeDoc(rawEvent, locale || undefined);
46
- const title = event ? event.title : [resource?.name, link?.name].filter(Boolean).join(' · ') || bw.untitled;
47
- const location = event ? locationLine(event.location) : resource?.name ?? '';
48
+ const title = event ? event.title : bookingTitle(resource, link, locale) || bw.untitled;
49
+ const location = event ? locationLine(event.location) : resource ? roomName(resource, locale) : '';
48
50
  // the address the visitor sees (/es/… is rewritten to this route)
49
51
  const url = origin + localePath(Astro.url.pathname, locale);
50
52
 
@@ -166,7 +168,7 @@ const googleUrl = `https://calendar.google.com/calendar/render?${new URLSearchPa
166
168
  )}
167
169
  {can.closed && <p class="pc-manage-note">{cutoff}</p>}
168
170
  <p class="pc-bk-foot">
169
- {event ? <a href={localePath(eventUrl(event.slug), locale)}>{fill(w.backTo, { title: event.title })}</a> : resource ? <a href={resourceUrl(resource.slug)}>{fill(w.backTo, { title: resource.name })}</a> : null}
171
+ {event ? <a href={localePath(eventUrl(event.slug), locale)}>{fill(w.backTo, { title: event.title })}</a> : resource ? <a href={resourceUrl(resource.slug)}>{fill(w.backTo, { title: roomName(resource, locale) })}</a> : null}
170
172
  </p>
171
173
  <p class="pc-manage-note">{w.keepLink}</p>
172
174
  </div>
@@ -4,25 +4,33 @@ import BookingFlow from '../../../../components/booking/BookingFlow.astro';
4
4
  import { getRepo } from '../../../../lib/repo';
5
5
  import { bookingLinkUrl, bookingsEnabled } from '../../../../lib/calendar/config';
6
6
  import { availabilityContext } from '../../../../lib/booking/service';
7
+ import { I18N, localePath } from '../../../../lib/i18n';
8
+ import { linkWords } from '../../../../lib/schema';
7
9
 
8
10
  // Embeddable picker for other sites: day + time selection happens inside the
9
11
  // iframe; choosing a time opens the full booking page (top window) to finish.
12
+ // ?lang=es (one of the site's languages; embeds are never under /es) names the
13
+ // link and room in it and hands off to that language's page.
10
14
  if (!bookingsEnabled()) return new Response(null, { status: 404 });
11
15
  const repo = await getRepo(Astro.locals);
12
16
  const resource = await repo.getResourceBySlug(Astro.params.resource ?? '');
13
17
  const link = resource ? await repo.getBookingLinkBySlug(resource.id, Astro.params.link ?? '') : null;
14
18
  if (!resource || !link || !link.active) return new Response(null, { status: 404 });
15
19
  const settings = await repo.getSettings();
20
+ const langParam = Astro.url.searchParams.get('lang') ?? '';
21
+ const locale = I18N.enabled && I18N.locales.includes(langParam) ? langParam : '';
16
22
  // a chosen time hands off to the real page, where the form lives
17
23
  if (Astro.url.searchParams.get('start')) {
18
- return Astro.redirect(`${bookingLinkUrl(resource.slug, link.slug)}${Astro.url.search}`);
24
+ const search = new URLSearchParams(Astro.url.search);
25
+ search.delete('lang');
26
+ return Astro.redirect(`${localePath(bookingLinkUrl(resource.slug, link.slug), locale)}?${search}`);
19
27
  }
20
28
  const ctx = await availabilityContext(repo, resource, link, settings);
21
29
  Astro.response.headers.set('Content-Security-Policy', 'frame-ancestors *');
22
30
  ---
23
31
 
24
- <Site title={link.name} bare noindex>
25
- <BookingFlow resource={resource} link={link} ctx={ctx} settings={settings} url={Astro.url} embed />
32
+ <Site title={linkWords(link, locale).name} bare noindex>
33
+ <BookingFlow resource={resource} link={link} ctx={ctx} settings={settings} url={Astro.url} embed locale={locale} />
26
34
  <script is:inline>
27
35
  (function () {
28
36
  var send = function () { parent.postMessage({ acmsBookingEmbed: true, height: document.documentElement.scrollHeight }, '*'); };