Internationalize all UI strings and localize date formatting

All hardcoded English strings now use t() with i18n keys: bottom nav
tabs, search placeholder, empty states, favorites page, settings page.

Added useDateLocale composable that maps the current i18n language to
the corresponding date-fns locale (fr/es/enUS). All date formatting
across ActivityCard, ActivityDetailPage, DatePickerStrip, calendar
view, and search overlay now passes the locale to date-fns format().
Month names, day names, and date labels change with language switch.

Added nav, search, favorites, and settings i18n sections to all three
locales (EN/FR/ES).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-04-21 07:24:43 +02:00
commit 93c2a73e21
14 changed files with 209 additions and 42 deletions

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Toaster } from '@/components/ui/sonner' import { Toaster } from '@/components/ui/sonner'
import LoginDialog from '@/components/auth/LoginDialog.vue' import LoginDialog from '@/components/auth/LoginDialog.vue'
import { useTheme } from '@/components/theme-provider' import { useTheme } from '@/components/theme-provider'
@ -11,19 +12,20 @@ import {
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const { t } = useI18n()
useTheme() useTheme()
const showLoginDialog = ref(false) const showLoginDialog = ref(false)
// Bottom navigation tabs (p'a semana style) // Bottom navigation tabs (p'a semana style)
const bottomTabs = [ const bottomTabs = computed(() => [
{ name: 'Feed', icon: Search, path: '/activities' }, { name: t('activities.nav.feed'), icon: Search, path: '/activities' },
{ name: 'Calendar', icon: CalendarDays, path: '/activities/calendar' }, { name: t('activities.nav.calendar'), icon: CalendarDays, path: '/activities/calendar' },
{ name: 'Map', icon: Map, path: '/activities/map' }, { name: t('activities.nav.map'), icon: Map, path: '/activities/map' },
{ name: 'Favorites', icon: Heart, path: '/activities/favorites' }, { name: t('activities.nav.favorites'), icon: Heart, path: '/activities/favorites' },
{ name: 'Settings', icon: Settings, path: '/settings' }, { name: t('activities.nav.settings'), icon: Settings, path: '/settings' },
] ])
const isLoginPage = computed(() => route.path === '/login') const isLoginPage = computed(() => route.path === '/login')

View file

@ -9,7 +9,7 @@ import { useI18n } from 'vue-i18n'
import { Sun, Moon, LogIn, LogOut } from 'lucide-vue-next' import { Sun, Moon, LogIn, LogOut } from 'lucide-vue-next'
const { theme, setTheme } = useTheme() const { theme, setTheme } = useTheme()
const { locale } = useI18n() const { t, locale } = useI18n()
const languages: { code: AvailableLocale; label: string }[] = [ const languages: { code: AvailableLocale; label: string }[] = [
{ code: 'fr', label: 'Français' }, { code: 'fr', label: 'Français' },
@ -35,27 +35,27 @@ async function handleLogout() {
<template> <template>
<div class="container mx-auto px-4 py-6 max-w-lg"> <div class="container mx-auto px-4 py-6 max-w-lg">
<h1 class="text-2xl font-bold text-foreground mb-6">Settings</h1> <h1 class="text-2xl font-bold text-foreground mb-6">{{ t('activities.settings.title') }}</h1>
<!-- Account --> <!-- Account -->
<div class="space-y-4"> <div class="space-y-4">
<h2 class="text-sm font-medium text-muted-foreground uppercase tracking-wide">Account</h2> <h2 class="text-sm font-medium text-muted-foreground uppercase tracking-wide">{{ t('activities.settings.account') }}</h2>
<div v-if="isAuthenticated" class="bg-muted/50 rounded-lg p-4 space-y-3"> <div v-if="isAuthenticated" class="bg-muted/50 rounded-lg p-4 space-y-3">
<p class="text-sm text-foreground font-mono truncate"> <p class="text-sm text-foreground font-mono truncate">
{{ userPubkey }} {{ userPubkey }}
</p> </p>
<Button variant="outline" size="sm" class="w-full gap-2" @click="handleLogout"> <Button variant="outline" size="sm" class="w-full gap-2" @click="handleLogout">
<LogOut class="w-4 h-4" /> <LogOut class="w-4 h-4" />
Log out {{ t('activities.settings.logOut') }}
</Button> </Button>
</div> </div>
<div v-else class="bg-muted/50 rounded-lg p-4"> <div v-else class="bg-muted/50 rounded-lg p-4">
<p class="text-sm text-muted-foreground mb-3"> <p class="text-sm text-muted-foreground mb-3">
Log in to bookmark activities, RSVP, and purchase tickets. {{ t('activities.settings.loginPrompt') }}
</p> </p>
<Button size="sm" class="w-full gap-2" @click="$router.push('/login')"> <Button size="sm" class="w-full gap-2" @click="$router.push('/login')">
<LogIn class="w-4 h-4" /> <LogIn class="w-4 h-4" />
Log in {{ t('activities.settings.logIn') }}
</Button> </Button>
</div> </div>
</div> </div>
@ -64,9 +64,9 @@ async function handleLogout() {
<!-- Appearance --> <!-- Appearance -->
<div class="space-y-4"> <div class="space-y-4">
<h2 class="text-sm font-medium text-muted-foreground uppercase tracking-wide">Appearance</h2> <h2 class="text-sm font-medium text-muted-foreground uppercase tracking-wide">{{ t('activities.settings.appearance') }}</h2>
<div class="flex items-center justify-between bg-muted/50 rounded-lg p-4"> <div class="flex items-center justify-between bg-muted/50 rounded-lg p-4">
<span class="text-sm text-foreground">Theme</span> <span class="text-sm text-foreground">{{ t('activities.settings.theme') }}</span>
<Button variant="outline" size="icon" class="h-8 w-8" @click="toggleTheme"> <Button variant="outline" size="icon" class="h-8 w-8" @click="toggleTheme">
<Sun v-if="theme === 'dark'" class="w-4 h-4" /> <Sun v-if="theme === 'dark'" class="w-4 h-4" />
<Moon v-else class="w-4 h-4" /> <Moon v-else class="w-4 h-4" />
@ -78,7 +78,7 @@ async function handleLogout() {
<!-- Language --> <!-- Language -->
<div class="space-y-4"> <div class="space-y-4">
<h2 class="text-sm font-medium text-muted-foreground uppercase tracking-wide">Language</h2> <h2 class="text-sm font-medium text-muted-foreground uppercase tracking-wide">{{ t('activities.settings.language') }}</h2>
<div class="flex gap-2"> <div class="flex gap-2">
<Button <Button
v-for="lang in languages" v-for="lang in languages"

View file

@ -90,6 +90,34 @@ const messages: LocaleMessages = {
pending: 'Pending', pending: 'Pending',
registered: 'Registered', registered: 'Registered',
}, },
nav: {
feed: 'Feed',
calendar: 'Calendar',
map: 'Map',
favorites: 'Favorites',
settings: 'Settings',
},
search: {
placeholder: 'Search activities...',
noResults: 'No activities found',
},
favorites: {
title: 'Favorites',
loginPrompt: 'Log in to save your favorite activities',
empty: 'No favorites yet',
emptyHint: 'Tap the heart icon on any activity to save it here',
logIn: 'Log in',
},
settings: {
title: 'Settings',
account: 'Account',
loginPrompt: 'Log in to bookmark activities, RSVP, and purchase tickets.',
logIn: 'Log in',
logOut: 'Log out',
appearance: 'Appearance',
theme: 'Theme',
language: 'Language',
},
}, },
dateTimeFormats: { dateTimeFormats: {
short: { short: {

View file

@ -90,6 +90,34 @@ const messages: LocaleMessages = {
pending: 'Pendiente', pending: 'Pendiente',
registered: 'Registrado', registered: 'Registrado',
}, },
nav: {
feed: 'Inicio',
calendar: 'Calendario',
map: 'Mapa',
favorites: 'Favoritos',
settings: 'Ajustes',
},
search: {
placeholder: 'Buscar actividades...',
noResults: 'No se encontraron actividades',
},
favorites: {
title: 'Favoritos',
loginPrompt: 'Inicia sesión para guardar tus actividades favoritas',
empty: 'Aún no tienes favoritos',
emptyHint: 'Toca el corazón en cualquier actividad para guardarla aquí',
logIn: 'Iniciar sesión',
},
settings: {
title: 'Ajustes',
account: 'Cuenta',
loginPrompt: 'Inicia sesión para guardar actividades, confirmar asistencia y comprar boletos.',
logIn: 'Iniciar sesión',
logOut: 'Cerrar sesión',
appearance: 'Apariencia',
theme: 'Tema',
language: 'Idioma',
},
}, },
dateTimeFormats: { dateTimeFormats: {
short: { short: {

View file

@ -90,6 +90,34 @@ const messages: LocaleMessages = {
pending: 'En attente', pending: 'En attente',
registered: 'Enregistré', registered: 'Enregistré',
}, },
nav: {
feed: 'Fil',
calendar: 'Calendrier',
map: 'Carte',
favorites: 'Favoris',
settings: 'Réglages',
},
search: {
placeholder: 'Rechercher des activités...',
noResults: 'Aucune activité trouvée',
},
favorites: {
title: 'Favoris',
loginPrompt: 'Connectez-vous pour sauvegarder vos activités préférées',
empty: 'Pas encore de favoris',
emptyHint: "Appuyez sur le cœur d'une activité pour la sauvegarder ici",
logIn: 'Se connecter',
},
settings: {
title: 'Réglages',
account: 'Compte',
loginPrompt: 'Connectez-vous pour sauvegarder des activités, confirmer votre présence et acheter des billets.',
logIn: 'Se connecter',
logOut: 'Se déconnecter',
appearance: 'Apparence',
theme: 'Thème',
language: 'Langue',
},
}, },
dateTimeFormats: { dateTimeFormats: {
short: { short: {

View file

@ -65,6 +65,34 @@ export interface LocaleMessages {
pending: string pending: string
registered: string registered: string
} }
nav: {
feed: string
calendar: string
map: string
favorites: string
settings: string
}
search: {
placeholder: string
noResults: string
}
favorites: {
title: string
loginPrompt: string
empty: string
emptyHint: string
logIn: string
}
settings: {
title: string
account: string
loginPrompt: string
logIn: string
logOut: string
appearance: string
theme: string
language: string
}
} }
// Add date/time formats // Add date/time formats
dateTimeFormats: { dateTimeFormats: {

View file

@ -7,6 +7,7 @@ import {
} from 'date-fns' } from 'date-fns'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next' import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { useDateLocale } from '../composables/useDateLocale'
import type { Activity } from '../types/activity' import type { Activity } from '../types/activity'
const props = defineProps<{ const props = defineProps<{
@ -18,11 +19,24 @@ const emit = defineEmits<{
selectActivity: [activity: Activity] selectActivity: [activity: Activity]
}>() }>()
const { dateLocale } = useDateLocale()
const currentMonth = ref(new Date()) const currentMonth = ref(new Date())
const monthLabel = computed(() => format(currentMonth.value, 'MMMM yyyy')) const monthLabel = computed(() => format(currentMonth.value, 'MMMM yyyy', { locale: dateLocale.value }))
const weekDays = ['M', 'T', 'W', 'T', 'F', 'S', 'S'] const weekDays = computed(() => {
// Generate localized single-letter day names starting from Monday
const days: string[] = []
// Start from a known Monday (2024-01-01 is a Monday)
const monday = new Date(2024, 0, 1)
for (let i = 0; i < 7; i++) {
const d = new Date(monday)
d.setDate(d.getDate() + i)
days.push(format(d, 'EEEEE', { locale: dateLocale.value }))
}
return days
})
const calendarDays = computed(() => { const calendarDays = computed(() => {
const monthStart = startOfMonth(currentMonth.value) const monthStart = startOfMonth(currentMonth.value)
@ -134,7 +148,7 @@ function nextMonth() {
<!-- Selected day activities --> <!-- Selected day activities -->
<div v-if="selectedDay" class="border-t pt-4 space-y-2"> <div v-if="selectedDay" class="border-t pt-4 space-y-2">
<h3 class="text-sm font-medium text-muted-foreground"> <h3 class="text-sm font-medium text-muted-foreground">
{{ format(selectedDay, 'EEEE, MMMM d') }} {{ format(selectedDay, 'EEEE, MMMM d', { locale: dateLocale }) }}
<span v-if="selectedDayActivities.length > 0" class="ml-1"> <span v-if="selectedDayActivities.length > 0" class="ml-1">
({{ selectedDayActivities.length }}) ({{ selectedDayActivities.length }})
</span> </span>

View file

@ -6,6 +6,7 @@ import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { MapPin, Calendar, Ticket } from 'lucide-vue-next' import { MapPin, Calendar, Ticket } from 'lucide-vue-next'
import BookmarkButton from './BookmarkButton.vue' import BookmarkButton from './BookmarkButton.vue'
import { useDateLocale } from '../composables/useDateLocale'
import type { Activity } from '../types/activity' import type { Activity } from '../types/activity'
const props = defineProps<{ const props = defineProps<{
@ -17,16 +18,18 @@ const emit = defineEmits<{
}>() }>()
const { t } = useI18n() const { t } = useI18n()
const { dateLocale } = useDateLocale()
const dateDisplay = computed(() => { const dateDisplay = computed(() => {
const a = props.activity const a = props.activity
if (!a.startDate || isNaN(a.startDate.getTime())) return '' if (!a.startDate || isNaN(a.startDate.getTime())) return ''
try { try {
const opts = { locale: dateLocale.value }
if (a.type === 'date') { if (a.type === 'date') {
return format(a.startDate, 'EEE, MMM d') return format(a.startDate, 'EEE, MMM d', opts)
} }
const date = format(a.startDate, 'EEE, MMM d') const date = format(a.startDate, 'EEE, MMM d', opts)
const time = format(a.startDate, 'HH:mm') const time = format(a.startDate, 'HH:mm', opts)
return `${date} \u2022 ${time}` return `${date} \u2022 ${time}`
} catch { } catch {
return '' return ''

View file

@ -43,7 +43,7 @@ const { t } = useI18n()
{{ t('activities.noActivities') }} {{ t('activities.noActivities') }}
</h3> </h3>
<p class="text-sm text-muted-foreground"> <p class="text-sm text-muted-foreground">
Try adjusting your filters or check back later {{ t('activities.search.noResults') }}
</p> </p>
</div> </div>

View file

@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { format } from 'date-fns' import { format } from 'date-fns'
import { useDateLocale } from '../composables/useDateLocale'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Search, X, MapPin, Calendar } from 'lucide-vue-next' import { Search, X, MapPin, Calendar } from 'lucide-vue-next'
@ -15,6 +17,8 @@ const emit = defineEmits<{
select: [activity: Activity] select: [activity: Activity]
}>() }>()
const { t } = useI18n()
const { dateLocale } = useDateLocale()
const isOpen = ref(false) const isOpen = ref(false)
const inputRef = ref<HTMLInputElement | null>(null) const inputRef = ref<HTMLInputElement | null>(null)
@ -51,8 +55,9 @@ const showNoResults = computed(() => isOpen.value && isSearching.value && filter
function formatDate(activity: Activity): string { function formatDate(activity: Activity): string {
if (!activity.startDate || isNaN(activity.startDate.getTime())) return '' if (!activity.startDate || isNaN(activity.startDate.getTime())) return ''
try { try {
if (activity.type === 'date') return format(activity.startDate, 'MMM d') const opts = { locale: dateLocale.value }
return format(activity.startDate, 'MMM d · HH:mm') if (activity.type === 'date') return format(activity.startDate, 'MMM d', opts)
return format(activity.startDate, 'MMM d · HH:mm', opts)
} catch { } catch {
return '' return ''
} }
@ -105,7 +110,7 @@ watch(isOpen, (open) => {
:model-value="searchQuery" :model-value="searchQuery"
@update:model-value="handleInput" @update:model-value="handleInput"
@focus="handleFocus" @focus="handleFocus"
placeholder="Search activities..." :placeholder="t('activities.search.placeholder')"
class="pl-9 pr-9" class="pl-9 pr-9"
/> />
<Button <Button
@ -126,7 +131,7 @@ watch(isOpen, (open) => {
> >
<!-- No results --> <!-- No results -->
<div v-if="showNoResults" class="p-4 text-sm text-muted-foreground text-center"> <div v-if="showNoResults" class="p-4 text-sm text-muted-foreground text-center">
No activities found {{ t('activities.search.noResults') }}
</div> </div>
<!-- Result items --> <!-- Result items -->

View file

@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
import { addDays, format, isSameDay, startOfWeek } from 'date-fns' import { addDays, format, isSameDay, startOfWeek } from 'date-fns'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { ChevronLeft, ChevronRight } from 'lucide-vue-next' import { ChevronLeft, ChevronRight } from 'lucide-vue-next'
import { useDateLocale } from '../composables/useDateLocale'
const props = defineProps<{ const props = defineProps<{
/** Currently selected date (if any) */ /** Currently selected date (if any) */
@ -13,6 +14,8 @@ const emit = defineEmits<{
select: [date: Date] select: [date: Date]
}>() }>()
const { dateLocale } = useDateLocale()
/** Start of the visible week */ /** Start of the visible week */
const weekStart = ref(startOfWeek(new Date(), { weekStartsOn: 1 })) const weekStart = ref(startOfWeek(new Date(), { weekStartsOn: 1 }))
@ -55,7 +58,7 @@ function nextWeek() {
<span class="text-[10px] font-medium uppercase leading-none" <span class="text-[10px] font-medium uppercase leading-none"
:class="isSelected(day) ? 'text-primary-foreground/70' : 'text-muted-foreground'" :class="isSelected(day) ? 'text-primary-foreground/70' : 'text-muted-foreground'"
> >
{{ format(day, 'EEEEE') }} {{ format(day, 'EEEEE', { locale: dateLocale }) }}
</span> </span>
<span class="text-sm font-semibold leading-tight mt-0.5"> <span class="text-sm font-semibold leading-tight mt-0.5">
{{ format(day, 'd') }} {{ format(day, 'd') }}

View file

@ -0,0 +1,23 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { fr, es, enUS } from 'date-fns/locale'
import type { Locale } from 'date-fns'
const localeMap: Record<string, Locale> = {
fr,
es,
en: enUS,
}
/**
* Returns a reactive date-fns Locale based on the current i18n language.
*/
export function useDateLocale() {
const { locale } = useI18n()
const dateLocale = computed<Locale>(() => {
return localeMap[locale.value] ?? enUS
})
return { dateLocale }
}

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted } from 'vue' import { computed, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Heart } from 'lucide-vue-next' import { Heart } from 'lucide-vue-next'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
@ -11,6 +12,7 @@ import ActivityList from '../components/ActivityList.vue'
import type { Activity } from '../types/activity' import type { Activity } from '../types/activity'
const router = useRouter() const router = useRouter()
const { t } = useI18n()
const { isAuthenticated } = useAuth() const { isAuthenticated } = useAuth()
const { isBookmarkedByDTag, isLoaded } = useBookmarks() const { isBookmarkedByDTag, isLoaded } = useBookmarks()
const store = useActivitiesStore() const store = useActivitiesStore()
@ -25,9 +27,9 @@ function handleSelect(activity: Activity) {
onMounted(() => { onMounted(() => {
if (!isAuthenticated.value) { if (!isAuthenticated.value) {
toast.info('Log in to save favorites', { toast.info(t('activities.favorites.loginPrompt'), {
action: { action: {
label: 'Log in', label: t('activities.favorites.logIn'),
onClick: () => router.push('/login'), onClick: () => router.push('/login'),
}, },
}) })
@ -37,14 +39,14 @@ onMounted(() => {
<template> <template>
<div class="container mx-auto px-4 py-6"> <div class="container mx-auto px-4 py-6">
<h1 class="text-2xl font-bold text-foreground mb-4">Favorites</h1> <h1 class="text-2xl font-bold text-foreground mb-4">{{ t('activities.favorites.title') }}</h1>
<!-- Not authenticated --> <!-- Not authenticated -->
<div v-if="!isAuthenticated" class="flex flex-col items-center justify-center py-16 text-center"> <div v-if="!isAuthenticated" class="flex flex-col items-center justify-center py-16 text-center">
<Heart class="w-16 h-16 text-muted-foreground/30 mb-4" /> <Heart class="w-16 h-16 text-muted-foreground opacity-30 mb-4" />
<p class="text-muted-foreground mb-3">Log in to save your favorite activities</p> <p class="text-muted-foreground mb-3">{{ t('activities.favorites.loginPrompt') }}</p>
<Button variant="outline" size="sm" @click="router.push('/login')"> <Button variant="outline" size="sm" @click="router.push('/login')">
Log in {{ t('activities.favorites.logIn') }}
</Button> </Button>
</div> </div>
@ -55,9 +57,9 @@ onMounted(() => {
<!-- Empty --> <!-- Empty -->
<div v-else-if="favoriteActivities.length === 0" class="flex flex-col items-center justify-center py-16 text-center"> <div v-else-if="favoriteActivities.length === 0" class="flex flex-col items-center justify-center py-16 text-center">
<Heart class="w-16 h-16 text-muted-foreground/30 mb-4" /> <Heart class="w-16 h-16 text-muted-foreground opacity-30 mb-4" />
<p class="text-muted-foreground">No favorites yet</p> <p class="text-muted-foreground">{{ t('activities.favorites.empty') }}</p>
<p class="text-sm text-muted-foreground/70 mt-1">Tap the heart icon on any activity to save it here</p> <p class="text-sm text-muted-foreground/70 mt-1">{{ t('activities.favorites.emptyHint') }}</p>
</div> </div>
<!-- Favorites list --> <!-- Favorites list -->

View file

@ -3,6 +3,7 @@ import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { format } from 'date-fns' import { format } from 'date-fns'
import { useDateLocale } from '../composables/useDateLocale'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
@ -20,20 +21,22 @@ const { t } = useI18n()
const activityId = route.params.id as string const activityId = route.params.id as string
const { activity, isLoading, error, reload } = useActivityDetail(activityId) const { activity, isLoading, error, reload } = useActivityDetail(activityId)
const { dateLocale } = useDateLocale()
const dateDisplay = computed(() => { const dateDisplay = computed(() => {
if (!activity.value) return '' if (!activity.value) return ''
const a = activity.value const a = activity.value
const opts = { locale: dateLocale.value }
if (a.type === 'date') { if (a.type === 'date') {
const start = format(a.startDate, 'EEEE, MMMM d, yyyy') const start = format(a.startDate, 'EEEE, MMMM d, yyyy', opts)
if (a.endDate) { if (a.endDate) {
return `${start}${format(a.endDate, 'EEEE, MMMM d, yyyy')}` return `${start}${format(a.endDate, 'EEEE, MMMM d, yyyy', opts)}`
} }
return start return start
} }
const start = format(a.startDate, 'EEEE, MMMM d, yyyy \u2022 HH:mm') const start = format(a.startDate, 'EEEE, MMMM d, yyyy \u2022 HH:mm', opts)
if (a.endDate) { if (a.endDate) {
return `${start}${format(a.endDate, 'HH:mm')}` return `${start}${format(a.endDate, 'HH:mm', opts)}`
} }
return start return start
}) })