feat(SW-2007): Added metadata for hotel subpages

Approved-by: Fredrik Thorsson
Approved-by: Matilda Landström
This commit is contained in:
Erik Tiekstra
2025-03-26 10:36:58 +00:00
parent c065101b7c
commit ca23589f88
9 changed files with 246 additions and 11 deletions

View File

@@ -23,6 +23,7 @@
"Access size": "Adgangsstørrelse",
"Accessibility": "Tilgængelighed",
"Accessibility at {hotel}": "Tilgængelighed på {hotel}",
"Accessibility information for {hotelName} in {destination}": "Tilgængelighedsinformation for {hotelName} i {destination}",
"Accessibility room": "Tilgængelighedsrum",
"Accommodation": "Indkvartering",
"Account unlinked, reloading": "Account unlinked, reloading",
@@ -268,6 +269,7 @@
"Explore all our hotels": "Udforsk alle vores hoteller",
"Explore nearby": "Udforsk i nærheden",
"Explore {city}": "Udforsk {city}",
"Explore {restaurantName} at {hotelName} in {destination}": "Udforsk {restaurantName} på {hotelName} i {destination}",
"Extra bed (child) × {count}": "Ekstra seng (barn) × {count}",
"Extra bed will be provided additionally": "Der vil blive stillet en ekstra seng til rådighed",
"Extras to your booking": "Tillæg til din booking",
@@ -338,6 +340,7 @@
"Guests": "Gæster",
"Guests & Rooms": "Gæster & værelser",
"Gym": "Fitnesscenter",
"Gym & Health Facilities at {hotelName} in {destination}": "Fitnesscenter & Sundhedsfaciliteter på {hotelName} i {destination}",
"Gym & Wellness": "Velvære & Motion",
"Half circle": "Half circle",
"Have you showed this benefit to the hotel staff?": "Har du vist denne fordel for hotellets personale?",
@@ -450,6 +453,7 @@
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
"Meetings & Conferences": "Møder & Konferencer",
"Meetings, Conferences & Events": "Møder, Konferencer & Arrangementer",
"Meetings, Conferences & Events at {hotelName} in {destination}": "Møder, Konferencer & Events på {hotelName} i {destination}",
"Member": "Medlem",
"Member Since: {value}": "Member Since: {value}",
"Member discount": "Member discount",
@@ -563,6 +567,7 @@
"Parking can be reserved in advance": "Parkering kan reserveres på forhånd",
"Parking can be reserved in advance: No": "Parkering kan reserveres på forhånd: Nej",
"Parking can be reserved in advance: Yes": "Parkering kan reserveres på forhånd: Ja",
"Parking information for {hotelName} in {destination}": "Parkering information for {hotelName} i {destination}",
"Password": "Adgangskode",
"Pay at check-in": "Betal ved check-in",
"Pay later": "Betal senere",

View File

@@ -23,6 +23,7 @@
"Access size": "Zugriffsgröße",
"Accessibility": "Zugänglichkeit",
"Accessibility at {hotel}": "Barrierefreiheit im {hotel}",
"Accessibility information for {hotelName} in {destination}": "Barrierefreiheitsinformationen für {hotelName} in {destination}",
"Accessibility room": "Barrierefreies Zimmer",
"Accommodation": "Unterkunft",
"Account unlinked, reloading": "Account unlinked, reloading",
@@ -269,6 +270,7 @@
"Explore all our hotels": "Entdecken Sie alle unsere Hotels",
"Explore nearby": "Erkunden Sie die Umgebung",
"Explore {city}": "Erkunden Sie {city}",
"Explore {restaurantName} at {hotelName} in {destination}": "Erkunden Sie {restaurantName} im {hotelName} in {destination}",
"Extra bed (child) × {count}": "Ekstra seng (Kind) × {count}",
"Extra bed will be provided additionally": "Ein zusätzliches Bett wird bereitgestellt",
"Extras to your booking": "Extras zu Ihrer Buchung",
@@ -339,6 +341,7 @@
"Guests": "Gäste",
"Guests & Rooms": "Gäste & Zimmer",
"Gym": "Fitnessstudio",
"Gym & Health Facilities at {hotelName} in {destination}": "Fitnessstudio & Gesundheitseinrichtungen im {hotelName} in {destination}",
"Gym & Wellness": "Wellness & Bewegung",
"Half circle": "Half circle",
"Have you showed this benefit to the hotel staff?": "Haben Sie dem Hotelpersonal diesen Vorteil gezeigt?",
@@ -451,6 +454,7 @@
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
"Meetings & Conferences": "Tagungen & Konferenzen",
"Meetings, Conferences & Events": "Møder, Konferencer & Arrangementer",
"Meetings, Conferences & Events at {hotelName} in {destination}": "Tagungen, Konferenzen & Veranstaltungen im {hotelName} in {destination}",
"Member": "Mitglied",
"Member Since: {value}": "Member Since: {value}",
"Member discount": "Member discount",
@@ -563,6 +567,7 @@
"Parking can be reserved in advance": "Parkplätze können im Voraus reserviert werden",
"Parking can be reserved in advance: No": "Parkplätze können im Voraus reserviert werden: Nein",
"Parking can be reserved in advance: Yes": "Parkplätze können im Voraus reserviert werden: Ja",
"Parking information for {hotelName} in {destination}": "Parkinformationen für {hotelName} in {destination}",
"Password": "Passwort",
"Pay at check-in": "Beim Check-in bezahlen",
"Pay later": "Später bezahlen",

View File

@@ -23,6 +23,7 @@
"Access size": "Access size",
"Accessibility": "Accessibility",
"Accessibility at {hotel}": "Accessibility at {hotel}",
"Accessibility information for {hotelName} in {destination}": "Accessibility information for {hotelName} in {destination}",
"Accessibility room": "Accessibility room",
"Accommodation": "Accommodation",
"Account unlinked, reloading": "Account unlinked, reloading",
@@ -267,6 +268,7 @@
"Explore all our hotels": "Explore all our hotels",
"Explore nearby": "Explore nearby",
"Explore {city}": "Explore {city}",
"Explore {restaurantName} at {hotelName} in {destination}": "Explore {restaurantName} at {hotelName} in {destination}",
"Extra bed (child) × {count}": "Extra bed (child) × {count}",
"Extra bed will be provided additionally": "Extra bed will be provided additionally",
"Extras to your booking": "Extras to your booking",
@@ -337,6 +339,7 @@
"Guests": "Guests",
"Guests & Rooms": "Guests & Rooms",
"Gym": "Gym",
"Gym & Health Facilities at {hotelName} in {destination}": "Gym & Health Facilities at {hotelName} in {destination}",
"Gym & Wellness": "Gym & Wellness",
"Half circle": "Half circle",
"Have you showed this benefit to the hotel staff?": "Have you showed this benefit to the hotel staff?",
@@ -449,6 +452,7 @@
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
"Meetings & Conferences": "Meetings & Conferences",
"Meetings, Conferences & Events": "Meetings, Conferences & Events",
"Meetings, Conferences & Events at {hotelName} in {destination}": "Meetings, Conferences & Events at {hotelName} in {destination}",
"Member": "Member",
"Member Since: {value}": "Member Since: {value}",
"Member discount": "Member discount",
@@ -562,6 +566,7 @@
"Parking can be reserved in advance": "Parking can be reserved in advance",
"Parking can be reserved in advance: No": "Parking can be reserved in advance: No",
"Parking can be reserved in advance: Yes": "Parking can be reserved in advance: Yes",
"Parking information for {hotelName} in {destination}": "Parking information for {hotelName} in {destination}",
"Password": "Password",
"Pay at check-in": "Pay at check-in",
"Pay later": "Pay later",

View File

@@ -23,6 +23,7 @@
"Access size": "Pääsyn koko",
"Accessibility": "Saavutettavuus",
"Accessibility at {hotel}": "Esteettömyys {hotel}",
"Accessibility information for {hotelName} in {destination}": "Esteettömyystiedot hotellille {hotelName} kohteessa {destination}",
"Accessibility room": "Esteetön huone",
"Accommodation": "Majoitus",
"Account unlinked, reloading": "Account unlinked, reloading",
@@ -268,6 +269,7 @@
"Explore all our hotels": "Tutustu kaikkiin hotelleihimme",
"Explore nearby": "Tutustu lähialueeseen",
"Explore {city}": "Tutustu {city}",
"Explore {restaurantName} at {hotelName} in {destination}": "Tutustu {restaurantName} hotellissa {hotelName} kohteessa {destination}",
"Extra bed (child) × {count}": "Lisävuode (lasta) × {count}",
"Extra bed will be provided additionally": "Lisävuode toimitetaan erikseen",
"Extras to your booking": "Varauksessa lisäpalveluita",
@@ -338,6 +340,7 @@
"Guests": "Vierailijat",
"Guests & Rooms": "Vieraat & Huoneet",
"Gym": "Kuntosali",
"Gym & Health Facilities at {hotelName} in {destination}": "Kuntosali ja terveyspalvelut hotellissa {hotelName} kohteessa {destination}",
"Gym & Wellness": "Hyvinvointi & Liikunta",
"Half circle": "Half circle",
"Have you showed this benefit to the hotel staff?": "Oletko näyttänyt tämän edun hotellin henkilökunnalle?",
@@ -450,6 +453,7 @@
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
"Meetings & Conferences": "Kokoukset & Konferenssit",
"Meetings, Conferences & Events": "Tagungen, Konferenzen & Veranstaltungen",
"Meetings, Conferences & Events at {hotelName} in {destination}": "Kokoukset, konferenssit ja tapahtumat hotellissa {hotelName} kohteessa {destination}",
"Member": "Jäsen",
"Member Since: {value}": "Member Since: {value}",
"Member discount": "Member discount",
@@ -562,6 +566,7 @@
"Parking can be reserved in advance": "Pysäköintipaikan voi varata etukäteen",
"Parking can be reserved in advance: No": "Pysäköintipaikan voi varata etukäteen: Ei",
"Parking can be reserved in advance: Yes": "Pysäköintipaikan voi varata etukäteen: Kyllä",
"Parking information for {hotelName} in {destination}": "Pysäköintitiedot hotellille {hotelName} kohteessa {destination}",
"Password": "Salasana",
"Pay at check-in": "Maksa tarkistuksessa",
"Pay later": "Maksa myöhemmin",

View File

@@ -23,6 +23,7 @@
"Access size": "Tilgangsstørrelse",
"Accessibility": "Tilgjengelighet",
"Accessibility at {hotel}": "Tilgjengelighet på {hotel}",
"Accessibility information for {hotelName} in {destination}": "Tilgjengelighetsinformasjon for {hotelName} i {destination}",
"Accessibility room": "Tilgjengelighetsrom",
"Accommodation": "Overnatting",
"Account unlinked, reloading": "Account unlinked, reloading",
@@ -267,6 +268,7 @@
"Explore all our hotels": "Utforsk alle våre hoteller",
"Explore nearby": "Utforsk i nærheten",
"Explore {city}": "Utforsk {city}",
"Explore {restaurantName} at {hotelName} in {destination}": "Utforsk {restaurantName} på {hotelName} i {destination}",
"Extra bed (child) × {count}": "Ekstra seng (barn) × {count}",
"Extra bed will be provided additionally": "Ekstra seng vil bli tilgjengelig",
"Extras to your booking": "Tilvalg til bestillingen din",
@@ -337,6 +339,7 @@
"Guests": "Gjester",
"Guests & Rooms": "Gjester & rom",
"Gym": "Treningsstudio",
"Gym & Health Facilities at {hotelName} in {destination}": "Treningsstudio og helsefasiliteter på {hotelName} i {destination}",
"Gym & Wellness": "Velvære & Trening",
"Half circle": "Half circle",
"Have you showed this benefit to the hotel staff?": "Har du vist denne fordelen til hotellpersonalet?",
@@ -449,6 +452,7 @@
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
"Meetings & Conferences": "Møter & Konferanser",
"Meetings, Conferences & Events": "Møter, Konferanser & Arrangementer",
"Meetings, Conferences & Events at {hotelName} in {destination}": "Møter, konferanser og arrangementer på {hotelName} i {destination}",
"Member": "Medlem",
"Member Since: {value}": "Member Since: {value}",
"Member discount": "Member discount",
@@ -561,6 +565,7 @@
"Parking can be reserved in advance": "Parkering kan reserveres på forhånd",
"Parking can be reserved in advance: No": "Parkering kan reserveres på forhånd: Nei",
"Parking can be reserved in advance: Yes": "Parkering kan reserveres på forhånd: Ja",
"Parking information for {hotelName} in {destination}": "Parkeringinformasjon for {hotelName} i {destination}",
"Password": "Passord",
"Pay at check-in": "Betal ved check-in",
"Pay later": "Betal senere",

View File

@@ -23,6 +23,7 @@
"Access size": "Åtkomststorlek",
"Accessibility": "Tillgänglighet",
"Accessibility at {hotel}": "Tillgänglighet på {hotel}",
"Accessibility information for {hotelName} in {destination}": "Tillgänglighetsinformation för {hotelName} i {destination}",
"Accessibility room": "Tillgänglighetsrum",
"Accommodation": "Boende",
"Account unlinked, reloading": "Account unlinked, reloading",
@@ -267,6 +268,7 @@
"Explore all our hotels": "Utforska alla våra hotell",
"Explore nearby": "Utforska i närheten",
"Explore {city}": "Utforska {city}",
"Explore {restaurantName} at {hotelName} in {destination}": "Utforska {restaurantName} på {hotelName} i {destination}",
"Extra bed (child) × {count}": "Extra säng (barn) × {count}",
"Extra bed will be provided additionally": "Extra säng kommer att tillhandahållas",
"Extras to your booking": "Extra tillval till din bokning",
@@ -337,6 +339,7 @@
"Guests": "Gäster",
"Guests & Rooms": "Gäster & rum",
"Gym": "Gym",
"Gym & Health Facilities at {hotelName} in {destination}": "Gym & hälsomöjligheter på {hotelName} i {destination}",
"Gym & Wellness": "Hälsa & Träning",
"Half circle": "Half circle",
"Have you showed this benefit to the hotel staff?": "Har du visat denna fördel för hotellpersonalen?",
@@ -449,6 +452,7 @@
"Max. {max, plural, one {{range} guest} other {{range} guests}}": "Max. {max, plural, one {{range} guest} other {{range} guests}}",
"Meetings & Conferences": "Möten & Konferenser",
"Meetings, Conferences & Events": "Möten, Konferenser & Evenemang",
"Meetings, Conferences & Events at {hotelName} in {destination}": "Möten, konferenser och evenemang på {hotelName} i {destination}",
"Member": "Medlem",
"Member Since: {value}": "Member Since: {value}",
"Member discount": "Member discount",
@@ -561,6 +565,7 @@
"Parking can be reserved in advance": "Parkering kan reserveras i förväg",
"Parking can be reserved in advance: No": "Parkering kan bokas i förväg: Nej",
"Parking can be reserved in advance: Yes": "Parkering kan bokas i förväg: Ja",
"Parking information for {hotelName} in {destination}": "Parkeringinformation för {hotelName} i {destination}",
"Password": "Lösenord",
"Pay at check-in": "Betala vid check-in",
"Pay later": "Betala senare",

View File

@@ -8,6 +8,7 @@ import { env } from "@/env/server"
import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel"
import { additionalDataAttributesSchema } from "../../hotels/schemas/hotel/include/additionalData"
import { imageSchema } from "../../hotels/schemas/image"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import { systemSchema } from "../schemas/system"
import { getDescription, getImage, getTitle } from "./utils"
@@ -98,13 +99,44 @@ export const rawMetadataSchema = z.object({
blocks: metaDataBlocksSchema,
hotel_page_id: z.string().optional().nullable(),
hotelData: hotelAttributesSchema
.pick({ name: true, address: true, hotelContent: true })
.pick({
name: true,
address: true,
hotelContent: true,
healthFacilities: true,
})
.optional()
.nullable(),
additionalHotelData: additionalDataAttributesSchema
.pick({ gallery: true })
.pick({
gallery: true,
hotelParking: true,
healthAndFitness: true,
hotelSpecialNeeds: true,
meetingRooms: true,
parkingImages: true,
accessibility: true,
conferencesAndMeetings: true,
})
.optional()
.nullable(),
hotelRestaurants: z
.array(
z.object({
nameInUrl: z.string().optional().nullable(),
elevatorPitch: z.string().optional().nullable(),
name: z.string().optional().nullable(),
content: z
.object({
images: z.array(imageSchema).optional().nullable(),
})
.optional()
.nullable(),
})
)
.optional()
.nullable(),
subpageUrl: z.string().optional().nullable(),
location: z.string().optional().nullable(),
filter: z.string().optional().nullable(),
filterType: z.enum(["facility", "surroundings"]).optional().nullable(),

View File

@@ -250,10 +250,12 @@ export const metadataQueryRouter = router({
...hotelPageData,
...(hotelData
? {
hotelData: hotelData?.hotel,
additionalHotelData: hotelData?.additionalData,
hotelData: hotelData.hotel,
additionalHotelData: hotelData.additionalData,
hotelRestaurants: hotelData.restaurants,
}
: {}),
subpageUrl: input.subpage,
})
break
case PageContentTypeEnum.startPage:

View File

@@ -81,6 +81,62 @@ export async function getTitle(data: RawMetadataSchema) {
return metadata.title
}
if (data.hotelData) {
if (data.subpageUrl) {
const restaurantSubPage = data.hotelRestaurants?.find(
(restaurant) => restaurant.nameInUrl === data.subpageUrl
)
if (restaurantSubPage) {
return intl.formatMessage(
{ id: "Explore {restaurantName} at {hotelName} in {destination}" },
{
restaurantName: restaurantSubPage.name,
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
}
switch (data.subpageUrl) {
case data.additionalHotelData?.hotelParking.nameInUrl:
return intl.formatMessage(
{ id: "Parking information for {hotelName} in {destination}" },
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
case data.additionalHotelData?.healthAndFitness.nameInUrl:
return intl.formatMessage(
{ id: "Gym & Health Facilities at {hotelName} in {destination}" },
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
return intl.formatMessage(
{
id: "Accessibility information for {hotelName} in {destination}",
},
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
case data.additionalHotelData?.meetingRooms.nameInUrl:
return intl.formatMessage(
{
id: "Meetings, Conferences & Events at {hotelName} in {destination}",
},
{
hotelName: data.hotelData.name,
destination: data.hotelData.address.city,
}
)
default:
break
}
}
return intl.formatMessage(
{ id: "Stay at {hotelName} | Hotel in {destination}" },
{
@@ -129,6 +185,40 @@ export function getDescription(data: RawMetadataSchema) {
return metadata.description
}
if (data.hotelData) {
if (data.subpageUrl) {
let subpageDescription: string | undefined
const restaurantSubPage = data.hotelRestaurants?.find(
(restaurant) => restaurant.nameInUrl === data.subpageUrl
)
if (restaurantSubPage?.elevatorPitch) {
subpageDescription = restaurantSubPage.elevatorPitch
}
switch (data.subpageUrl) {
case data.additionalHotelData?.hotelParking.nameInUrl:
subpageDescription =
data.additionalHotelData?.hotelParking.elevatorPitch
break
case data.additionalHotelData?.healthAndFitness.nameInUrl:
subpageDescription =
data.additionalHotelData?.healthAndFitness.elevatorPitch
break
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
subpageDescription =
data.additionalHotelData?.hotelSpecialNeeds.elevatorPitch
break
case data.additionalHotelData?.meetingRooms.nameInUrl:
subpageDescription =
data.additionalHotelData?.meetingRooms.elevatorPitch
break
default:
break
}
if (subpageDescription) {
return truncateTextAfterLastPeriod(subpageDescription)
}
}
return data.hotelData.hotelContent.texts.descriptions?.short
}
if (data.preamble) {
@@ -156,9 +246,6 @@ export function getDescription(data: RawMetadataSchema) {
export function getImage(data: RawMetadataSchema) {
const metadataImage = data.web?.seo_metadata?.seo_image
const heroImage = data.hero_image || data.header?.hero_image
const hotelImage =
data.additionalHotelData?.gallery?.heroImages?.[0] ||
data.additionalHotelData?.gallery?.smallerImages?.[0]
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
if (metadataImage) {
@@ -169,10 +256,94 @@ export function getImage(data: RawMetadataSchema) {
height: metadataImage.dimensions.height,
}
}
if (hotelImage) {
return {
url: hotelImage.imageSizes.small,
alt: hotelImage.metaData.altText || undefined,
if (data.hotelData) {
if (data.subpageUrl) {
let subpageImage: { url: string; alt: string } | undefined
const restaurantSubPage = data.hotelRestaurants?.find(
(restaurant) => restaurant.nameInUrl === data.subpageUrl
)
const restaurantImage = restaurantSubPage?.content?.images?.[0]
if (restaurantImage) {
subpageImage = {
url: restaurantImage.imageSizes.small,
alt:
restaurantImage.metaData.altText ||
restaurantImage.metaData.altText_En ||
"",
}
}
switch (data.subpageUrl) {
case data.additionalHotelData?.hotelParking.nameInUrl:
const parkingImage =
data.additionalHotelData?.parkingImages?.heroImages[0]
if (parkingImage) {
subpageImage = {
url: parkingImage.imageSizes.small,
alt:
parkingImage.metaData.altText ||
parkingImage.metaData.altText_En ||
"",
}
}
break
case data.additionalHotelData?.healthAndFitness.nameInUrl:
const wellnessImage = data.hotelData.healthFacilities.find(
(fac) => fac.content.images.length
)?.content.images[0]
if (wellnessImage) {
subpageImage = {
url: wellnessImage.imageSizes.small,
alt:
wellnessImage.metaData.altText ||
wellnessImage.metaData.altText_En ||
"",
}
}
break
case data.additionalHotelData?.hotelSpecialNeeds.nameInUrl:
const accessibilityImage =
data.additionalHotelData?.accessibility?.heroImages[0]
if (accessibilityImage) {
subpageImage = {
url: accessibilityImage.imageSizes.small,
alt:
accessibilityImage.metaData.altText ||
accessibilityImage.metaData.altText_En ||
"",
}
}
break
case data.additionalHotelData?.meetingRooms.nameInUrl:
const meetingImage =
data.additionalHotelData?.conferencesAndMeetings?.heroImages[0]
if (meetingImage) {
subpageImage = {
url: meetingImage.imageSizes.small,
alt:
meetingImage.metaData.altText ||
meetingImage.metaData.altText_En ||
"",
}
}
break
default:
break
}
if (subpageImage) {
return subpageImage
}
}
const hotelImage =
data.additionalHotelData?.gallery?.heroImages?.[0] ||
data.additionalHotelData?.gallery?.smallerImages?.[0]
if (hotelImage) {
return {
url: hotelImage.imageSizes.small,
alt: hotelImage.metaData.altText || undefined,
}
}
}
if (heroImage) {