From 869c9b67b82e7ccf4129d60acc0127c59bb23aac Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Thu, 3 Oct 2024 07:42:26 +0000 Subject: [PATCH 1/3] Merged in feat/SW-475-enter-details-header (pull request #630) Feat/SW-475 enter details header * feat(SW-475): updated hotel mock data to reflect api * feat(SW-475): Added hotel selection header with sidepeek buttons * fix(SW-475): fixes from PR * fix(SW-475): changed intl string Approved-by: Arvid Norlin --- .../hotelreservation/[section]/page.tsx | 7 +- .../hotelDetailSidePeek.module.css | 4 + .../HotelDetailSidePeek/index.tsx | 58 ++ .../hotelSelectionHeader.module.css | 52 ++ .../HotelSelectionHeader/index.tsx | 52 ++ .../Text/Body/body.module.css | 4 + .../TempDesignSystem/Text/Body/variants.ts | 1 + server/routers/hotels/tempHotelData.json | 764 +++++++++++++++--- .../selectRate/hotelSelectionHeader.ts | 5 + 9 files changed, 840 insertions(+), 107 deletions(-) create mode 100644 components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css create mode 100644 components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx create mode 100644 components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css create mode 100644 components/HotelReservation/HotelSelectionHeader/index.tsx create mode 100644 types/components/hotelReservation/selectRate/hotelSelectionHeader.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx index 45ca7d308..a0373645b 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx @@ -1,6 +1,8 @@ import { serverClient } from "@/lib/trpc/server" +import { getHotelDataSchema } from "@/server/routers/hotels/output" import tempHotelData from "@/server/routers/hotels/tempHotelData.json" +import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader" import BedSelection from "@/components/HotelReservation/SelectRate/BedSelection" import BreakfastSelection from "@/components/HotelReservation/SelectRate/BreakfastSelection" import Details from "@/components/HotelReservation/SelectRate/Details" @@ -79,7 +81,7 @@ export default async function SectionsPage({ setLang(params.lang) // TODO: Use real endpoint. - const hotel = tempHotelData.data.attributes + const hotel = getHotelDataSchema.parse(tempHotelData) const rooms = await serverClient().hotel.rates.get({ // TODO: pass the correct hotel ID and all other parameters that should be included in the search @@ -107,8 +109,7 @@ export default async function SectionsPage({ return (
- {/* TODO: Add Hotel Listing Card */} -
Hotel Listing Card TBI
+
diff --git a/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css new file mode 100644 index 000000000..fb400465e --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/hotelDetailSidePeek.module.css @@ -0,0 +1,4 @@ +.buttons { + display: flex; + gap: var(--Spacing-x3); +} diff --git a/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx new file mode 100644 index 000000000..d4e3eb829 --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/HotelDetailSidePeek/index.tsx @@ -0,0 +1,58 @@ +"use client" + +import { useState } from "react" +import { useIntl } from "react-intl" + +import ChevronRightSmallIcon from "@/components/Icons/ChevronRightSmall" +import Button from "@/components/TempDesignSystem/Button" +import SidePeek from "@/components/TempDesignSystem/SidePeek" + +import styles from "./hotelDetailSidePeek.module.css" + +export default function HotelDetailSidePeek() { + const intl = useIntl() + const [isOpen, setIsOpen] = useState(false) + + function toggleSidePeek() { + setIsOpen(!isOpen) + } + + return ( + <> +
+ + +
+ setIsOpen(false)} + > +
TBD
+
+ + ) +} diff --git a/components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css b/components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css new file mode 100644 index 000000000..9646ec746 --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/hotelSelectionHeader.module.css @@ -0,0 +1,52 @@ +.hotelSelectionHeader { + display: flex; + flex-direction: column; + background-color: var(--Base-Surface-Subtle-Normal); + padding: var(--Spacing-x3) var(--Spacing-x2); + justify-content: center; + gap: var(--Spacing-x3); +} + +.titleContainer { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + gap: var(--Spacing-x1); +} + +.descriptionContainer { + display: flex; + flex-direction: column; + gap: var(--Spacing-x-one-and-half); +} + +.address { + display: flex; + gap: var(--Spacing-x-one-and-half); + font-style: normal; +} + +.dividerContainer { + display: none; +} + +@media (min-width: 768px) { + .hotelSelectionHeader { + flex-direction: row; + padding: var(--Spacing-x4) var(--Spacing-x5); + gap: var(--Spacing-x6); + } + + .titleContainer > h1 { + white-space: nowrap; + } + + .dividerContainer { + display: block; + } + + .address { + gap: var(--Spacing-x3); + } +} diff --git a/components/HotelReservation/HotelSelectionHeader/index.tsx b/components/HotelReservation/HotelSelectionHeader/index.tsx new file mode 100644 index 000000000..c7e119b0a --- /dev/null +++ b/components/HotelReservation/HotelSelectionHeader/index.tsx @@ -0,0 +1,52 @@ +import Divider from "@/components/TempDesignSystem/Divider" +import Body from "@/components/TempDesignSystem/Text/Body" +import Caption from "@/components/TempDesignSystem/Text/Caption" +import Title from "@/components/TempDesignSystem/Text/Title" +import { getIntl } from "@/i18n" + +import HotelDetailSidePeek from "./HotelDetailSidePeek" + +import styles from "./hotelSelectionHeader.module.css" + +import { HotelSelectionHeaderProps } from "@/types/components/hotelReservation/selectRate/hotelSelectionHeader" + +export default async function HotelSelectionHeader({ + hotel, +}: HotelSelectionHeaderProps) { + const intl = await getIntl() + + return ( +
+
+ + {hotel.name} + +
+ + {hotel.address.streetAddress}, {hotel.address.city} + +
+ +
+ + {intl.formatMessage( + { + id: "Distance to city centre", + }, + { number: hotel.location.distanceToCentre } + )} + +
+
+
+ +
+
+ + {hotel.hotelContent.texts.descriptions.short} + + +
+
+ ) +} diff --git a/components/TempDesignSystem/Text/Body/body.module.css b/components/TempDesignSystem/Text/Body/body.module.css index 238128edd..30939d217 100644 --- a/components/TempDesignSystem/Text/Body/body.module.css +++ b/components/TempDesignSystem/Text/Body/body.module.css @@ -76,6 +76,10 @@ color: var(--UI-Text-Medium-contrast); } +.textHighContrast { + color: var(--UI-Text-High-contrast); +} + .white { color: var(--UI-Opacity-White-100); } diff --git a/components/TempDesignSystem/Text/Body/variants.ts b/components/TempDesignSystem/Text/Body/variants.ts index e55bbc3e2..055710eb3 100644 --- a/components/TempDesignSystem/Text/Body/variants.ts +++ b/components/TempDesignSystem/Text/Body/variants.ts @@ -11,6 +11,7 @@ const config = { pale: styles.pale, red: styles.red, textMediumContrast: styles.textMediumContrast, + textHighContrast: styles.textHighContrast, white: styles.white, peach50: styles.peach50, peach80: styles.peach80, diff --git a/server/routers/hotels/tempHotelData.json b/server/routers/hotels/tempHotelData.json index d77cbf416..2ec39da33 100644 --- a/server/routers/hotels/tempHotelData.json +++ b/server/routers/hotels/tempHotelData.json @@ -9,19 +9,11 @@ "cityName": "Stockholm", "ratings": { "tripAdvisor": { - "numberOfReviews": 2776, - "rating": 4.0, - "ratingImageUrl": "https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/4.5-15458-5.svg", - "webUrl": "https://www.tripadvisor.com/Hotel_Review-g189852-d229383-Reviews-Scandic_Continental-Stockholm.html", + "numberOfReviews": 2793, + "rating": 4, + "ratingImageUrl": "https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/4.0-15458-5.svg", + "webUrl": "https://www.tripadvisor.com/Hotel_Review-g189852-d229383-Reviews-Scandic_Continental-Stockholm.html?m=15458", "awards": [ - { - "displayName": "Travelers Choice", - "images": { - "small": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2024_L.png", - "medium": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2024_L.png", - "large": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2024_L.png" - } - }, { "displayName": "Travelers Choice", "images": { @@ -30,12 +22,7 @@ "large": "https://static.tacdn.com/img2/travelers_choice/widgets/tchotel_2023_L.png" } } - ], - "reviews": { - "widgetHtmlTagId": "TA_cdspropertydetail", - "widgetScriptEmbedUrlIframe": "//www.tripadvisor.com/WidgetEmbed-cdspropertydetail?locationId=12441627&partnerId=FDF3F5CC73C349C0A5AB94C0DD86BB76&lang=en&display=true", - "widgetScriptEmbedUrlJavaScript": "//www.tripadvisor.com/WidgetEmbed-cdspropertydetail?locationId=12441627&partnerId=FDF3F5CC73C349C0A5AB94C0DD86BB76&lang=en&display=false" - } + ] } }, "address": { @@ -157,7 +144,7 @@ "facilityInformation": "Relax with your favorite mixed drink in the bar on our roof terrace. Work out in our gym or relax in our sauna. At Scandic Continental we offer meeting rooms for up to 900 participants.", "surroundingInformation": "Explore popular attractions such as the Old Town, the Opera, City Hall and Royal Palace. You are close to public transport, buses and trams and within walking distance of all that the city centre has to offer.", "descriptions": { - "short": "Scandic Continental enjoys a fantastic location in Stockholm city centre. Close to shopping areas, culture, attractions and restaurants. ", + "short": "Scandic Continental enjoys a fantastic location in Stockholm city centre. Close to shopping areas, culture, attractions and restaurants.", "medium": "Scandic Continental enjoys a fantastic location in Stockholm city centre. Close to shopping areas, culture, attractions and restaurants. Popular attractions such as the Old Town, the Opera, City Hall and Royal Palace are all close by." } }, @@ -172,190 +159,149 @@ { "id": 5550, "name": "Bikes for loan", - "applyToAllHotels": false, "public": true, - "icon": "BikesForLoan", - "sortOrder": 700 + "sortOrder": 700, + "filter": "None" }, { "id": 1829, "name": "Gym", - "code": "HEA - TRAI", - "applyToAllHotels": false, "public": true, - "icon": "Gym", - "iconName": "Gym", - "sortOrder": 1700 + "sortOrder": 1700, + "filter": "Hotel facilities" }, { "id": 1833, "name": "Free WiFi", - "code": "IHF", - "applyToAllHotels": false, "public": true, - "icon": "FreeWiFi", - "sortOrder": 1900 + "sortOrder": 1900, + "filter": "None" }, { "id": 1834, "name": "Laundry service", - "code": "LAU", - "applyToAllHotels": false, "public": true, - "icon": "LaundryService", - "sortOrder": 200 + "sortOrder": 200, + "filter": "None" }, { "id": 1406, "name": "Parking - additional cost", - "code": "PAR", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 2665, "name": "Parking - garage", - "code": "GAR", - "applyToAllHotels": false, "public": true, - "icon": "Parking", - "iconName": "Garage", - "sortOrder": 1400 + "sortOrder": 1400, + "filter": "Hotel facilities" }, { "id": 1835, "name": "Pet-friendly rooms", - "code": "PET", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "Hotel facilities" }, { "id": 1379, "name": "Sauna", - "code": "SAU - RELX", - "applyToAllHotels": false, "public": true, - "icon": "Sauna", - "sortOrder": 2000 + "sortOrder": 2000, + "filter": "Hotel facilities" }, { "id": 1017, "name": "Meeting rooms", - "code": "MEE", - "applyToAllHotels": false, "public": true, - "icon": "Meeting", - "sortOrder": 9000 + "sortOrder": 9000, + "filter": "None" }, { "id": 1382, "name": "Outdoor terrace", - "code": "-", - "applyToAllHotels": false, "public": true, - "icon": "OutdoorTerrace", - "sortOrder": 1000 + "sortOrder": 1000, + "filter": "Hotel facilities" }, { "id": 1408, "name": "Scandic Shop 24 hrs", - "code": "SHOP", - "applyToAllHotels": false, "public": true, - "icon": "Shop", - "sortOrder": 100 + "sortOrder": 100, + "filter": "None" }, { "id": 1606, "name": "Sky bar", - "code": "-", - "applyToAllHotels": false, "public": true, - "icon": "Skybar", - "iconName": "Sky Bar / Rooftop Bar", - "sortOrder": 1100 + "sortOrder": 1100, + "filter": "Hotel facilities" }, { "id": 5806, "name": "Meeting / conference facilities", - "code": "MEE - MEETING ", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 1500 + "sortOrder": 1500, + "filter": "None" }, { "id": 1607, "name": "Golf course (0-30 km)", - "code": "GOLF", - "applyToAllHotels": false, "public": false, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "Hotel surroundings" }, { "id": 971, "name": "Shopping", - "code": "-", - "applyToAllHotels": false, "public": false, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "Hotel surroundings" }, { "id": 1911, "name": "24 hours security", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 1913, "name": "Overnight security", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 162583, "name": "Laundry service - express", - "applyToAllHotels": false, "public": true, - "icon": "ExpressLaundryService", - "iconName": "Express dry cleaning", - "sortOrder": 300 + "sortOrder": 300, + "filter": "None" }, { "id": 229144, "name": "TV with Chromecast", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 1407, "name": "Serves breakfast (always included)", - "code": "-", - "applyToAllHotels": false, "public": true, - "icon": "None", - "sortOrder": 0 + "sortOrder": 0, + "filter": "None" }, { "id": 1378, "name": "Room service", - "code": "ROO - R/S", - "applyToAllHotels": false, "public": true, - "icon": "RoomService", - "sortOrder": 400 + "sortOrder": 400, + "filter": "None" } ], "healthFacilities": [ @@ -1067,7 +1013,617 @@ "instagram": "https://www.instagram.com/scandiccontinental/", "facebook": "https://www.facebook.com/scandiccontinental/" }, - "isActive": true - } + "isActive": true, + "gallery": { + "heroImages": [ + { + "metaData": { + "title": "Superior plus, room", + "altText": "Superior plus, room", + "altText_En": "Superior plus, room", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/5rrxa1aq23taddapu11q/scandic-continental-room-superiorplus.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/66hti5xhqzmr8jhab213/scandic-continental-room-superiorplus.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/9lplgtij1oyjwaz93lrs/scandic-continental-room-superiorplus.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/f5bdchr9zeat67jri8kw/scandic-continental-room-superiorplus.jpg" + } + }, + { + "metaData": { + "title": "Entrance, restaurant Market", + "altText": "Entrance, restaurant Market", + "altText_En": "Entrance, restaurant Market", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/9qgydydjtavu128kv2xt/scandic-continental-entrance-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/0c0sz9g0r9hhejw62ez9/scandic-continental-entrance-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ka3ku5zqp5uuqpfjddot/scandic-continental-entrance-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ksa5jgagck9sbj2uarz5/scandic-continental-entrance-themarket.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Terrace", + "altText_En": "Terrace", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/m9yj4g160snutij4sivk/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fqqakeunmamm3uetf47w/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/6fp0avu8yjrh38zv1as8/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/j8dveyubybb4f7or9qay/Scandic_Continental_Capitol_Terrace.jpg" + } + } + ], + "smallerImages": [ + { + "metaData": { + "title": "Entrance", + "altText": "Entrance from Vasagatan", + "altText_En": "Entrance from Vasagatan", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/jlnmjycubt2ks3oiaoee/scandic-continental-entrance-vasagatan.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/izgmvkaesxw9jynunuyo/scandic-continental-entrance-vasagatan.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/sfasl29ijndz2ez6lywn/scandic-continental-entrance-vasagatan.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/9vcvf9x9exykkl2yf0bg/scandic-continental-entrance-vasagatan.jpg" + } + }, + { + "metaData": { + "title": "Standard room", + "altText": "Standard room", + "altText_En": "Standard room", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/qj2en3cqvvadxe5y5b6y/scandic-continental-room-standard.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/16kz0cyxngrwe7880gjh/scandic-continental-room-standard.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/x8r0of336j2rsrhvwlqy/scandic-continental-room-standard.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/co3h6nr4p28j2tn641sz/scandic-continental-room-standard.jpg" + } + }, + { + "metaData": { + "title": "Junior suite", + "altText": "Junior suite, detail", + "altText_En": "Junior suite, detail", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/tijveokv1qmnmy964iv9/scandic-continental-room-juniorsuite-detail-1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/i7h3u2x6a9ta46jxb2tg/scandic-continental-room-juniorsuite-detail-1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/hv4on6ec8qlcxmcd0enl/scandic-continental-room-juniorsuite-detail-1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/3eakprn9n0zwyjpzpy6p/scandic-continental-room-juniorsuite-detail-1.jpg" + } + }, + { + "metaData": { + "title": "Junior Suite", + "altText": "Junior Suite", + "altText_En": "Junior Suite", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/7h4bonsezjsjz1lpg6hd/scandic-continental-room-juniorsuite.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/gge47e5ach2e0kuakggx/scandic-continental-room-juniorsuite.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/cts4y9u9w02ockyyoze7/scandic-continental-room-juniorsuite.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/f8aojtx38vb7ywv33cf6/scandic-continental-room-juniorsuite.jpg" + } + }, + { + "metaData": { + "title": "Superior plus, room", + "altText": "Superior plus, room", + "altText_En": "Superior plus, room", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/5rrxa1aq23taddapu11q/scandic-continental-room-superiorplus.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/66hti5xhqzmr8jhab213/scandic-continental-room-superiorplus.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/9lplgtij1oyjwaz93lrs/scandic-continental-room-superiorplus.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/f5bdchr9zeat67jri8kw/scandic-continental-room-superiorplus.jpg" + } + }, + { + "metaData": { + "title": "Superior room, detail", + "altText": "Superior room, detail", + "altText_En": "Superior room, detail", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/6jjitns74k9nutn9v9tz/scandic-continental-room-superior-detail.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ft3se6waag20lx6hxtn9/scandic-continental-room-superior-detail.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wsfl5usd8qbdm929dvxu/scandic-continental-room-superior-detail.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/duedba0io5xew6almv7l/scandic-continental-room-superior-detail.jpg" + } + }, + { + "metaData": { + "title": "Junior suite, bathroom", + "altText": "Junior suite, bathroom", + "altText_En": "Junior suite, bathroom", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/2cpdxrywepicqnp9eoob/scandic-continental-room-juniorsuite-bathroom-1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ynkzimfaldgef0ukj7q5/scandic-continental-room-juniorsuite-bathroom-1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/jrhqe8vbm91vegd02bjh/scandic-continental-room-juniorsuite-bathroom-1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/93ag11udgjefz6qwa8st/scandic-continental-room-juniorsuite-bathroom-1.jpg" + } + }, + { + "metaData": { + "title": "Entrance, restaurant Market", + "altText": "Entrance, restaurant Market", + "altText_En": "Entrance, restaurant Market", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/9qgydydjtavu128kv2xt/scandic-continental-entrance-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/0c0sz9g0r9hhejw62ez9/scandic-continental-entrance-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ka3ku5zqp5uuqpfjddot/scandic-continental-entrance-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ksa5jgagck9sbj2uarz5/scandic-continental-entrance-themarket.jpg" + } + }, + { + "metaData": { + "title": "Dining room, the Market", + "altText": "Dining room, the Market", + "altText_En": "Dining room, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/bywlg4nwp11hmebfb1wg/scandic-continental-diningroom-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fdvp429hm1rfpg2le6jo/scandic-continental-diningroom-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wm24ma9ngk8gijotm7va/scandic-continental-diningroom-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/7xiz9p0f254yl9oef7bj/scandic-continental-diningroom-themarket.jpg" + } + }, + { + "metaData": { + "title": "Dining room, the Market", + "altText": "Dining room, the Market", + "altText_En": "Dining room, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/zd9rkkm383ctblciw0np/scandic-continental-diningroom-themarket-2.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/i9giiq3ijihm172b5bbw/scandic-continental-diningroom-themarket-2.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/9baixkat8wjdsjn1lo34/scandic-continental-diningroom-themarket-2.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/kwemrr8fohnnwo0uzd3s/scandic-continental-diningroom-themarket-2.jpg" + } + }, + { + "metaData": { + "title": "Restaurant, the Market, detail", + "altText": "Restaurant, the Market, detail", + "altText_En": "Restaurant, the Market, detail", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/uo7e32pi5cmf06as55ad/scandic-continental-themarket-detail.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/1qhpesv8d407j00b1y5x/scandic-continental-themarket-detail.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ldenlssse8595b4iavjj/scandic-continental-themarket-detail.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/yhs5jgy8mgw9xvzazjze/scandic-continental-themarket-detail.jpg" + } + }, + { + "metaData": { + "title": "Breakfast, the Market", + "altText": "Breakfast, the Market", + "altText_En": "Breakfast, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/vsj1725jfae5eqx9urua/scandic-continental-breakfast-themarket-15.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ajqxe5obci19sw4g08ub/scandic-continental-breakfast-themarket-15.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wi3xa47bwtb5s5kbaxq8/scandic-continental-breakfast-themarket-15.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/1igemz65ffu4nw7bg64l/scandic-continental-breakfast-themarket-15.jpg" + } + }, + { + "metaData": { + "title": "Breakfast, the Market", + "altText": "Breakfast, the Market", + "altText_En": "Breakfast, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/1clcvo8e86itpus3xy99/scandic-continental-breakfast-themarket-9.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/on5ckcmwxvs47l11natn/scandic-continental-breakfast-themarket-9.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/l5g6dmumzh9duseaa9zw/scandic-continental-breakfast-themarket-9.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/pl45n94xmu19bo0fokwf/scandic-continental-breakfast-themarket-9.jpg" + } + }, + { + "metaData": { + "title": "Restaurant Caldo", + "altText": "Restaurant Caldo", + "altText_En": "Restaurant Caldo", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/q5cj5qehiq03ey38ppen/scandic-continental-caldo.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/na08m2sippkfzd3yf9le/scandic-continental-caldo.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wxh6w02ujd5wqyr9bvg8/scandic-continental-caldo.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/bem9c6dxappvlwxg7s1g/scandic-continental-caldo.jpg" + } + }, + { + "metaData": { + "title": "Roof top bar", + "altText": "Roof top bar at Scandic Continental", + "altText_En": "Roof top bar at Scandic Continental", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/cxf1z9sgr9152ls1kdmg/Scandic_Continental_Capitol_The_View_13.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/pd43w13z5dx8l7kzhsyg/Scandic_Continental_Capitol_The_View_13.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/7ryqztmdqg0mam62oyf5/Scandic_Continental_Capitol_The_View_13.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/hwpsq70dbln0l052cjqa/Scandic_Continental_Capitol_The_View_13.jpg" + } + }, + { + "metaData": { + "title": "Roof top bar", + "altText": "Roof top bar at Scandic Continental", + "altText_En": "Roof top bar at Scandic Continental", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/hd1wk1wvtwsz4fuvf0rb/Scandic_Continental_Capitol_The_View_6.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ioi4ir6ijpy0f5dvu56n/Scandic_Continental_Capitol_The_View_6.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ydbhbjj3ulju2bxh7ebj/Scandic_Continental_Capitol_The_View_6.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/kom0ttrq6xazxxuodrr1/Scandic_Continental_Capitol_The_View_6.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Terrace", + "altText_En": "Terrace", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/m9yj4g160snutij4sivk/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fqqakeunmamm3uetf47w/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/6fp0avu8yjrh38zv1as8/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/j8dveyubybb4f7or9qay/Scandic_Continental_Capitol_Terrace.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Terrace", + "altText_En": "Terrace", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/dsm1ief68fx4b6j5673x/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/pm1lxtm8zf1ejd0qtko4/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wwgkqa2fr5z223ybdemr/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/aztwaanmwiwk5s0tc36w/Scandic_Continental_Capitol_Terrace.jpg" + } + }, + { + "metaData": { + "title": "Rooftop terrace", + "altText": "Rooftop terrace", + "altText_En": "Rooftop terrace", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/m4mhmwyynuf65ls1cybu/scandic-continental-rooftop-terrace-capital-4.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/phsnpd122oq7e6hszsc7/scandic-continental-rooftop-terrace-capital-4.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/ty9gb8ogkq5iqszke7pe/scandic-continental-rooftop-terrace-capital-4.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ug0co0ks7v25avze1vi3/scandic-continental-rooftop-terrace-capital-4.jpg" + } + }, + { + "metaData": { + "title": "View", + "altText": "View", + "altText_En": "View", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/gs1lqt13ptscg4audxgo/scandic-continental-theview-detail-1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/otr9i3f4q8084ggfwnm1/scandic-continental-theview-detail-1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/w8pkgf9igqibmlzp9egl/scandic-continental-theview-detail-1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/9yujqdk34x6juk6fstks/scandic-continental-theview-detail-1.jpg" + } + }, + { + "metaData": { + "title": "View", + "altText": "View", + "altText_En": "View", + "copyRight": "Elin Strömberg © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/autuf074xvc2xr3zmxyf/scandic-continental-theview-detail-2.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/hyf3fjkreqw2rlx4wm4e/scandic-continental-theview-detail-2.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/3wezw0ftkli48oloekdb/scandic-continental-theview-detail-2.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/ygxz2q45b4gcgscqdzaw/scandic-continental-theview-detail-2.jpg" + } + }, + { + "metaData": { + "title": "Work out equipment at the terrace", + "altText": "Work out equipment at the terrace", + "altText_En": "Work out equipment at the terrace", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/oe32sgg1pudiwfd9hjpa/Scandic_Continental_Terrace_Workout_1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/1g9d62gqkyvfky85kgag/Scandic_Continental_Terrace_Workout_1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/eb9n7j8s9scwg89ac4lk/Scandic_Continental_Terrace_Workout_1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/cyrtt6bmy11y1wti2d9h/Scandic_Continental_Terrace_Workout_1.jpg" + } + }, + { + "metaData": { + "title": "Meeting room", + "altText": "", + "altText_En": "", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/zbc3bhdzs59sqsvm9v29/scandic-continental-conference-9.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/m2nh4f57ys3bcxuq27hb/scandic-continental-conference-9.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wk39k84g3nrivntzuww2/scandic-continental-conference-9.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/yn1mzw6nbyl90dreiz6b/scandic-continental-conference-9.jpg" + } + }, + { + "metaData": { + "title": "Meeting room", + "altText": "", + "altText_En": "", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/mfq1ayjplo7keqxm88ng/scandic-continental-conference-3.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/wzjl4n8qk4imfgla7r73/scandic-continental-conference-3.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wf6lg2u4bz1f9ntkszuz/scandic-continental-conference-3.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/uh83sqrope55u6q4hbhl/scandic-continental-conference-3.jpg" + } + }, + { + "metaData": { + "title": "Meeting room ", + "altText": "Meeting room ", + "altText_En": "Meeting room ", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/z00ei7j974hx7c9vp8a6/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/v64n5ne3ayfo8v3q9x2s/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/mtx54l82hr3o3m7k0vqi/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/3xr7ovgdjxq5taz8nzma/Scandic_Continental_meetingroom_15-16-17-18-0357.jpg" + } + }, + { + "metaData": { + "title": "Breakout area ", + "altText": "Breakout area ", + "altText_En": "Breakout area ", + "copyRight": "Karl Gabor " + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/e84l4tyafvmx6epe0c3d/Scandic_Continental_Breakout_3-0432.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/60im9a4yre1kg5me9riz/Scandic_Continental_Breakout_3-0432.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/nsfh80un5l7qv9y9i1iw/Scandic_Continental_Breakout_3-0432.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/pxe0dozr1joaqig3cbf2/Scandic_Continental_Breakout_3-0432.jpg" + } + }, + { + "metaData": { + "title": "Breakout area ", + "altText": "Breakout area ", + "altText_En": "Breakout area ", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/fpyspgc9op7wb4ay3083/Scandic_Continental_breakout2-0391.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/2ueu1qe77bdqkj682xvq/Scandic_Continental_breakout2-0391.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/xmaaxzi1qrlwkm2gbl0z/Scandic_Continental_breakout2-0391.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/px12tarpgqq31cuz512z/Scandic_Continental_breakout2-0391.jpg" + } + }, + { + "metaData": { + "title": "Meeting room ", + "altText": "Meeting room ", + "altText_En": "Meeting room ", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ssrltn03vl7layrr5wgk/Scandic_Continental_Meetingroom_15_16_17_18.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/jozqcjuo1dpd9gyqjcyo/Scandic_Continental_Meetingroom_15_16_17_18.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/vdinjqa1j09av33z6wss/Scandic_Continental_Meetingroom_15_16_17_18.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/gq5lhbwrjvvo6otkgi9z/Scandic_Continental_Meetingroom_15_16_17_18.jpg" + } + }, + { + "metaData": { + "title": "Gym", + "altText": "Gym of Scandic Continental in Stockholm", + "altText_En": "Gym of Scandic Continental in Stockholm", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ic6077yovsoxdmj9545r/scandic_continental_gym.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/uhhqrnb8wzy9xlr42zn8/scandic_continental_gym.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/iqor5zd7owsll42z59vh/scandic_continental_gym.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/5v5o0bbopf9t7r95sp2o/scandic_continental_gym.jpg" + } + } + ] + }, + "conferencesAndMeetings": { + "headingText": "Meetings, conferences & events", + "pageUrl": "https://test3.scandichotels.com/hotels/sweden/stockholm/scandic-continental/meetings-conferences-events", + "heroImages": [ + { + "metaData": { + "title": "Roof top bar", + "altText": "Roof bar of Scandic Continental in Stockholm", + "altText_En": "Roof bar of Scandic Continental in Stockholm", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/086xasvs4lq29x4oqvbq/Scandic_Continental_Capitol_The_View_2.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/rgrcj8ed9mhecp6hpcuq/Scandic_Continental_Capitol_The_View_2.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/gklysd2pkx9gi0nx1mqu/Scandic_Continental_Capitol_The_View_2.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/sjkd7r6mw6xxhwzkkqvk/Scandic_Continental_Capitol_The_View_2.jpg" + } + } + ] + }, + "healthAndWellness": { + "headingText": "Gym and health", + "heroImages": [ + { + "metaData": { + "title": "Gym", + "altText": "Gym of Scandic Continental in Stockholm", + "altText_En": "Gym of Scandic Continental in Stockholm", + "copyRight": "Elin Sylwan © 2016" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ic6077yovsoxdmj9545r/scandic_continental_gym.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/uhhqrnb8wzy9xlr42zn8/scandic_continental_gym.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/iqor5zd7owsll42z59vh/scandic_continental_gym.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/5v5o0bbopf9t7r95sp2o/scandic_continental_gym.jpg" + } + }, + { + "metaData": { + "title": "Work out equipment at the terrace", + "altText": "Work out equipment at the terrace", + "altText_En": "Work out equipment at the terrace", + "copyRight": "Scandic" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/oe32sgg1pudiwfd9hjpa/Scandic_Continental_Terrace_Workout_1.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/1g9d62gqkyvfky85kgag/Scandic_Continental_Terrace_Workout_1.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/eb9n7j8s9scwg89ac4lk/Scandic_Continental_Terrace_Workout_1.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/cyrtt6bmy11y1wti2d9h/Scandic_Continental_Terrace_Workout_1.jpg" + } + } + ] + }, + "accessibilityElevatorPitchText": "Find the information you might need, before visiting us. You are always welcome to our hotel - completely without barriers. Regardless of impairment, sight, hearing, allergies or wheelchair, we have made sure that you enjoy your stay.", + "merchantInformationData": { + "webMerchantId": "1110009031", + "cards": { + "americanExpress": true, + "dankort": false, + "dinersClub": true, + "jcb": true, + "masterCard": true, + "visa": true, + "maestro": false, + "chinaUnionPay": true, + "discover": true + }, + "alternatePaymentOptions": { + "swish": true, + "vipps": false, + "mobilePay": true, + "applePay": true, + "alipayPlus": false, + "googlePay": true, + "klarna": false, + "payPal": false, + "weChatPay": false + } + }, + "restaurantImages": { + "headingText": "Bar and breakfast", + "heroImages": [ + { + "metaData": { + "title": "Dining room, the Market", + "altText": "Dining room, the Market", + "altText_En": "Dining room, the Market", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/bywlg4nwp11hmebfb1wg/scandic-continental-diningroom-themarket.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/fdvp429hm1rfpg2le6jo/scandic-continental-diningroom-themarket.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/wm24ma9ngk8gijotm7va/scandic-continental-diningroom-themarket.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/7xiz9p0f254yl9oef7bj/scandic-continental-diningroom-themarket.jpg" + } + }, + { + "metaData": { + "title": "Rooftop bar", + "altText": "Rooftop bar", + "altText_En": "Rooftop bar", + "copyRight": "Karl Gabor" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/ntwor0b4i90sp9sh5qbj/scandic-continental-rooftopbar-capital.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/ivxowx9eunbyz68qkstd/scandic-continental-rooftopbar-capital.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/6pi2262z6lo0un7x7iik/scandic-continental-rooftopbar-capital.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/6rlwgo7lq198xg7ifii6/scandic-continental-rooftopbar-capital.jpg" + } + }, + { + "metaData": { + "title": "Terrace", + "altText": "Bar of Scandic Continental in Stockholm", + "altText_En": "Bar of Scandic Continental in Stockholm", + "copyRight": "Björn Enström" + }, + "imageSizes": { + "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/69jl573jtmw8g4jjtugo/Scandic_Continental_Capitol_Terrace.jpg", + "small": "https://test3.scandichotels.com/imagevault/publishedmedia/td5tz4ld37kwws6rpnye/Scandic_Continental_Capitol_Terrace.jpg", + "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/mlrdylyxf3m9fr5r9l7x/Scandic_Continental_Capitol_Terrace.jpg", + "large": "https://test3.scandichotels.com/imagevault/publishedmedia/dywcmnaamp0altjge9qx/Scandic_Continental_Capitol_Terrace.jpg" + } + } + ] + } + }, + "relationships": { + "restaurants": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/restaurants?language=En" + } + }, + "nearbyHotels": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/nearbyHotels?language=En" + } + }, + "roomCategories": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/roomCategories?language=En" + } + }, + "meetingRooms": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/meetingRooms?language=En" + } + }, + "merchantInformation": { + "links": { + "related": "http://tstapi.scandichotels.com/hotel/v1/Hotels/811/merchantInformation?language=En" + } + } + }, + "id": "811", + "language": "En", + "type": "hotels" } } diff --git a/types/components/hotelReservation/selectRate/hotelSelectionHeader.ts b/types/components/hotelReservation/selectRate/hotelSelectionHeader.ts new file mode 100644 index 000000000..9804e0424 --- /dev/null +++ b/types/components/hotelReservation/selectRate/hotelSelectionHeader.ts @@ -0,0 +1,5 @@ +import { Hotel } from "@/types/hotel" + +export type HotelSelectionHeaderProps = { + hotel: Hotel +} From 105f721dc953e062bbb02b140f99ca324408c4e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matilda=20Landstr=C3=B6m?= Date: Wed, 2 Oct 2024 15:08:05 +0200 Subject: [PATCH 2/3] fix: "as of today" translation corrections --- i18n/dictionaries/da.json | 4 ++-- i18n/dictionaries/no.json | 4 ++-- i18n/dictionaries/sv.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 35b5e759a..50c95105a 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -263,7 +263,7 @@ "Zoo": "Zoo", "Zoom in": "Zoom ind", "Zoom out": "Zoom ud", - "as of today": "fra idag", + "as of today": "pr. dags dato", "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", "by": "inden", "characters": "tegn", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} Brugbare point udløber den {date}", "to": "til", "uppercase letter": "stort bogstav" -} \ No newline at end of file +} diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index ed600ecb5..39dda59c3 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -263,7 +263,7 @@ "Zoo": "Dyrehage", "Zoom in": "Zoom inn", "Zoom out": "Zoom ut", - "as of today": "per idag", + "as of today": "per i dag", "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "by": "innen", "characters": "tegn", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "to": "til", "uppercase letter": "stor bokstav" -} \ No newline at end of file +} diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 9f3f71511..e2205b7b6 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -263,7 +263,7 @@ "Zoo": "Djurpark", "Zoom in": "Zooma in", "Zoom out": "Zooma ut", - "as of today": "från och med idag", + "as of today": "per idag", "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", "by": "innan", "characters": "tecken", @@ -281,4 +281,4 @@ "spendable points expiring by": "{points} poäng förfaller {date}", "to": "till", "uppercase letter": "stor bokstav" -} \ No newline at end of file +} From 4103e3fb37461d3a276e1cecea917392282c2992 Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Fri, 4 Oct 2024 09:37:09 +0000 Subject: [PATCH 3/3] Merged in feat/SW-431-payment-flow (pull request #635) Feat/SW-431 payment flow * feat(SW-431): Update mock hotel data * feat(SW-431): Added route handler and trpc routes * feat(SW-431): List payment methods and handle booking status and redirection * feat(SW-431): Updated booking page to poll for booking status * feat(SW-431): Updated create booking contract * feat(SW-431): small fix * fix(SW-431): Added intl string and sorted dictionaries * fix(SW-431): Changes from PR * fix(SW-431): fixes from PR * fix(SW-431): add todo comments * fix(SW-431): update schema prop Approved-by: Simon.Emanuelsson --- .../hotelreservation/[section]/page.tsx | 21 +- .../booking-confirmation/loading.tsx | 5 + .../booking-confirmation/page.tsx | 63 +++++- .../payment-callback/[lang]/[status]/route.ts | 37 ++++ .../IntroSection/index.tsx | 7 +- .../BookingConfirmation/StaySection/index.tsx | 7 +- .../SummarySection/index.tsx | 7 +- .../SelectRate/Payment/index.tsx | 193 +++++++++++++----- .../SelectRate/Payment/payment.module.css | 18 ++ constants/booking.ts | 7 + constants/routes/hotelReservation.js | 20 ++ hooks/booking/useHandleBookingStatus.ts | 35 ++++ i18n/dictionaries/da.json | 51 ++--- i18n/dictionaries/de.json | 53 ++--- i18n/dictionaries/en.json | 61 +++--- i18n/dictionaries/fi.json | 49 ++--- i18n/dictionaries/no.json | 51 ++--- i18n/dictionaries/sv.json | 51 ++--- server/routers/booking/index.ts | 6 +- server/routers/booking/input.ts | 90 +++++--- server/routers/booking/mutation.ts | 17 +- server/routers/booking/output.ts | 4 +- server/routers/booking/query.ts | 85 ++++++++ server/routers/hotels/output.ts | 17 ++ server/trpc.ts | 37 ++-- .../hotelReservation/selectRate/section.ts | 6 + 26 files changed, 711 insertions(+), 287 deletions(-) create mode 100644 app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/loading.tsx create mode 100644 app/api/web/payment-callback/[lang]/[status]/route.ts create mode 100644 components/HotelReservation/SelectRate/Payment/payment.module.css create mode 100644 constants/booking.ts create mode 100644 hooks/booking/useHandleBookingStatus.ts create mode 100644 server/routers/booking/query.ts diff --git a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx index a0373645b..b3ef81538 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/[section]/page.tsx @@ -1,6 +1,6 @@ +import { notFound } from "next/navigation" + import { serverClient } from "@/lib/trpc/server" -import { getHotelDataSchema } from "@/server/routers/hotels/output" -import tempHotelData from "@/server/routers/hotels/tempHotelData.json" import HotelSelectionHeader from "@/components/HotelReservation/HotelSelectionHeader" import BedSelection from "@/components/HotelReservation/SelectRate/BedSelection" @@ -80,12 +80,19 @@ export default async function SectionsPage({ }: PageArgs) { setLang(params.lang) - // TODO: Use real endpoint. - const hotel = getHotelDataSchema.parse(tempHotelData) + const hotel = await serverClient().hotel.hotelData.get({ + hotelId: "811", + language: params.lang, + }) + + if (!hotel) { + // TODO: handle case with hotel missing + return notFound() + } const rooms = await serverClient().hotel.rates.get({ // TODO: pass the correct hotel ID and all other parameters that should be included in the search - hotelId: "1", + hotelId: hotel.data.id, }) const intl = await getIntl() @@ -170,7 +177,9 @@ export default async function SectionsPage({ header={intl.formatMessage({ id: "Payment info" })} path={`payment?${currentSearchParams}`} > - {params.section === "payment" && } + {params.section === "payment" && ( + + )}
diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/loading.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/loading.tsx new file mode 100644 index 000000000..c739b6635 --- /dev/null +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/loading.tsx @@ -0,0 +1,5 @@ +import LoadingSpinner from "@/components/LoadingSpinner" + +export default function Loading() { + return +} diff --git a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx index 46f941bbc..0a22c5bc0 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/booking-confirmation/page.tsx @@ -1,20 +1,67 @@ +"use client" + +import { useMemo } from "react" + +import { + BOOKING_CONFIRMATION_NUMBER, + BookingStatusEnum, +} from "@/constants/booking" + import IntroSection from "@/components/HotelReservation/BookingConfirmation/IntroSection" import StaySection from "@/components/HotelReservation/BookingConfirmation/StaySection" import SummarySection from "@/components/HotelReservation/BookingConfirmation/SummarySection" import { tempConfirmationData } from "@/components/HotelReservation/BookingConfirmation/tempConfirmationData" +import LoadingSpinner from "@/components/LoadingSpinner" +import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus" import styles from "./page.module.css" +const maxRetries = 10 +const retryInterval = 2000 + export default function BookingConfirmationPage() { const { email, hotel, stay, summary } = tempConfirmationData - return ( -
-
- - - -
-
+ const confirmationNumber = useMemo(() => { + if (typeof window === "undefined") return "" + + const storedConfirmationNumber = sessionStorage.getItem( + BOOKING_CONFIRMATION_NUMBER + ) + // TODO: cleanup stored values + // sessionStorage.removeItem(BOOKING_CONFIRMATION_NUMBER) + return storedConfirmationNumber + }, []) + + const bookingStatus = useHandleBookingStatus( + confirmationNumber, + BookingStatusEnum.BookingCompleted, + maxRetries, + retryInterval ) + + if ( + confirmationNumber === null || + bookingStatus.isError || + (bookingStatus.isFetched && !bookingStatus.data) + ) { + // TODO: handle error + throw new Error("Error fetching booking status") + } + + if ( + bookingStatus.data?.reservationStatus === BookingStatusEnum.BookingCompleted + ) { + return ( +
+
+ + + +
+
+ ) + } + + return } diff --git a/app/api/web/payment-callback/[lang]/[status]/route.ts b/app/api/web/payment-callback/[lang]/[status]/route.ts new file mode 100644 index 000000000..0b8133c43 --- /dev/null +++ b/app/api/web/payment-callback/[lang]/[status]/route.ts @@ -0,0 +1,37 @@ +import { NextRequest, NextResponse } from "next/server" +import { env } from "process" + +import { Lang } from "@/constants/languages" +import { + bookingConfirmation, + payment, +} from "@/constants/routes/hotelReservation" + +export async function GET( + request: NextRequest, + { params }: { params: { lang: string; status: string } } +): Promise { + console.log(`[payment-callback] callback started`) + const lang = params.lang as Lang + const status = params.status + const returnUrl = new URL(`${env.PUBLIC_URL}/${payment[lang]}`) + + if (status === "success") { + const confirmationUrl = new URL( + `${env.PUBLIC_URL}/${bookingConfirmation[lang]}` + ) + console.log(`[payment-callback] redirecting to: ${confirmationUrl}`) + return NextResponse.redirect(confirmationUrl) + } + + if (status === "cancel") { + returnUrl.searchParams.set("cancel", "true") + } + + if (status === "error") { + returnUrl.searchParams.set("error", "true") + } + + console.log(`[payment-callback] redirecting to: ${returnUrl}`) + return NextResponse.redirect(returnUrl) +} diff --git a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx index 9482f51fe..448dc82e1 100644 --- a/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/IntroSection/index.tsx @@ -1,16 +1,17 @@ +import { useIntl } from "react-intl" + import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import Body from "@/components/TempDesignSystem/Text/Body" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Title from "@/components/TempDesignSystem/Text/Title" -import { getIntl } from "@/i18n" import styles from "./introSection.module.css" import { IntroSectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" -export default async function IntroSection({ email }: IntroSectionProps) { - const intl = await getIntl() +export default function IntroSection({ email }: IntroSectionProps) { + const intl = useIntl() return (
diff --git a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx index 99ecae8ca..7907ac191 100644 --- a/components/HotelReservation/BookingConfirmation/StaySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/StaySection/index.tsx @@ -1,16 +1,17 @@ +import { useIntl } from "react-intl" + import { ArrowRightIcon, ScandicLogoIcon } from "@/components/Icons" import Image from "@/components/Image" import Body from "@/components/TempDesignSystem/Text/Body" import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" -import { getIntl } from "@/i18n" import styles from "./staySection.module.css" import { StaySectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" -export default async function StaySection({ hotel, stay }: StaySectionProps) { - const intl = await getIntl() +export default function StaySection({ hotel, stay }: StaySectionProps) { + const intl = useIntl() const nightsText = stay.nights > 1 diff --git a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx index 509af9c52..16eb84330 100644 --- a/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx +++ b/components/HotelReservation/BookingConfirmation/SummarySection/index.tsx @@ -1,13 +1,14 @@ +import { useIntl } from "react-intl" + import Caption from "@/components/TempDesignSystem/Text/Caption" import Title from "@/components/TempDesignSystem/Text/Title" -import { getIntl } from "@/i18n" import styles from "./summarySection.module.css" import { SummarySectionProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" -export default async function SummarySection({ summary }: SummarySectionProps) { - const intl = await getIntl() +export default function SummarySection({ summary }: SummarySectionProps) { + const intl = useIntl() const roomType = `${intl.formatMessage({ id: "Type of room" })}: ${summary.roomType}` const bedType = `${intl.formatMessage({ id: "Type of bed" })}: ${summary.bedType}` const breakfast = `${intl.formatMessage({ id: "Breakfast" })}: ${summary.breakfast}` diff --git a/components/HotelReservation/SelectRate/Payment/index.tsx b/components/HotelReservation/SelectRate/Payment/index.tsx index ed8068076..3ddf73c01 100644 --- a/components/HotelReservation/SelectRate/Payment/index.tsx +++ b/components/HotelReservation/SelectRate/Payment/index.tsx @@ -1,62 +1,161 @@ "use client" +import { useRouter } from "next/navigation" +import { useEffect, useState } from "react" +import { useIntl } from "react-intl" + +import { + BOOKING_CONFIRMATION_NUMBER, + BookingStatusEnum, +} from "@/constants/booking" import { trpc } from "@/lib/trpc/client" +import LoadingSpinner from "@/components/LoadingSpinner" import Button from "@/components/TempDesignSystem/Button" +import { toast } from "@/components/TempDesignSystem/Toasts" +import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus" +import useLang from "@/hooks/useLang" + +import styles from "./payment.module.css" + +import { PaymentProps } from "@/types/components/hotelReservation/selectRate/section" + +const maxRetries = 40 +const retryInterval = 2000 + +export default function Payment({ hotel }: PaymentProps) { + const router = useRouter() + const lang = useLang() + const intl = useIntl() + const [confirmationNumber, setConfirmationNumber] = useState("") + const [selectedPaymentMethod, setSelectedPaymentMethod] = useState("") -export default function Payment() { const initiateBooking = trpc.booking.booking.create.useMutation({ onSuccess: (result) => { - // TODO: Handle success, poll for payment link and redirect the user to the payment - console.log("Res", result) + if (result?.confirmationNumber) { + // Planet doesn't support query params so we have to store values in session storage + sessionStorage.setItem( + BOOKING_CONFIRMATION_NUMBER, + result.confirmationNumber + ) + + setConfirmationNumber(result.confirmationNumber) + } else { + // TODO: add proper error message + toast.error("Failed to create booking") + } }, - onError: () => { - // TODO: Handle error - console.log("Error") + onError: (error) => { + console.error("Error", error) + // TODO: add proper error message + toast.error("Failed to create booking") }, }) - return ( - + packages: { + breakfast: true, + allergyFriendly: true, + petFriendly: true, + accessibility: true, + }, + smsConfirmationRequested: true, + }, + ], + payment: { + paymentMethod: selectedPaymentMethod, + cardHolder: { + email: "test.user@scandichotels.com", + name: "Test User", + phoneCountryCode: "", + phoneSubscriber: "", + }, + success: `api/web/payment-callback/${lang}/success`, + error: `api/web/payment-callback/${lang}/error`, + cancel: `api/web/payment-callback/${lang}/cancel`, + }, + }) + } + + if ( + initiateBooking.isPending || + (confirmationNumber && !bookingStatus.data?.paymentUrl) + ) { + return + } + + return ( +
+
+
+ + {hotel.merchantInformationData.alternatePaymentOptions.map( + (paymentOption) => ( + + ) + )} +
+
+ +
) } diff --git a/components/HotelReservation/SelectRate/Payment/payment.module.css b/components/HotelReservation/SelectRate/Payment/payment.module.css new file mode 100644 index 000000000..9200ce8f3 --- /dev/null +++ b/components/HotelReservation/SelectRate/Payment/payment.module.css @@ -0,0 +1,18 @@ +.paymentItemContainer { + max-width: 480px; + display: flex; + flex-direction: column; + gap: var(--Spacing-x1); + padding-bottom: var(--Spacing-x4); +} + +.paymentItem { + background-color: var(--Base-Background-Normal); + padding: var(--Spacing-x3); + border: 1px solid var(--Base-Border-Normal); + border-radius: var(--Corner-radius-Medium); + display: flex; + align-items: center; + gap: var(--Spacing-x2); + cursor: pointer; +} diff --git a/constants/booking.ts b/constants/booking.ts new file mode 100644 index 000000000..664240a79 --- /dev/null +++ b/constants/booking.ts @@ -0,0 +1,7 @@ +export enum BookingStatusEnum { + CreatedInOhip = "CreatedInOhip", + PaymentRegistered = "PaymentRegistered", + BookingCompleted = "BookingCompleted", +} + +export const BOOKING_CONFIRMATION_NUMBER = "bookingConfirmationNumber" diff --git a/constants/routes/hotelReservation.js b/constants/routes/hotelReservation.js index 72d298965..5fdb1b6b1 100644 --- a/constants/routes/hotelReservation.js +++ b/constants/routes/hotelReservation.js @@ -28,4 +28,24 @@ export const selectHotelMap = { de: `${selectHotel.de}/map`, } +/** @type {import('@/types/routes').LangRoute} */ +export const payment = { + en: `${hotelReservation.en}/payment`, + sv: `${hotelReservation.sv}/betalning`, + no: `${hotelReservation.no}/betaling`, + fi: `${hotelReservation.fi}/maksu`, + da: `${hotelReservation.da}/payment`, + de: `${hotelReservation.de}/bezahlung`, +} + +/** @type {import('@/types/routes').LangRoute} */ +export const bookingConfirmation = { + en: `${hotelReservation.en}/booking-confirmation`, + sv: `${hotelReservation.sv}/bokningsbekraftelse`, + no: `${hotelReservation.no}/booking-confirmation`, + fi: `${hotelReservation.fi}/varausvahvistus`, + da: `${hotelReservation.da}/booking-confirmation`, + de: `${hotelReservation.de}/buchungsbesttigung`, +} + export const bookingFlow = [...Object.values(hotelReservation)] diff --git a/hooks/booking/useHandleBookingStatus.ts b/hooks/booking/useHandleBookingStatus.ts new file mode 100644 index 000000000..5373057a8 --- /dev/null +++ b/hooks/booking/useHandleBookingStatus.ts @@ -0,0 +1,35 @@ +"use client" + +import { BookingStatusEnum } from "@/constants/booking" +import { trpc } from "@/lib/trpc/client" + +export function useHandleBookingStatus( + confirmationNumber: string | null, + expectedStatus: BookingStatusEnum, + maxRetries: number, + retryInterval: number +) { + const query = trpc.booking.status.useQuery( + { confirmationNumber: confirmationNumber ?? "" }, + { + enabled: !!confirmationNumber, + refetchInterval: (query) => { + if (query.state.error || query.state.dataUpdateCount >= maxRetries) { + return false + } + + if (query.state.data?.reservationStatus === expectedStatus) { + return false + } + + return retryInterval + }, + refetchIntervalInBackground: true, + refetchOnWindowFocus: false, + refetchOnMount: false, + retry: false, + } + ) + + return query +} diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index 50c95105a..01abf69d0 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -14,6 +14,7 @@ "Any changes you've made will be lost.": "Alle ændringer, du har foretaget, går tabt.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på, at du vil fjerne kortet, der slutter me {lastFourDigits} fra din medlemsprofil?", "Arrival date": "Ankomstdato", + "as of today": "fra idag", "As our": "Som vores {level}", "As our Close Friend": "Som vores nære ven", "At latest": "Senest", @@ -23,14 +24,16 @@ "Bed type": "Seng type", "Book": "Book", "Book reward night": "Book bonusnat", - "Code / Voucher": "Bookingkoder / voucher", "Booking number": "Bookingnummer", + "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", "Breakfast": "Morgenmad", "Breakfast excluded": "Morgenmad ikke inkluderet", "Breakfast included": "Morgenmad inkluderet", "Bus terminal": "Busstation", "Business": "Forretning", + "by": "inden", "Cancel": "Afbestille", + "characters": "tegn", "Check in": "Check ind", "Check out": "Check ud", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tjek de kreditkort, der er gemt på din profil. Betal med et gemt kort, når du er logget ind for en mere jævn weboplevelse.", @@ -45,8 +48,10 @@ "Close menu": "Luk menu", "Close my pages menu": "Luk mine sider menu", "Close the map": "Luk kortet", + "Code / Voucher": "Bookingkoder / voucher", "Coming up": "Er lige om hjørnet", "Compare all levels": "Sammenlign alle niveauer", + "Complete booking & go to payment": "Udfyld booking & gå til betaling", "Contact us": "Kontakt os", "Continue": "Blive ved", "Copyright all rights reserved": "Scandic AB Alle rettigheder forbeholdes", @@ -72,9 +77,9 @@ "Explore all levels and benefits": "Udforsk alle niveauer og fordele", "Explore nearby": "Udforsk i nærheden", "Extras to your booking": "Tillæg til din booking", - "FAQ": "Ofte stillede spørgsmål", "Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.", "Fair": "Messe", + "FAQ": "Ofte stillede spørgsmål", "Find booking": "Find booking", "Find hotels": "Find hotel", "Flexibility": "Fleksibilitet", @@ -85,17 +90,22 @@ "Get inspired": "Bliv inspireret", "Go back to edit": "Gå tilbage til redigering", "Go back to overview": "Gå tilbage til oversigten", + "Guests & Rooms": "Gæster & værelser", "Hi": "Hei", "Highest level": "Højeste niveau", "Hospital": "Hospital", "Hotel": "Hotel", "Hotel facilities": "Hotel faciliteter", "Hotel surroundings": "Hotel omgivelser", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "personer", + "hotelPages.rooms.roomCard.seeRoomDetails": "Se værelsesdetaljer", "Hotels": "Hoteller", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det virker", "Image gallery": "Billedgalleri", "Join Scandic Friends": "Tilmeld dig Scandic Friends", + "km to city center": "km til byens centrum", "Language": "Sprog", "Latest searches": "Seneste søgninger", "Level": "Niveau", @@ -122,9 +132,9 @@ "Member price": "Medlemspris", "Member price from": "Medlemspris fra", "Members": "Medlemmer", + "Membership cards": "Medlemskort", "Membership ID": "Medlems-id", "Membership ID copied to clipboard": "Medlems-ID kopieret til udklipsholder", - "Membership cards": "Medlemskort", "Menu": "Menu", "Modify": "Ændre", "Month": "Måned", @@ -139,6 +149,9 @@ "Nearby companies": "Nærliggende virksomheder", "New password": "Nyt kodeord", "Next": "Næste", + "next level:": "Næste niveau:", + "night": "nat", + "nights": "nætter", "Nights needed to level up": "Nætter nødvendige for at komme i niveau", "No content published": "Intet indhold offentliggjort", "No matching location found": "Der blev ikke fundet nogen matchende placering", @@ -149,11 +162,13 @@ "Non-refundable": "Ikke-refunderbart", "Not found": "Ikke fundet", "Nr night, nr adult": "{nights, number} nat, {adults, number} voksen", + "number": "nummer", "On your journey": "På din rejse", "Open": "Åben", "Open language menu": "Åbn sprogmenuen", "Open menu": "Åbn menuen", "Open my pages menu": "Åbn mine sider menuen", + "or": "eller", "Overview": "Oversigt", "Parking": "Parkering", "Parking / Garage": "Parkering / Garage", @@ -165,6 +180,7 @@ "Phone is required": "Telefonnummer er påkrævet", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", + "points": "Point", "Points": "Point", "Points being calculated": "Point udregnes", "Points earned prior to May 1, 2021": "Point optjent inden 1. maj 2021", @@ -183,7 +199,6 @@ "Room & Terms": "Værelse & Vilkår", "Room facilities": "Værelsesfaciliteter", "Rooms": "Værelser", - "Guests & Rooms": "Gæster & værelser", "Save": "Gemme", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", @@ -208,25 +223,29 @@ "Something went wrong and we couldn't add your card. Please try again later.": "Noget gik galt, og vi kunne ikke tilføje dit kort. Prøv venligst igen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noget gik galt, og vi kunne ikke fjerne dit kort. Prøv venligst igen senere.", "Something went wrong!": "Noget gik galt!", + "special character": "speciel karakter", + "spendable points expiring by": "{points} Brugbare point udløber den {date}", "Sports": "Sport", "Standard price": "Standardpris", "Street": "Gade", "Successfully updated profile!": "Profilen er opdateret med succes!", "Summary": "Opsummering", - "TUI Points": "TUI Points", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortæl os, hvilke oplysninger og opdateringer du gerne vil modtage, og hvordan, ved at klikke på linket nedenfor.", "Thank you": "Tak", "Theatre": "Teater", "There are no transactions to display": "Der er ingen transaktioner at vise", "Things nearby HOTEL_NAME": "Ting i nærheden af {hotelName}", + "to": "til", "Total Points": "Samlet antal point", "Tourist": "Turist", "Transaction date": "Overførselsdato", "Transactions": "Transaktioner", "Transportations": "Transport", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", + "TUI Points": "TUI Points", "Type of bed": "Sengtype", "Type of room": "Værelsestype", + "uppercase letter": "stort bogstav", "Use bonus cheque": "Brug Bonus Cheque", "User information": "Brugeroplysninger", "View as list": "Vis som liste", @@ -252,9 +271,9 @@ "You canceled adding a new credit card.": "Du har annulleret tilføjelsen af et nyt kreditkort.", "You have no previous stays.": "Du har ingen tidligere ophold.", "You have no upcoming stays.": "Du har ingen kommende ophold.", - "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your card was successfully removed!": "Dit kort blev fjernet!", "Your card was successfully saved!": "Dit kort blev gemt!", + "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your current level": "Dit nuværende niveau", "Your details": "Dine oplysninger", "Your level": "Dit niveau", @@ -262,23 +281,5 @@ "Zip code": "Postnummer", "Zoo": "Zoo", "Zoom in": "Zoom ind", - "Zoom out": "Zoom ud", - "as of today": "pr. dags dato", - "booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}", - "by": "inden", - "characters": "tegn", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personer", - "hotelPages.rooms.roomCard.seeRoomDetails": "Se værelsesdetaljer", - "km to city center": "km til byens centrum", - "next level:": "Næste niveau:", - "night": "nat", - "nights": "nætter", - "number": "nummer", - "or": "eller", - "points": "Point", - "special character": "speciel karakter", - "spendable points expiring by": "{points} Brugbare point udløber den {date}", - "to": "til", - "uppercase letter": "stort bogstav" + "Zoom out": "Zoom ud" } diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 916804097..ab6a0d31d 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -14,6 +14,7 @@ "Any changes you've made will be lost.": "Alle Änderungen, die Sie vorgenommen haben, gehen verloren.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Möchten Sie die Karte mit der Endung {lastFourDigits} wirklich aus Ihrem Mitgliedsprofil entfernen?", "Arrival date": "Ankunftsdatum", + "as of today": "Stand heute", "As our": "Als unser {level}", "As our Close Friend": "Als unser enger Freund", "At latest": "Spätestens", @@ -23,14 +24,16 @@ "Bed type": "Bettentyp", "Book": "Buchen", "Book reward night": "Bonusnacht buchen", - "Code / Voucher": "Buchungscodes / Gutscheine", "Booking number": "Buchungsnummer", + "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", "Breakfast": "Frühstück", "Breakfast excluded": "Frühstück nicht inbegriffen", "Breakfast included": "Frühstück inbegriffen", "Bus terminal": "Busbahnhof", "Business": "Geschäft", + "by": "bis", "Cancel": "Stornieren", + "characters": "figuren", "Check in": "Einchecken", "Check out": "Auschecken", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sehen Sie sich die in Ihrem Profil gespeicherten Kreditkarten an. Bezahlen Sie mit einer gespeicherten Karte, wenn Sie angemeldet sind, für ein reibungsloseres Web-Erlebnis.", @@ -45,8 +48,10 @@ "Close menu": "Menü schließen", "Close my pages menu": "Meine Seiten Menü schließen", "Close the map": "Karte schließen", + "Code / Voucher": "Buchungscodes / Gutscheine", "Coming up": "Demnächst", "Compare all levels": "Vergleichen Sie alle Levels", + "Complete booking & go to payment": "Buchung abschließen & zur Bezahlung gehen", "Contact us": "Kontaktieren Sie uns", "Continue": "Weitermachen", "Copyright all rights reserved": "Scandic AB Alle Rechte vorbehalten", @@ -72,9 +77,9 @@ "Explore all levels and benefits": "Entdecken Sie alle Levels und Vorteile", "Explore nearby": "Erkunden Sie die Umgebung", "Extras to your booking": "Extras zu Ihrer Buchung", - "FAQ": "Häufig gestellte Fragen", "Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.", "Fair": "Messe", + "FAQ": "Häufig gestellte Fragen", "Find booking": "Buchung finden", "Find hotels": "Hotels finden", "Flexibility": "Flexibilität", @@ -85,17 +90,22 @@ "Get inspired": "Lassen Sie sich inspieren", "Go back to edit": "Zurück zum Bearbeiten", "Go back to overview": "Zurück zur Übersicht", + "Guests & Rooms": "Gäste & Zimmer", "Hi": "Hallo", "Highest level": "Höchstes Level", "Hospital": "Krankenhaus", "Hotel": "Hotel", "Hotel facilities": "Hotel-Infos", "Hotel surroundings": "Umgebung des Hotels", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "personen", + "hotelPages.rooms.roomCard.seeRoomDetails": "Zimmerdetails ansehen", "Hotels": "Hotels", "How do you want to sleep?": "Wie möchtest du schlafen?", "How it works": "Wie es funktioniert", "Image gallery": "Bildergalerie", "Join Scandic Friends": "Treten Sie Scandic Friends bei", + "km to city center": "km bis zum Stadtzentrum", "Language": "Sprache", "Latest searches": "Letzte Suchanfragen", "Level": "Level", @@ -122,9 +132,9 @@ "Member price": "Mitgliederpreis", "Member price from": "Mitgliederpreis ab", "Members": "Mitglieder", + "Membership cards": "Mitgliedskarten", "Membership ID": "Mitglieds-ID", "Membership ID copied to clipboard": "Mitglieds-ID in die Zwischenablage kopiert", - "Membership cards": "Mitgliedskarten", "Menu": "Menu", "Modify": "Ändern", "Month": "Monat", @@ -139,6 +149,9 @@ "Nearby companies": "Nahe gelegene Unternehmen", "New password": "Neues Kennwort", "Next": "Nächste", + "next level:": "Nächstes Level:", + "night": "nacht", + "nights": "Nächte", "Nights needed to level up": "Nächte, die zum Levelaufstieg benötigt werden", "No content published": "Kein Inhalt veröffentlicht", "No matching location found": "Kein passender Standort gefunden", @@ -149,11 +162,13 @@ "Non-refundable": "Nicht erstattungsfähig", "Not found": "Nicht gefunden", "Nr night, nr adult": "{nights, number} Nacht, {adults, number} Erwachsener", + "number": "nummer", "On your journey": "Auf deiner Reise", "Open": "Offen", "Open language menu": "Sprachmenü öffnen", "Open menu": "Menü öffnen", "Open my pages menu": "Meine Seiten Menü öffnen", + "or": "oder", "Overview": "Übersicht", "Parking": "Parken", "Parking / Garage": "Parken / Garage", @@ -164,6 +179,7 @@ "Phone is required": "Telefon ist erforderlich", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", + "points": "Punkte", "Points": "Punkte", "Points being calculated": "Punkte werden berechnet", "Points earned prior to May 1, 2021": "Zusammengeführte Punkte vor dem 1. Mai 2021", @@ -182,7 +198,6 @@ "Room & Terms": "Zimmer & Bedingungen", "Room facilities": "Zimmerausstattung", "Rooms": "Räume", - "Guests & Rooms": "Gäste & Zimmer", "Save": "Speichern", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", @@ -207,25 +222,29 @@ "Something went wrong and we couldn't add your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht hinzufügen. Bitte versuchen Sie es später erneut.", "Something went wrong and we couldn't remove your card. Please try again later.": "Ein Fehler ist aufgetreten und wir konnten Ihre Karte nicht entfernen. Bitte versuchen Sie es später noch einmal.", "Something went wrong!": "Etwas ist schief gelaufen!", + "special character": "sonderzeichen", + "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", "Sports": "Sport", "Standard price": "Standardpreis", "Street": "Straße", "Successfully updated profile!": "Profil erfolgreich aktualisiert!", "Summary": "Zusammenfassung", - "TUI Points": "TUI Points", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Teilen Sie uns mit, welche Informationen und Updates Sie wie erhalten möchten, indem Sie auf den unten stehenden Link klicken.", "Thank you": "Danke", "Theatre": "Theater", "There are no transactions to display": "Es sind keine Transaktionen zum Anzeigen vorhanden", "Things nearby HOTEL_NAME": "Dinge in der Nähe von {hotelName}", + "to": "zu", "Total Points": "Gesamtpunktzahl", "Tourist": "Tourist", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktionen", "Transportations": "Transportmittel", "Tripadvisor reviews": "{rating} ({count} Bewertungen auf Tripadvisor)", + "TUI Points": "TUI Points", "Type of bed": "Bettentyp", "Type of room": "Zimmerart", + "uppercase letter": "großbuchstabe", "Use bonus cheque": "Bonusscheck nutzen", "User information": "Nutzerinformation", "View as list": "Als Liste anzeigen", @@ -251,9 +270,9 @@ "You canceled adding a new credit card.": "Sie haben das Hinzufügen einer neuen Kreditkarte abgebrochen.", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", "You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.", - "Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!", "Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!", "Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!", + "Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!", "Your current level": "Ihr aktuelles Level", "Your details": "Ihre Angaben", "Your level": "Dein level", @@ -261,23 +280,5 @@ "Zip code": "PLZ", "Zoo": "Zoo", "Zoom in": "Vergrößern", - "Zoom out": "Verkleinern", - "as of today": "Stand heute", - "booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}", - "by": "bis", - "characters": "figuren", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personen", - "hotelPages.rooms.roomCard.seeRoomDetails": "Zimmerdetails ansehen", - "km to city center": "km bis zum Stadtzentrum", - "next level:": "Nächstes Level:", - "night": "nacht", - "nights": "Nächte", - "number": "nummer", - "or": "oder", - "points": "Punkte", - "special character": "sonderzeichen", - "spendable points expiring by": "{points} Einlösbare punkte verfallen bis zum {date}", - "to": "zu", - "uppercase letter": "großbuchstabe" -} \ No newline at end of file + "Zoom out": "Verkleinern" +} diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index c663a0f80..eb7f33489 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -14,6 +14,7 @@ "Any changes you've made will be lost.": "Any changes you've made will be lost.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?", "Arrival date": "Arrival date", + "as of today": "as of today", "As our": "As our {level}", "As our Close Friend": "As our Close Friend", "At latest": "At latest", @@ -23,14 +24,18 @@ "Bed type": "Bed type", "Book": "Book", "Book reward night": "Book reward night", - "Code / Voucher": "Code / Voucher", "Booking number": "Booking number", + "booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}", + "booking.nights": "{totalNights, plural, one {# night} other {# nights}}", + "booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}", "Breakfast": "Breakfast", "Breakfast excluded": "Breakfast excluded", "Breakfast included": "Breakfast included", "Bus terminal": "Bus terminal", "Business": "Business", + "by": "by", "Cancel": "Cancel", + "characters": "characters", "Check in": "Check in", "Check out": "Check out", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.", @@ -45,8 +50,10 @@ "Close menu": "Close menu", "Close my pages menu": "Close my pages menu", "Close the map": "Close the map", + "Code / Voucher": "Code / Voucher", "Coming up": "Coming up", "Compare all levels": "Compare all levels", + "Complete booking & go to payment": "Complete booking & go to payment", "Contact us": "Contact us", "Continue": "Continue", "Copyright all rights reserved": "Scandic AB All rights reserved", @@ -59,8 +66,8 @@ "Date of Birth": "Date of Birth", "Day": "Day", "Description": "Description", - "Destinations & hotels": "Destinations & hotels", "Destination": "Destination", + "Destinations & hotels": "Destinations & hotels", "Discard changes": "Discard changes", "Discard unsaved changes?": "Discard unsaved changes?", "Distance to city centre": "{number}km to city centre", @@ -73,9 +80,9 @@ "Explore all levels and benefits": "Explore all levels and benefits", "Explore nearby": "Explore nearby", "Extras to your booking": "Extras to your booking", - "FAQ": "FAQ", "Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.", "Fair": "Fair", + "FAQ": "FAQ", "Find booking": "Find booking", "Find hotels": "Find hotels", "Flexibility": "Flexibility", @@ -86,17 +93,22 @@ "Get inspired": "Get inspired", "Go back to edit": "Go back to edit", "Go back to overview": "Go back to overview", + "Guests & Rooms": "Guests & Rooms", "Hi": "Hi", "Highest level": "Highest level", "Hospital": "Hospital", "Hotel": "Hotel", "Hotel facilities": "Hotel facilities", "Hotel surroundings": "Hotel surroundings", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "persons", + "hotelPages.rooms.roomCard.seeRoomDetails": "See room details", "Hotels": "Hotels", "How do you want to sleep?": "How do you want to sleep?", "How it works": "How it works", "Image gallery": "Image gallery", "Join Scandic Friends": "Join Scandic Friends", + "km to city center": "km to city center", "Language": "Language", "Latest searches": "Latest searches", "Level": "Level", @@ -123,9 +135,9 @@ "Member price": "Member price", "Member price from": "Member price from", "Members": "Members", + "Membership cards": "Membership cards", "Membership ID": "Membership ID", "Membership ID copied to clipboard": "Membership ID copied to clipboard", - "Membership cards": "Membership cards", "Menu": "Menu", "Modify": "Modify", "Month": "Month", @@ -140,6 +152,9 @@ "Nearby companies": "Nearby companies", "New password": "New password", "Next": "Next", + "next level:": "next level:", + "night": "night", + "nights": "nights", "Nights needed to level up": "Nights needed to level up", "No content published": "No content published", "No matching location found": "No matching location found", @@ -150,11 +165,13 @@ "Non-refundable": "Non-refundable", "Not found": "Not found", "Nr night, nr adult": "{nights, number} night, {adults, number} adult", + "number": "number", "On your journey": "On your journey", "Open": "Open", "Open language menu": "Open language menu", "Open menu": "Open menu", "Open my pages menu": "Open my pages menu", + "or": "or", "Overview": "Overview", "Parking": "Parking", "Parking / Garage": "Parking / Garage", @@ -166,6 +183,7 @@ "Phone is required": "Phone is required", "Phone number": "Phone number", "Please enter a valid phone number": "Please enter a valid phone number", + "points": "Points", "Points": "Points", "Points being calculated": "Points being calculated", "Points earned prior to May 1, 2021": "Points earned prior to May 1, 2021", @@ -184,12 +202,11 @@ "Room & Terms": "Room & Terms", "Room facilities": "Room facilities", "Rooms": "Rooms", - "Guests & Rooms": "Guests & Rooms", "Save": "Save", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", - "See all photos": "See all photos", "Search": "Search", + "See all photos": "See all photos", "See hotel details": "See hotel details", "See room details": "See room details", "See rooms": "See rooms", @@ -210,25 +227,29 @@ "Something went wrong and we couldn't add your card. Please try again later.": "Something went wrong and we couldn't add your card. Please try again later.", "Something went wrong and we couldn't remove your card. Please try again later.": "Something went wrong and we couldn't remove your card. Please try again later.", "Something went wrong!": "Something went wrong!", + "special character": "special character", + "spendable points expiring by": "{points} spendable points expiring by {date}", "Sports": "Sports", "Standard price": "Standard price", "Street": "Street", "Successfully updated profile!": "Successfully updated profile!", "Summary": "Summary", - "TUI Points": "TUI Points", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Tell us what information and updates you'd like to receive, and how, by clicking the link below.", "Thank you": "Thank you", "Theatre": "Theatre", "There are no transactions to display": "There are no transactions to display", "Things nearby HOTEL_NAME": "Things nearby {hotelName}", + "to": "to", "Total Points": "Total Points", "Tourist": "Tourist", "Transaction date": "Transaction date", "Transactions": "Transactions", "Transportations": "Transportations", "Tripadvisor reviews": "{rating} ({count} reviews on Tripadvisor)", + "TUI Points": "TUI Points", "Type of bed": "Type of bed", "Type of room": "Type of room", + "uppercase letter": "uppercase letter", "Use bonus cheque": "Use bonus cheque", "User information": "User information", "View as list": "View as list", @@ -254,9 +275,9 @@ "You canceled adding a new credit card.": "You canceled adding a new credit card.", "You have no previous stays.": "You have no previous stays.", "You have no upcoming stays.": "You have no upcoming stays.", - "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your card was successfully removed!": "Your card was successfully removed!", "Your card was successfully saved!": "Your card was successfully saved!", + "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your current level": "Your current level", "Your details": "Your details", "Your level": "Your level", @@ -264,25 +285,5 @@ "Zip code": "Zip code", "Zoo": "Zoo", "Zoom in": "Zoom in", - "Zoom out": "Zoom out", - "as of today": "as of today", - "booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}", - "booking.nights": "{totalNights, plural, one {# night} other {# nights}}", - "booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}", - "by": "by", - "characters": "characters", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "persons", - "hotelPages.rooms.roomCard.seeRoomDetails": "See room details", - "km to city center": "km to city center", - "next level:": "next level:", - "night": "night", - "nights": "nights", - "number": "number", - "or": "or", - "points": "Points", - "special character": "special character", - "spendable points expiring by": "{points} spendable points expiring by {date}", - "to": "to", - "uppercase letter": "uppercase letter" -} \ No newline at end of file + "Zoom out": "Zoom out" +} diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index aa12eccf0..6faf8b396 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -14,6 +14,7 @@ "Any changes you've made will be lost.": "Kaikki tekemäsi muutokset menetetään.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Haluatko varmasti poistaa kortin, joka päättyy numeroon {lastFourDigits} jäsenprofiilistasi?", "Arrival date": "Saapumispäivä", + "as of today": "tänään", "As our": "{level}-etu", "As our Close Friend": "Läheisenä ystävänämme", "At latest": "Viimeistään", @@ -24,12 +25,15 @@ "Book": "Varaa", "Book reward night": "Kirjapalkinto-ilta", "Booking number": "Varausnumero", + "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", "Breakfast": "Aamiainen", "Breakfast excluded": "Aamiainen ei sisälly", "Breakfast included": "Aamiainen sisältyy", "Bus terminal": "Bussiasema", "Business": "Business", + "by": "mennessä", "Cancel": "Peruuttaa", + "characters": "hahmoja", "Check in": "Sisäänkirjautuminen", "Check out": "Uloskirjautuminen", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Tarkista profiiliisi tallennetut luottokortit. Maksa tallennetulla kortilla kirjautuneena, jotta verkkokokemus on sujuvampi.", @@ -47,6 +51,7 @@ "Code / Voucher": "Varauskoodit / kupongit", "Coming up": "Tulossa", "Compare all levels": "Vertaa kaikkia tasoja", + "Complete booking & go to payment": "Täydennä varaus & siirry maksamaan", "Contact us": "Ota meihin yhteyttä", "Continue": "Jatkaa", "Copyright all rights reserved": "Scandic AB Kaikki oikeudet pidätetään", @@ -72,9 +77,9 @@ "Explore all levels and benefits": "Tutustu kaikkiin tasoihin ja etuihin", "Explore nearby": "Tutustu lähialueeseen", "Extras to your booking": "Varauksessa lisäpalveluita", - "FAQ": "UKK", "Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.", "Fair": "Messukeskus", + "FAQ": "UKK", "Find booking": "Etsi varaus", "Find hotels": "Etsi hotelleja", "Flexibility": "Joustavuus", @@ -92,11 +97,15 @@ "Hotel": "Hotelli", "Hotel facilities": "Hotellin palvelut", "Hotel surroundings": "Hotellin ympäristö", + "hotelPages.rooms.roomCard.person": "henkilö", + "hotelPages.rooms.roomCard.persons": "Henkilöä", + "hotelPages.rooms.roomCard.seeRoomDetails": "Katso huoneen tiedot", "Hotels": "Hotellit", "How do you want to sleep?": "Kuinka haluat nukkua?", "How it works": "Kuinka se toimii", "Image gallery": "Kuvagalleria", "Join Scandic Friends": "Liity jäseneksi", + "km to city center": "km keskustaan", "Language": "Kieli", "Latest searches": "Viimeisimmät haut", "Level": "Level", @@ -123,9 +132,9 @@ "Member price": "Jäsenhinta", "Member price from": "Jäsenhinta alkaen", "Members": "Jäsenet", + "Membership cards": "Jäsenkortit", "Membership ID": "Jäsentunnus", "Membership ID copied to clipboard": "Jäsenyystunnus kopioitu leikepöydälle", - "Membership cards": "Jäsenkortit", "Menu": "Valikko", "Modify": "Muokkaa", "Month": "Kuukausi", @@ -140,6 +149,9 @@ "Nearby companies": "Läheiset yritykset", "New password": "Uusi salasana", "Next": "Seuraava", + "next level:": "pistettä tasolle:", + "night": "yö", + "nights": "yötä", "Nights needed to level up": "Yöt, joita tarvitaan tasolle", "No content published": "Ei julkaistua sisältöä", "No matching location found": "Vastaavaa sijaintia ei löytynyt", @@ -150,11 +162,13 @@ "Non-refundable": "Ei palautettavissa", "Not found": "Ei löydetty", "Nr night, nr adult": "{nights, number} yö, {adults, number} aikuinen", + "number": "määrä", "On your journey": "Matkallasi", "Open": "Avata", "Open language menu": "Avaa kielivalikko", "Open menu": "Avaa valikko", "Open my pages menu": "Avaa omat sivut -valikko", + "or": "tai", "Overview": "Yleiskatsaus", "Parking": "Pysäköinti", "Parking / Garage": "Pysäköinti / Autotalli", @@ -166,6 +180,7 @@ "Phone is required": "Puhelin vaaditaan", "Phone number": "Puhelinnumero", "Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero", + "points": "pistettä", "Points": "Pisteet", "Points being calculated": "Pisteitä lasketaan", "Points earned prior to May 1, 2021": "Pisteet, jotka ansaittu ennen 1.5.2021", @@ -208,25 +223,29 @@ "Something went wrong and we couldn't add your card. Please try again later.": "Jotain meni pieleen, emmekä voineet lisätä korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong and we couldn't remove your card. Please try again later.": "Jotain meni pieleen, emmekä voineet poistaa korttiasi. Yritä myöhemmin uudelleen.", "Something went wrong!": "Jotain meni pieleen!", + "special character": "erikoishahmo", + "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", "Sports": "Urheilu", "Standard price": "Normaali hinta", "Street": "Katu", "Successfully updated profile!": "Profiilin päivitys onnistui!", "Summary": "Yhteenveto", - "TUI Points": "TUI Points", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Kerro meille, mitä tietoja ja päivityksiä haluat saada ja miten, napsauttamalla alla olevaa linkkiä.", "Thank you": "Kiitos", "Theatre": "Teatteri", "There are no transactions to display": "Näytettäviä tapahtumia ei ole", "Things nearby HOTEL_NAME": "Lähellä olevia asioita {hotelName}", + "to": "to", "Total Points": "Kokonaispisteet", "Tourist": "Turisti", "Transaction date": "Tapahtuman päivämäärä", "Transactions": "Tapahtumat", "Transportations": "Kuljetukset", "Tripadvisor reviews": "{rating} ({count} arvostelua TripAdvisorissa)", + "TUI Points": "TUI Points", "Type of bed": "Vuodetyyppi", "Type of room": "Huonetyyppi", + "uppercase letter": "iso kirjain", "Use bonus cheque": "Käytä bonussekkiä", "User information": "Käyttäjän tiedot", "View as list": "Näytä listana", @@ -252,9 +271,9 @@ "You canceled adding a new credit card.": "Peruutit uuden luottokortin lisäämisen.", "You have no previous stays.": "Sinulla ei ole aiempia majoituksia.", "You have no upcoming stays.": "Sinulla ei ole tulevia majoituksia.", - "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!", "Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!", + "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your current level": "Nykyinen tasosi", "Your details": "Tietosi", "Your level": "Tasosi", @@ -262,23 +281,5 @@ "Zip code": "Postinumero", "Zoo": "Eläintarha", "Zoom in": "Lähennä", - "Zoom out": "Loitonna", - "as of today": "tänään", - "booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}", - "by": "mennessä", - "characters": "hahmoja", - "hotelPages.rooms.roomCard.person": "henkilö", - "hotelPages.rooms.roomCard.persons": "Henkilöä", - "hotelPages.rooms.roomCard.seeRoomDetails": "Katso huoneen tiedot", - "km to city center": "km keskustaan", - "next level:": "pistettä tasolle:", - "night": "yö", - "nights": "yötä", - "number": "määrä", - "or": "tai", - "points": "pistettä", - "special character": "erikoishahmo", - "spendable points expiring by": "{points} pistettä vanhenee {date} mennessä", - "to": "to", - "uppercase letter": "iso kirjain" -} \ No newline at end of file + "Zoom out": "Loitonna" +} diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 39dda59c3..916e8d15d 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -14,6 +14,7 @@ "Any changes you've made will be lost.": "Eventuelle endringer du har gjort, går tapt.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Er du sikker på at du vil fjerne kortet som slutter på {lastFourDigits} fra medlemsprofilen din?", "Arrival date": "Ankomstdato", + "as of today": "per idag", "As our": "Som vår {level}", "As our Close Friend": "Som vår nære venn", "At latest": "Senest", @@ -23,14 +24,16 @@ "Bed type": "Seng type", "Book": "Bestill", "Book reward night": "Bestill belønningskveld", - "Code / Voucher": "Bestillingskoder / kuponger", "Booking number": "Bestillingsnummer", + "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", "Breakfast": "Frokost", "Breakfast excluded": "Frokost ekskludert", "Breakfast included": "Frokost inkludert", "Bus terminal": "Bussterminal", "Business": "Forretnings", + "by": "innen", "Cancel": "Avbryt", + "characters": "tegn", "Check in": "Sjekk inn", "Check out": "Sjekk ut", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Sjekk ut kredittkortene som er lagret på profilen din. Betal med et lagret kort når du er pålogget for en jevnere nettopplevelse.", @@ -45,8 +48,10 @@ "Close menu": "Lukk meny", "Close my pages menu": "Lukk mine sidermenyn", "Close the map": "Lukk kartet", + "Code / Voucher": "Bestillingskoder / kuponger", "Coming up": "Kommer opp", "Compare all levels": "Sammenlign alle nivåer", + "Complete booking & go to payment": "Fullfør bestilling & gå til betaling", "Contact us": "Kontakt oss", "Continue": "Fortsette", "Copyright all rights reserved": "Scandic AB Alle rettigheter forbeholdt", @@ -72,9 +77,9 @@ "Explore all levels and benefits": "Utforsk alle nivåer og fordeler", "Explore nearby": "Utforsk i nærheten", "Extras to your booking": "Tilvalg til bestillingen din", - "FAQ": "FAQ", "Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.", "Fair": "Messe", + "FAQ": "FAQ", "Find booking": "Finn booking", "Find hotels": "Finn hotell", "Flexibility": "Fleksibilitet", @@ -85,17 +90,22 @@ "Get inspired": "Bli inspirert", "Go back to edit": "Gå tilbake til redigering", "Go back to overview": "Gå tilbake til oversikten", + "Guests & Rooms": "Gjester & rom", "Hi": "Hei", "Highest level": "Høyeste nivå", "Hospital": "Sykehus", "Hotel": "Hotel", "Hotel facilities": "Hotelfaciliteter", "Hotel surroundings": "Hotellomgivelser", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "personer", + "hotelPages.rooms.roomCard.seeRoomDetails": "Se detaljer om rommet", "Hotels": "Hoteller", "How do you want to sleep?": "Hvordan vil du sove?", "How it works": "Hvordan det fungerer", "Image gallery": "Bildegalleri", "Join Scandic Friends": "Bli med i Scandic Friends", + "km to city center": "km til sentrum", "Language": "Språk", "Latest searches": "Siste søk", "Level": "Nivå", @@ -122,9 +132,9 @@ "Member price": "Medlemspris", "Member price from": "Medlemspris fra", "Members": "Medlemmer", + "Membership cards": "Medlemskort", "Membership ID": "Medlems-ID", "Membership ID copied to clipboard": "Medlems-ID kopiert til utklippstavlen", - "Membership cards": "Medlemskort", "Menu": "Menu", "Modify": "Endre", "Month": "Måned", @@ -139,6 +149,9 @@ "Nearby companies": "Nærliggende selskaper", "New password": "Nytt passord", "Next": "Neste", + "next level:": "Neste nivå:", + "night": "natt", + "nights": "netter", "Nights needed to level up": "Netter som trengs for å komme opp i nivå", "No content published": "Ingen innhold publisert", "No matching location found": "Fant ingen samsvarende plassering", @@ -149,11 +162,13 @@ "Non-refundable": "Ikke-refunderbart", "Not found": "Ikke funnet", "Nr night, nr adult": "{nights, number} natt, {adults, number} voksen", + "number": "antall", "On your journey": "På reisen din", "Open": "Åpen", "Open language menu": "Åpne språkmenyen", "Open menu": "Åpne menyen", "Open my pages menu": "Åpne mine sider menyen", + "or": "eller", "Overview": "Oversikt", "Parking": "Parkering", "Parking / Garage": "Parkering / Garasje", @@ -165,6 +180,7 @@ "Phone is required": "Telefon kreves", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", + "points": "poeng", "Points": "Poeng", "Points being calculated": "Poeng beregnes", "Points earned prior to May 1, 2021": "Opptjente poeng før 1. mai 2021", @@ -183,7 +199,6 @@ "Room & Terms": "Rom & Vilkår", "Room facilities": "Romfasiliteter", "Rooms": "Rom", - "Guests & Rooms": "Gjester & rom", "Save": "Lagre", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", @@ -208,25 +223,29 @@ "Something went wrong and we couldn't add your card. Please try again later.": "Noe gikk galt, og vi kunne ikke legge til kortet ditt. Prøv igjen senere.", "Something went wrong and we couldn't remove your card. Please try again later.": "Noe gikk galt, og vi kunne ikke fjerne kortet ditt. Vennligst prøv igjen senere.", "Something went wrong!": "Noe gikk galt!", + "special character": "spesiell karakter", + "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", "Sports": "Sport", "Standard price": "Standardpris", "Street": "Gate", "Successfully updated profile!": "Vellykket oppdatert profil!", "Summary": "Sammendrag", - "TUI Points": "TUI Points", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Fortell oss hvilken informasjon og hvilke oppdateringer du ønsker å motta, og hvordan, ved å klikke på lenken nedenfor.", "Thank you": "Takk", "Theatre": "Teater", "There are no transactions to display": "Det er ingen transaksjoner å vise", "Things nearby HOTEL_NAME": "Ting i nærheten av {hotelName}", + "to": "til", "Total Points": "Totale poeng", "Tourist": "Turist", "Transaction date": "Transaksjonsdato", "Transactions": "Transaksjoner", "Transportations": "Transport", "Tripadvisor reviews": "{rating} ({count} anmeldelser på Tripadvisor)", + "TUI Points": "TUI Points", "Type of bed": "Sengtype", "Type of room": "Romtype", + "uppercase letter": "stor bokstav", "Use bonus cheque": "Bruk bonussjekk", "User information": "Brukerinformasjon", "View as list": "Vis som liste", @@ -252,9 +271,9 @@ "You canceled adding a new credit card.": "Du kansellerte å legge til et nytt kredittkort.", "You have no previous stays.": "Du har ingen tidligere opphold.", "You have no upcoming stays.": "Du har ingen kommende opphold.", - "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your card was successfully removed!": "Kortet ditt ble fjernet!", "Your card was successfully saved!": "Kortet ditt ble lagret!", + "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your current level": "Ditt nåværende nivå", "Your details": "Dine detaljer", "Your level": "Ditt nivå", @@ -262,23 +281,5 @@ "Zip code": "Post kode", "Zoo": "Dyrehage", "Zoom in": "Zoom inn", - "Zoom out": "Zoom ut", - "as of today": "per i dag", - "booking.nights": "{totalNights, plural, one {# natt} other {# netter}}", - "by": "innen", - "characters": "tegn", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personer", - "hotelPages.rooms.roomCard.seeRoomDetails": "Se detaljer om rommet", - "km to city center": "km til sentrum", - "next level:": "Neste nivå:", - "night": "natt", - "nights": "netter", - "number": "antall", - "or": "eller", - "points": "poeng", - "special character": "spesiell karakter", - "spendable points expiring by": "{points} Brukbare poeng utløper innen {date}", - "to": "til", - "uppercase letter": "stor bokstav" + "Zoom out": "Zoom ut" } diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index e2205b7b6..02291102e 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -14,6 +14,7 @@ "Any changes you've made will be lost.": "Alla ändringar du har gjort kommer att gå förlorade.", "Are you sure you want to remove the card ending with {lastFourDigits} from your member profile?": "Är du säker på att du vill ta bort kortet som slutar med {lastFourDigits} från din medlemsprofil?", "Arrival date": "Ankomstdatum", + "as of today": "från och med idag", "As our": "Som vår {level}", "As our Close Friend": "Som vår nära vän", "At latest": "Senast", @@ -23,14 +24,16 @@ "Bed type": "Sängtyp", "Book": "Boka", "Book reward night": "Boka frinatt", - "Code / Voucher": "Bokningskoder / kuponger", "Booking number": "Bokningsnummer", + "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", "Breakfast": "Frukost", "Breakfast excluded": "Frukost ingår ej", "Breakfast included": "Frukost ingår", "Bus terminal": "Bussterminal", "Business": "Business", + "by": "innan", "Cancel": "Avbryt", + "characters": "tecken", "Check in": "Checka in", "Check out": "Checka ut", "Check out the credit cards saved to your profile. Pay with a saved card when signed in for a smoother web experience.": "Kolla in kreditkorten som sparats i din profil. Betala med ett sparat kort när du är inloggad för en smidigare webbupplevelse.", @@ -45,8 +48,10 @@ "Close menu": "Stäng menyn", "Close my pages menu": "Stäng mina sidor menyn", "Close the map": "Stäng kartan", + "Code / Voucher": "Bokningskoder / kuponger", "Coming up": "Kommer härnäst", "Compare all levels": "Jämför alla nivåer", + "Complete booking & go to payment": "Fullför bokning & gå till betalning", "Contact us": "Kontakta oss", "Continue": "Fortsätt", "Copyright all rights reserved": "Scandic AB Alla rättigheter förbehålls", @@ -72,9 +77,9 @@ "Explore all levels and benefits": "Utforska alla nivåer och fördelar", "Explore nearby": "Utforska i närheten", "Extras to your booking": "Extra tillval till din bokning", - "FAQ": "FAQ", "Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.", "Fair": "Mässa", + "FAQ": "FAQ", "Find booking": "Hitta bokning", "Find hotels": "Hitta hotell", "Flexibility": "Flexibilitet", @@ -85,17 +90,22 @@ "Get inspired": "Bli inspirerad", "Go back to edit": "Gå tillbaka till redigeringen", "Go back to overview": "Gå tillbaka till översikten", + "Guests & Rooms": "Gäster & rum", "Hi": "Hej", "Highest level": "Högsta nivå", "Hospital": "Sjukhus", "Hotel": "Hotell", "Hotel facilities": "Hotellfaciliteter", "Hotel surroundings": "Hotellomgivning", + "hotelPages.rooms.roomCard.person": "person", + "hotelPages.rooms.roomCard.persons": "personer", + "hotelPages.rooms.roomCard.seeRoomDetails": "Se information om rummet", "Hotels": "Hotell", "How do you want to sleep?": "Hur vill du sova?", "How it works": "Hur det fungerar", "Image gallery": "Bildgalleri", "Join Scandic Friends": "Gå med i Scandic Friends", + "km to city center": "km till stadens centrum", "Language": "Språk", "Latest searches": "Senaste sökningarna", "Level": "Nivå", @@ -122,9 +132,9 @@ "Member price": "Medlemspris", "Member price from": "Medlemspris från", "Members": "Medlemmar", + "Membership cards": "Medlemskort", "Membership ID": "Medlems-ID", "Membership ID copied to clipboard": "Medlems-ID kopierat till urklipp", - "Membership cards": "Medlemskort", "Menu": "Meny", "Modify": "Ändra", "Month": "Månad", @@ -139,6 +149,9 @@ "Nearby companies": "Närliggande företag", "New password": "Nytt lösenord", "Next": "Nästa", + "next level:": "Nästa nivå:", + "night": "natt", + "nights": "nätter", "Nights needed to level up": "Nätter som behövs för att gå upp i nivå", "No content published": "Inget innehåll publicerat", "No matching location found": "Ingen matchande plats hittades", @@ -149,11 +162,13 @@ "Non-refundable": "Ej återbetalningsbar", "Not found": "Hittades inte", "Nr night, nr adult": "{nights, number} natt, {adults, number} vuxen", + "number": "nummer", "On your journey": "På din resa", "Open": "Öppna", "Open language menu": "Öppna språkmenyn", "Open menu": "Öppna menyn", "Open my pages menu": "Öppna mina sidor menyn", + "or": "eller", "Overview": "Översikt", "Parking": "Parkering", "Parking / Garage": "Parkering / Garage", @@ -165,6 +180,7 @@ "Phone is required": "Telefonnummer är obligatorisk", "Phone number": "Telefonnummer", "Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer", + "points": "poäng", "Points": "Poäng", "Points being calculated": "Poäng beräknas", "Points earned prior to May 1, 2021": "Intjänade poäng före den 1 maj 2021", @@ -183,7 +199,6 @@ "Room & Terms": "Rum & Villkor", "Room facilities": "Rumfaciliteter", "Rooms": "Rum", - "Guests & Rooms": "Gäster & rum", "Save": "Spara", "Scandic Friends Mastercard": "Scandic Friends Mastercard", "Scandic Friends Point Shop": "Scandic Friends Point Shop", @@ -208,25 +223,29 @@ "Something went wrong and we couldn't add your card. Please try again later.": "Något gick fel och vi kunde inte lägga till ditt kort. Försök igen senare.", "Something went wrong and we couldn't remove your card. Please try again later.": "Något gick fel och vi kunde inte ta bort ditt kort. Försök igen senare.", "Something went wrong!": "Något gick fel!", + "special character": "speciell karaktär", + "spendable points expiring by": "{points} poäng förfaller {date}", "Sports": "Sport", "Standard price": "Standardpris", "Street": "Gata", "Successfully updated profile!": "Profilen har uppdaterats framgångsrikt!", "Summary": "Sammanfattning", - "TUI Points": "TUI Points", "Tell us what information and updates you'd like to receive, and how, by clicking the link below.": "Berätta för oss vilken information och vilka uppdateringar du vill få och hur genom att klicka på länken nedan.", "Thank you": "Tack", "Theatre": "Teater", "There are no transactions to display": "Det finns inga transaktioner att visa", "Things nearby HOTEL_NAME": "Saker i närheten av {hotelName}", + "to": "till", "Total Points": "Poäng totalt", "Tourist": "Turist", "Transaction date": "Transaktionsdatum", "Transactions": "Transaktioner", "Transportations": "Transport", "Tripadvisor reviews": "{rating} ({count} recensioner på Tripadvisor)", + "TUI Points": "TUI Points", "Type of bed": "Sängtyp", "Type of room": "Rumstyp", + "uppercase letter": "stor bokstav", "Use bonus cheque": "Use bonus cheque", "User information": "Användarinformation", "View as list": "Visa som lista", @@ -252,9 +271,9 @@ "You canceled adding a new credit card.": "Du avbröt att lägga till ett nytt kreditkort.", "You have no previous stays.": "Du har inga tidigare vistelser.", "You have no upcoming stays.": "Du har inga planerade resor.", - "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your card was successfully removed!": "Ditt kort har tagits bort!", "Your card was successfully saved!": "Ditt kort har sparats!", + "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your current level": "Din nuvarande nivå", "Your details": "Dina uppgifter", "Your level": "Din nivå", @@ -262,23 +281,5 @@ "Zip code": "Postnummer", "Zoo": "Djurpark", "Zoom in": "Zooma in", - "Zoom out": "Zooma ut", - "as of today": "per idag", - "booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}", - "by": "innan", - "characters": "tecken", - "hotelPages.rooms.roomCard.person": "person", - "hotelPages.rooms.roomCard.persons": "personer", - "hotelPages.rooms.roomCard.seeRoomDetails": "Se information om rummet", - "km to city center": "km till stadens centrum", - "next level:": "Nästa nivå:", - "night": "natt", - "nights": "nätter", - "number": "nummer", - "or": "eller", - "points": "poäng", - "special character": "speciell karaktär", - "spendable points expiring by": "{points} poäng förfaller {date}", - "to": "till", - "uppercase letter": "stor bokstav" + "Zoom out": "Zooma ut" } diff --git a/server/routers/booking/index.ts b/server/routers/booking/index.ts index 65b968733..f3c0b45ad 100644 --- a/server/routers/booking/index.ts +++ b/server/routers/booking/index.ts @@ -1,5 +1,9 @@ import { mergeRouters } from "@/server/trpc" import { bookingMutationRouter } from "./mutation" +import { bookingQueryRouter } from "./query" -export const bookingRouter = mergeRouters(bookingMutationRouter) +export const bookingRouter = mergeRouters( + bookingMutationRouter, + bookingQueryRouter +) diff --git a/server/routers/booking/input.ts b/server/routers/booking/input.ts index 46a88110e..7e9b9b2c8 100644 --- a/server/routers/booking/input.ts +++ b/server/routers/booking/input.ts @@ -1,38 +1,68 @@ import { z } from "zod" -// Query +const roomsSchema = z.array( + z.object({ + adults: z.number().int().nonnegative(), + childrenAges: z + .array( + z.object({ + age: z.number().int().nonnegative(), + bedType: z.string(), + }) + ) + .default([]), + rateCode: z.string(), + roomTypeCode: z.string(), + guest: z.object({ + title: z.string(), + firstName: z.string(), + lastName: z.string(), + email: z.string().email(), + phoneCountryCodePrefix: z.string(), + phoneNumber: z.string(), + countryCode: z.string(), + membershipNumber: z.string().optional(), + }), + smsConfirmationRequested: z.boolean(), + packages: z.object({ + breakfast: z.boolean(), + allergyFriendly: z.boolean(), + petFriendly: z.boolean(), + accessibility: z.boolean(), + }), + }) +) + +const paymentSchema = z.object({ + paymentMethod: z.string(), + card: z + .object({ + alias: z.string(), + expiryDate: z.string(), + cardType: z.string(), + }) + .optional(), + cardHolder: z.object({ + email: z.string().email(), + name: z.string(), + phoneCountryCode: z.string(), + phoneSubscriber: z.string(), + }), + success: z.string(), + error: z.string(), + cancel: z.string(), +}) + // Mutation export const createBookingInput = z.object({ hotelId: z.string(), checkInDate: z.string(), checkOutDate: z.string(), - rooms: z.array( - z.object({ - adults: z.number().int().nonnegative(), - children: z.number().int().nonnegative(), - rateCode: z.string(), - roomTypeCode: z.string(), - guest: z.object({ - title: z.string(), - firstName: z.string(), - lastName: z.string(), - email: z.string().email(), - phoneCountryCodePrefix: z.string(), - phoneNumber: z.string(), - countryCode: z.string(), - }), - smsConfirmationRequested: z.boolean(), - }) - ), - payment: z.object({ - cardHolder: z.object({ - Email: z.string().email(), - Name: z.string(), - PhoneCountryCode: z.string(), - PhoneSubscriber: z.string(), - }), - success: z.string(), - error: z.string(), - cancel: z.string(), - }), + rooms: roomsSchema, + payment: paymentSchema, +}) + +// Query +export const getBookingStatusInput = z.object({ + confirmationNumber: z.string(), }) diff --git a/server/routers/booking/mutation.ts b/server/routers/booking/mutation.ts index 2b35f56d4..53595b2d0 100644 --- a/server/routers/booking/mutation.ts +++ b/server/routers/booking/mutation.ts @@ -2,7 +2,7 @@ import { metrics } from "@opentelemetry/api" import * as api from "@/lib/api" import { getVerifiedUser } from "@/server/routers/user/query" -import { router, safeProtectedProcedure } from "@/server/trpc" +import { bookingServiceProcedure, router } from "@/server/trpc" import { getMembership } from "@/utils/user" @@ -36,13 +36,15 @@ async function getMembershipNumber( export const bookingMutationRouter = router({ booking: router({ - create: safeProtectedProcedure + create: bookingServiceProcedure .input(createBookingInput) .mutation(async function ({ ctx, input }) { const { checkInDate, checkOutDate, hotelId } = input + // TODO: add support for user token OR service token in procedure + // then we can fetch membership number if user token exists const loggingAttributes = { - membershipNumber: await getMembershipNumber(ctx.session), + // membershipNumber: await getMembershipNumber(ctx.session), checkInDate, checkOutDate, hotelId, @@ -56,11 +58,10 @@ export const bookingMutationRouter = router({ query: loggingAttributes, }) ) - const headers = ctx.session - ? { - Authorization: `Bearer ${ctx.session?.token.access_token}`, - } - : undefined + const headers = { + Authorization: `Bearer ${ctx.serviceToken}`, + } + const apiResponse = await api.post(api.endpoints.v1.booking, { headers, body: input, diff --git a/server/routers/booking/output.ts b/server/routers/booking/output.ts index 8fedd8716..dbc8101df 100644 --- a/server/routers/booking/output.ts +++ b/server/routers/booking/output.ts @@ -5,9 +5,9 @@ export const createBookingSchema = z data: z.object({ attributes: z.object({ confirmationNumber: z.string(), - cancellationNumber: z.string().nullable(), + cancellationNumber: z.string().optional(), reservationStatus: z.string(), - paymentUrl: z.string().nullable(), + paymentUrl: z.string().optional(), }), type: z.string(), id: z.string(), diff --git a/server/routers/booking/query.ts b/server/routers/booking/query.ts new file mode 100644 index 000000000..f7f439b90 --- /dev/null +++ b/server/routers/booking/query.ts @@ -0,0 +1,85 @@ +import { metrics } from "@opentelemetry/api" + +import * as api from "@/lib/api" +import { badRequestError, serverErrorByStatus } from "@/server/errors/trpc" +import { bookingServiceProcedure, router } from "@/server/trpc" + +import { getBookingStatusInput } from "./input" +import { createBookingSchema } from "./output" + +const meter = metrics.getMeter("trpc.booking") +const getBookingStatusCounter = meter.createCounter("trpc.booking.status") +const getBookingStatusSuccessCounter = meter.createCounter( + "trpc.booking.status-success" +) +const getBookingStatusFailCounter = meter.createCounter( + "trpc.booking.status-fail" +) + +export const bookingQueryRouter = router({ + status: bookingServiceProcedure + .input(getBookingStatusInput) + .query(async function ({ ctx, input }) { + const { confirmationNumber } = input + getBookingStatusCounter.add(1, { confirmationNumber }) + + const apiResponse = await api.get( + `${api.endpoints.v1.booking}/${confirmationNumber}/status`, + { + headers: { + Authorization: `Bearer ${ctx.serviceToken}`, + }, + } + ) + + if (!apiResponse.ok) { + const responseMessage = await apiResponse.text() + getBookingStatusFailCounter.add(1, { + confirmationNumber, + error_type: "http_error", + error: responseMessage, + }) + console.error( + "api.booking.status error", + JSON.stringify({ + query: { confirmationNumber }, + error: { + status: apiResponse.status, + statusText: apiResponse.statusText, + text: responseMessage, + }, + }) + ) + + throw serverErrorByStatus(apiResponse.status, apiResponse) + } + + const apiJson = await apiResponse.json() + const verifiedData = createBookingSchema.safeParse(apiJson) + if (!verifiedData.success) { + getBookingStatusFailCounter.add(1, { + confirmationNumber, + error_type: "validation_error", + error: JSON.stringify(verifiedData.error), + }) + console.error( + "api.booking.status validation error", + JSON.stringify({ + query: { confirmationNumber }, + error: verifiedData.error, + }) + ) + throw badRequestError() + } + + getBookingStatusSuccessCounter.add(1, { confirmationNumber }) + console.info( + "api.booking.status success", + JSON.stringify({ + query: { confirmationNumber }, + }) + ) + + return verifiedData.data + }), +}) diff --git a/server/routers/hotels/output.ts b/server/routers/hotels/output.ts index d0d95903a..d1fb2dfe8 100644 --- a/server/routers/hotels/output.ts +++ b/server/routers/hotels/output.ts @@ -436,6 +436,22 @@ export const roomSchema = z.object({ type: z.enum(["roomcategories"]), }) +const merchantInformationSchema = z.object({ + webMerchantId: z.string(), + cards: z.record(z.string(), z.boolean()).transform((val) => { + return Object.entries(val) + .filter(([_, enabled]) => enabled) + .map(([key]) => key) + }), + alternatePaymentOptions: z + .record(z.string(), z.boolean()) + .transform((val) => { + return Object.entries(val) + .filter(([_, enabled]) => enabled) + .map(([key]) => key) + }), +}) + // NOTE: Find schema at: https://aks-test.scandichotels.com/hotel/swagger/v1/index.html export const getHotelDataSchema = z.object({ data: z.object({ @@ -471,6 +487,7 @@ export const getHotelDataSchema = z.object({ hotelContent: hotelContentSchema, detailedFacilities: z.array(detailedFacilitySchema), healthFacilities: z.array(healthFacilitySchema), + merchantInformationData: merchantInformationSchema, rewardNight: rewardNightSchema, pointsOfInterest: z .array(pointOfInterestSchema) diff --git a/server/trpc.ts b/server/trpc.ts index 4f5409a01..e8f9c26f1 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -121,29 +121,24 @@ export const safeProtectedProcedure = t.procedure.use(async function (opts) { }) }) -export const profileServiceProcedure = t.procedure.use(async (opts) => { - const { access_token } = await fetchServiceToken(["profile"]) - if (!access_token) { - throw internalServerError("Failed to obtain profile service token") - } - return opts.next({ - ctx: { - serviceToken: access_token, - }, +function createServiceProcedure(serviceName: string) { + return t.procedure.use(async (opts) => { + const { access_token } = await fetchServiceToken([serviceName]) + if (!access_token) { + throw internalServerError(`Failed to obtain ${serviceName} service token`) + } + return opts.next({ + ctx: { + serviceToken: access_token, + }, + }) }) -}) +} + +export const bookingServiceProcedure = createServiceProcedure("booking") +export const hotelServiceProcedure = createServiceProcedure("hotel") +export const profileServiceProcedure = createServiceProcedure("profile") -export const hotelServiceProcedure = t.procedure.use(async (opts) => { - const { access_token } = await fetchServiceToken(["hotel"]) - if (!access_token) { - throw internalServerError("Failed to obtain hotel service token") - } - return opts.next({ - ctx: { - serviceToken: access_token, - }, - }) -}) export const serverActionProcedure = t.procedure.experimental_caller( experimental_nextAppDirCaller({ createContext, diff --git a/types/components/hotelReservation/selectRate/section.ts b/types/components/hotelReservation/selectRate/section.ts index e64e51506..69460962b 100644 --- a/types/components/hotelReservation/selectRate/section.ts +++ b/types/components/hotelReservation/selectRate/section.ts @@ -1,5 +1,7 @@ import { Rate } from "@/server/routers/hotels/output" +import { Hotel } from "@/types/hotel" + export interface SectionProps { nextPath: string } @@ -33,6 +35,10 @@ export interface RoomSelectionProps extends SectionProps { export interface DetailsProps extends SectionProps {} +export interface PaymentProps { + hotel: Hotel +} + export interface SectionPageProps { breakfast?: string bed?: string