diff --git a/src/pages/city/[code].tsx b/src/pages/city/[code].tsx index cbe8c29..5e61c6e 100644 --- a/src/pages/city/[code].tsx +++ b/src/pages/city/[code].tsx @@ -12,9 +12,11 @@ import "dayjs/locale/zh-tw"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { groupBy } from "es-toolkit"; import { useTranslation } from "next-i18next"; -import { getDayjsLocale, monthNumberFormatter } from "@/utils/locale"; +import { currentSupportLocale, getDayjsLocale, monthNumberFormatter } from "@/utils/locale"; import { EventsAPI } from "@/api/events"; import dayjs from "dayjs"; +import { breadcrumbGenerator } from "@/utils/structuredData"; +import { cityDetailDescriptionGenerator, cityDetailKeywordGenerator, CityPageMeta } from "@/utils/meta"; export default function CityDetail({ region, events }: { region: Region; events: EventItem[] }) { const { t, i18n } = useTranslation(); @@ -179,10 +181,32 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { }), ); + const parsedEvents = pickEventSchema.parse(regionEvents?.records); + return { props: { + headMetas: { + title: `${region.name} - ${CityPageMeta[locale as currentSupportLocale].title}`, + des: cityDetailDescriptionGenerator(locale as currentSupportLocale, region.name, regionEvents.total), + keywords: cityDetailKeywordGenerator(locale as currentSupportLocale, { name: region.name }), + link: `/city/${regionCode}`, + }, + structuredData: { + ...breadcrumbGenerator({ + items: [ + { + name: CityPageMeta[locale as currentSupportLocale].title, + item: "/city", + }, + { + name: region.name, + item: `/city/${regionCode}`, + }, + ], + }), + }, region: region, - events: pickEventSchema.parse(regionEvents?.records), + events: parsedEvents, ...(await serverSideTranslations(locale, ["common"])), }, }; diff --git a/src/utils/meta.ts b/src/utils/meta.ts index 54bed19..d783663 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -148,6 +148,36 @@ function generateOrganizationKeywords(organization: { name: string }, locale: cu } } +function generateCityDetailKeywords(city: { name: string }, locale: currentSupportLocale) { + switch (locale) { + case "zh-Hans": + default: + return [ + `${city.name}`, + `${city.name}兽展`, + `${city.name}兽聚`, + `${city.name}兽展时间`, + `${city.name}兽展日历`, + ]; + case "zh-Hant": + return [ + `${city.name}`, + `${city.name}獸展`, + `${city.name}獸聚`, + `${city.name}獸展時間`, + `${city.name}獸展日曆`, + ]; + case "en": + return [ + `${city.name}`, + `${city.name} Furry Convention`, + `${city.name} Furry Gathering`, + `${city.name} Furry Convention Schedule`, + `${city.name} Furry Event Calendar`, + ]; + } +} + function keywordGenerator({ page, locale, @@ -227,45 +257,38 @@ function eventDescriptionGenerator( if (!event) { return defaultDescriptionGenerator(locale); } + const venue = `${event?.region?.localName || ""}${event?.address || ""}`.trim(); switch (locale) { case "zh-Hans": default: return event.startAt && event.endAt - ? `欢迎来到兽展日历!兽展日历提供关于“${event?.name}”的详细信息:这是由“${ - event?.organization?.name - }”举办的兽展,将于${formatDate( - event?.startAt!, + ? `“${event?.name}”是由“${event?.organization?.name}”举办的兽展,展会定于${formatDate( + event?.startAt, "YYYY年MM月DD日", locale, - )}至${formatDate(event?.endAt!, "YYYY年MM月DD日", locale)}在“${ - event?.region?.localName - }${event?.address}”举办,喜欢的朋友记得关注开始售票时间~` - : `欢迎来到兽展日历!兽展日历提供关于“${event?.name}”的详细信息:这是由“${event?.organization?.name}”举办的兽展,将在“${event?.region?.localName}${event?.address}”举办,喜欢的朋友记得关注开始售票时间~`; + )}至${formatDate(event?.endAt, "YYYY年MM月DD日", locale)}${venue ? `在“${venue}”举办` : "举办"}` + : `“${event?.name}”是由“${event?.organization?.name}”举办的兽展,展会的举办时间还没有公布,${venue ? `预计将在“${venue}”举办` : "举办地点暂未公布"}`; case "zh-Hant": return event.startAt && event.endAt - ? `歡迎來到獸展日曆!獸展日曆提供關於“${event?.name}”的詳細信息:這是由“${ - event?.organization?.name - }”舉辦的獸展,將於${formatDate(event?.startAt!, "YYYY/MM/DD", locale)}至${formatDate( - event?.endAt!, - "YYYY/MM/DD", + ? `“${event?.name}”是由“${event?.organization?.name}”舉辦的獸展,展會定於${formatDate( + event?.startAt, + "YYYY年MM月DD日", locale, - )}在“${ - event?.region?.localName - }${event?.address}”舉辦,喜歡的朋友記得關注開票時間~` - : `歡迎來到獸展日曆!獸展日曆提供關於“${event?.name}”的詳細信息:這是由“${event?.organization?.name}”舉辦的獸展,將在“${event?.region?.localName}${event?.address}”舉辦,喜歡的朋友記得關注開票時間~`; + )}至${formatDate(event?.endAt, "YYYY年MM月DD日", locale)}${venue ? `在“${venue}”舉辦` : "舉辦"}` + : `“${event?.name}”是由“${event?.organization?.name}”舉辦的獸展,展會的舉辦時間還沒有公佈,${venue ? `預計將在“${venue}”舉辦` : "舉辦地點暫未公佈"}`; case "en": return event.startAt && event.endAt - ? `Details about "${event?.name}": This furry convention is organized by "${ + ? `"${event?.name}" is a furry convention organized by "${ event?.organization?.name - }" and will be held from ${formatDate(event?.startAt!, "MMMM DD, YYYY", locale)} to ${formatDate( - event?.endAt!, + }". The event is scheduled to be held from ${formatDate( + event?.startAt, "MMMM DD, YYYY", locale, - )} at "${ - event?.region?.localName - }${event?.address}". Stay tuned for ticket sales!` - : `Welcome to FurConsCalendar! FCC provides detailed information about "${event?.name}": This furry convention is organized by "${event?.organization?.name}" and will be held at "${event?.region?.localName}${event?.address}". Stay tuned for ticket sales!`; + )} to ${formatDate(event?.endAt, "MMMM DD, YYYY", locale)}${venue ? ` at "${venue}".` : "."}` + : `"${event?.name}" is a furry convention organized by "${ + event?.organization?.name + }". The event date has not been announced yet${venue ? ` and is expected to be held at "${venue}".` : ", and the venue has not been announced yet."}`; } } @@ -310,11 +333,33 @@ function organizationDetailDescriptionGenerator( } } +function cityDetailDescriptionGenerator( + locale: currentSupportLocale, + cityName: string, + eventCount: number, +) { + switch (locale) { + case "zh-Hans": + default: + return `这里是 ${cityName} 的兽展活动列表,累计收录了 ${eventCount} 场兽展(兽聚)活动。`; + case "zh-Hant": + return `這裡是 ${cityName} 的獸展活動列表,累計收錄了 ${eventCount} 場獸展(獸聚)活動。`; + case "en": + return `This is the furry event list for ${cityName}, with a total of ${eventCount} recorded furry conventions and gatherings.`; + } +} + +function cityDetailKeywordGenerator(locale: currentSupportLocale, city: { name: string }) { + return generateCityDetailKeywords(city, locale).join(","); +} + export { universalKeywords, keywordGenerator, + cityDetailKeywordGenerator, titleGenerator, defaultDescriptionGenerator as descriptionGenerator, eventDescriptionGenerator, + cityDetailDescriptionGenerator, organizationDetailDescriptionGenerator, };