Merge branch 'develop' into feat/sw-222-staycard-link
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
import {
|
||||
getCreditCardsSafely,
|
||||
getHotelData,
|
||||
getProfileSafely,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import EnterDetailsProvider from "@/components/HotelReservation/EnterDetails/Provider"
|
||||
import SelectedRoom from "@/components/HotelReservation/EnterDetails/SelectedRoom"
|
||||
@@ -14,15 +18,20 @@ import styles from "./layout.module.css"
|
||||
import { StepEnum } from "@/types/components/enterDetails/step"
|
||||
import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
|
||||
function preload(id: string, lang: string) {
|
||||
void getHotelData(id, lang)
|
||||
void getProfileSafely()
|
||||
void getCreditCardsSafely()
|
||||
}
|
||||
|
||||
export default async function StepLayout({
|
||||
children,
|
||||
params,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams & { step: StepEnum }>>) {
|
||||
setLang(params.lang)
|
||||
const hotel = await serverClient().hotel.hotelData.get({
|
||||
hotelId: "811",
|
||||
language: params.lang,
|
||||
})
|
||||
preload("811", params.lang)
|
||||
|
||||
const hotel = await getHotelData("811", params.lang)
|
||||
|
||||
if (!hotel?.data) {
|
||||
redirect(`/${params.lang}`)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
import {
|
||||
getCreditCardsSafely,
|
||||
getHotelData,
|
||||
getProfileSafely,
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import BedType from "@/components/HotelReservation/EnterDetails/BedType"
|
||||
import Breakfast from "@/components/HotelReservation/EnterDetails/Breakfast"
|
||||
import Details from "@/components/HotelReservation/EnterDetails/Details"
|
||||
import HistoryStateManager from "@/components/HotelReservation/EnterDetails/HistoryStateManager"
|
||||
import Payment from "@/components/HotelReservation/EnterDetails/Payment"
|
||||
import SectionAccordion from "@/components/HotelReservation/EnterDetails/SectionAccordion"
|
||||
import Payment from "@/components/HotelReservation/SelectRate/Payment"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import { StepEnum } from "@/types/components/enterDetails/step"
|
||||
@@ -25,12 +28,9 @@ export default async function StepPage({
|
||||
|
||||
const intl = await getIntl()
|
||||
|
||||
const hotel = await serverClient().hotel.hotelData.get({
|
||||
hotelId: "811",
|
||||
language: lang,
|
||||
})
|
||||
|
||||
const hotel = await getHotelData("811", lang)
|
||||
const user = await getProfileSafely()
|
||||
const savedCreditCards = await getCreditCardsSafely()
|
||||
|
||||
if (!isValidStep(step) || !hotel) {
|
||||
return notFound()
|
||||
@@ -65,7 +65,14 @@ export default async function StepPage({
|
||||
step={StepEnum.payment}
|
||||
label={intl.formatMessage({ id: "Select payment method" })}
|
||||
>
|
||||
<Payment hotel={hotel.data.attributes} />
|
||||
<Payment
|
||||
hotelId={hotel.data.attributes.operaId}
|
||||
otherPaymentOptions={
|
||||
hotel.data.attributes.merchantInformationData
|
||||
.alternatePaymentOptions
|
||||
}
|
||||
savedCreditCards={savedCreditCards}
|
||||
/>
|
||||
</SectionAccordion>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
padding: var(--Spacing-x3) var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.column {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3);
|
||||
padding: var(--Spacing-x3) var(--Spacing-x4);
|
||||
}
|
||||
@media screen and (min-width: 767px) {
|
||||
.grid {
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
display: grid;
|
||||
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
|
||||
gap: var(--Spacing-x4);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
|
||||
@@ -44,6 +44,7 @@ export default async function ContentPage() {
|
||||
<Hero
|
||||
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
|
||||
src={hero_image.url}
|
||||
focalPoint={hero_image.focalPoint}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -397,6 +397,7 @@ export const renderOptions: RenderOptions = {
|
||||
height={365}
|
||||
src={image.url}
|
||||
width={width}
|
||||
focalPoint={image.focalPoint}
|
||||
{...props}
|
||||
/>
|
||||
<Caption>{image.meta.caption}</Caption>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { FocalPoint } from "@/types/components/image"
|
||||
|
||||
export interface HeroProps {
|
||||
alt: string
|
||||
src: string
|
||||
focalPoint?: FocalPoint
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { HeroProps } from "./hero"
|
||||
|
||||
import styles from "./hero.module.css"
|
||||
|
||||
export default async function Hero({ alt, src }: HeroProps) {
|
||||
export default async function Hero({ alt, src, focalPoint }: HeroProps) {
|
||||
return (
|
||||
<Image
|
||||
className={styles.hero}
|
||||
@@ -12,6 +12,7 @@ export default async function Hero({ alt, src }: HeroProps) {
|
||||
height={480}
|
||||
width={1196}
|
||||
src={src}
|
||||
focalPoint={focalPoint}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import Image from "next/image"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
|
||||
import { PAYMENT_METHOD_ICONS, PaymentMethodEnum } from "@/constants/booking"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import { PaymentOptionProps } from "./paymentOption"
|
||||
|
||||
import styles from "./paymentOption.module.css"
|
||||
|
||||
export default function PaymentOption({
|
||||
name,
|
||||
value,
|
||||
label,
|
||||
cardNumber,
|
||||
registerOptions = {},
|
||||
}: PaymentOptionProps) {
|
||||
const { register } = useFormContext()
|
||||
|
||||
return (
|
||||
<label key={value} className={styles.paymentOption}>
|
||||
<div className={styles.titleContainer}>
|
||||
<input
|
||||
aria-hidden
|
||||
hidden
|
||||
type="radio"
|
||||
id={value}
|
||||
value={value}
|
||||
{...register(name, registerOptions)}
|
||||
/>
|
||||
<span className={styles.radio} />
|
||||
<Body>{label}</Body>
|
||||
</div>
|
||||
{cardNumber ? (
|
||||
<Caption color="uiTextMediumContrast">•••• {cardNumber}</Caption>
|
||||
) : (
|
||||
<Image
|
||||
className={styles.paymentOptionIcon}
|
||||
src={PAYMENT_METHOD_ICONS[value as PaymentMethodEnum]}
|
||||
alt={label}
|
||||
width={48}
|
||||
height={32}
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { RegisterOptions } from "react-hook-form"
|
||||
|
||||
import { PaymentMethodEnum } from "@/constants/booking"
|
||||
|
||||
export interface PaymentOptionProps {
|
||||
name: string
|
||||
value: PaymentMethodEnum
|
||||
value: string
|
||||
label: string
|
||||
cardNumber?: string
|
||||
registerOptions?: RegisterOptions
|
||||
onChange?: () => void
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import LoadingSpinner from "@/components/LoadingSpinner"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Checkbox from "@/components/TempDesignSystem/Checkbox"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||
import { useHandleBookingStatus } from "@/hooks/booking/useHandleBookingStatus"
|
||||
@@ -38,7 +39,15 @@ import { PaymentProps } from "@/types/components/hotelReservation/selectRate/sec
|
||||
const maxRetries = 40
|
||||
const retryInterval = 2000
|
||||
|
||||
export default function Payment({ hotel }: PaymentProps) {
|
||||
function isPaymentMethodEnum(value: string): value is PaymentMethodEnum {
|
||||
return Object.values(PaymentMethodEnum).includes(value as PaymentMethodEnum)
|
||||
}
|
||||
|
||||
export default function Payment({
|
||||
hotelId,
|
||||
otherPaymentOptions,
|
||||
savedCreditCards,
|
||||
}: PaymentProps) {
|
||||
const router = useRouter()
|
||||
const lang = useLang()
|
||||
const intl = useIntl()
|
||||
@@ -46,7 +55,9 @@ export default function Payment({ hotel }: PaymentProps) {
|
||||
|
||||
const methods = useForm<PaymentFormData>({
|
||||
defaultValues: {
|
||||
paymentMethod: PaymentMethodEnum.card,
|
||||
paymentMethod: savedCreditCards?.length
|
||||
? savedCreditCards[0].id
|
||||
: PaymentMethodEnum.card,
|
||||
smsConfirmation: false,
|
||||
termsAndConditions: false,
|
||||
},
|
||||
@@ -87,8 +98,17 @@ export default function Payment({ hotel }: PaymentProps) {
|
||||
}, [confirmationNumber, bookingStatus, router])
|
||||
|
||||
function handleSubmit(data: PaymentFormData) {
|
||||
// set payment method to card if saved card is submitted
|
||||
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
||||
? data.paymentMethod
|
||||
: PaymentMethodEnum.card
|
||||
|
||||
const savedCreditCard = savedCreditCards?.find(
|
||||
(card) => card.id === data.paymentMethod
|
||||
)
|
||||
|
||||
initiateBooking.mutate({
|
||||
hotelId: hotel.operaId,
|
||||
hotelId: hotelId,
|
||||
checkInDate: "2024-12-10",
|
||||
checkOutDate: "2024-12-11",
|
||||
rooms: [
|
||||
@@ -116,7 +136,14 @@ export default function Payment({ hotel }: PaymentProps) {
|
||||
},
|
||||
],
|
||||
payment: {
|
||||
paymentMethod: data.paymentMethod,
|
||||
paymentMethod,
|
||||
card: savedCreditCard
|
||||
? {
|
||||
alias: savedCreditCard.alias,
|
||||
expiryDate: savedCreditCard.expirationDate,
|
||||
cardType: savedCreditCard.cardType,
|
||||
}
|
||||
: undefined,
|
||||
cardHolder: {
|
||||
email: "test.user@scandichotels.com",
|
||||
name: "Test User",
|
||||
@@ -143,65 +170,94 @@ export default function Payment({ hotel }: PaymentProps) {
|
||||
className={styles.paymentContainer}
|
||||
onSubmit={methods.handleSubmit(handleSubmit)}
|
||||
>
|
||||
<div className={styles.paymentOptionContainer}>
|
||||
<PaymentOption
|
||||
name="paymentMethod"
|
||||
value={PaymentMethodEnum.card}
|
||||
label={intl.formatMessage({ id: "Credit card" })}
|
||||
/>
|
||||
{hotel.merchantInformationData.alternatePaymentOptions.map(
|
||||
(paymentMethod) => (
|
||||
{savedCreditCards?.length ? (
|
||||
<section className={styles.section}>
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
{intl.formatMessage({ id: "MY SAVED CARDS" })}
|
||||
</Body>
|
||||
<div className={styles.paymentOptionContainer}>
|
||||
{savedCreditCards?.map((savedCreditCard) => (
|
||||
<PaymentOption
|
||||
key={savedCreditCard.id}
|
||||
name="paymentMethod"
|
||||
value={savedCreditCard.id}
|
||||
label={
|
||||
PAYMENT_METHOD_TITLES[
|
||||
savedCreditCard.cardType as PaymentMethodEnum
|
||||
]
|
||||
}
|
||||
cardNumber={savedCreditCard.truncatedNumber}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
<section className={styles.section}>
|
||||
{savedCreditCards?.length ? (
|
||||
<Body color="uiTextHighContrast" textTransform="bold">
|
||||
{intl.formatMessage({ id: "OTHER PAYMENT METHODS" })}
|
||||
</Body>
|
||||
) : null}
|
||||
<div className={styles.paymentOptionContainer}>
|
||||
<PaymentOption
|
||||
name="paymentMethod"
|
||||
value={PaymentMethodEnum.card}
|
||||
label={intl.formatMessage({ id: "Credit card" })}
|
||||
/>
|
||||
{otherPaymentOptions.map((paymentMethod) => (
|
||||
<PaymentOption
|
||||
key={paymentMethod}
|
||||
name="paymentMethod"
|
||||
value={paymentMethod as PaymentMethodEnum}
|
||||
value={paymentMethod}
|
||||
label={
|
||||
PAYMENT_METHOD_TITLES[paymentMethod as PaymentMethodEnum]
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<Checkbox name="smsConfirmation">
|
||||
<Caption>
|
||||
{intl.formatMessage({
|
||||
id: "I would like to get my booking confirmation via sms",
|
||||
})}
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
<section className={styles.section}>
|
||||
<Checkbox name="smsConfirmation">
|
||||
<Caption>
|
||||
{intl.formatMessage({
|
||||
id: "I would like to get my booking confirmation via sms",
|
||||
})}
|
||||
</Caption>
|
||||
</Checkbox>
|
||||
|
||||
<AriaLabel className={styles.terms}>
|
||||
<Checkbox name="termsAndConditions" />
|
||||
<Caption>
|
||||
{intl.formatMessage<React.ReactNode>(
|
||||
{
|
||||
id: "booking.terms",
|
||||
},
|
||||
{
|
||||
termsLink: (str) => (
|
||||
<Link
|
||||
className={styles.link}
|
||||
variant="underscored"
|
||||
href={bookingTermsAndConditions[lang]}
|
||||
target="_blank"
|
||||
>
|
||||
{str}
|
||||
</Link>
|
||||
),
|
||||
privacyLink: (str) => (
|
||||
<Link
|
||||
className={styles.link}
|
||||
variant="underscored"
|
||||
href={privacyPolicy[lang]}
|
||||
target="_blank"
|
||||
>
|
||||
{str}
|
||||
</Link>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
</AriaLabel>
|
||||
<AriaLabel className={styles.terms}>
|
||||
<Checkbox name="termsAndConditions" />
|
||||
<Caption>
|
||||
{intl.formatMessage<React.ReactNode>(
|
||||
{
|
||||
id: "booking.terms",
|
||||
},
|
||||
{
|
||||
termsLink: (str) => (
|
||||
<Link
|
||||
className={styles.link}
|
||||
variant="underscored"
|
||||
href={bookingTermsAndConditions[lang]}
|
||||
target="_blank"
|
||||
>
|
||||
{str}
|
||||
</Link>
|
||||
),
|
||||
privacyLink: (str) => (
|
||||
<Link
|
||||
className={styles.link}
|
||||
variant="underscored"
|
||||
href={privacyPolicy[lang]}
|
||||
target="_blank"
|
||||
>
|
||||
{str}
|
||||
</Link>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Caption>
|
||||
</AriaLabel>
|
||||
</section>
|
||||
<Button
|
||||
type="submit"
|
||||
className={styles.submitButton}
|
||||
@@ -1,10 +1,16 @@
|
||||
.paymentContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
gap: var(--Spacing-x4);
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.paymentOptionContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -1,9 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { PaymentMethodEnum } from "@/constants/booking"
|
||||
|
||||
export const paymentSchema = z.object({
|
||||
paymentMethod: z.nativeEnum(PaymentMethodEnum),
|
||||
paymentMethod: z.string(),
|
||||
smsConfirmation: z.boolean(),
|
||||
termsAndConditions: z.boolean().refine((value) => value === true, {
|
||||
message: "You must accept the terms and conditions",
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import Image from "next/image"
|
||||
import { useFormContext } from "react-hook-form"
|
||||
|
||||
import { PAYMENT_METHOD_ICONS } from "@/constants/booking"
|
||||
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
|
||||
import { PaymentOptionProps } from "./paymentOption"
|
||||
|
||||
import styles from "./paymentOption.module.css"
|
||||
|
||||
export default function PaymentOption({
|
||||
name,
|
||||
value,
|
||||
label,
|
||||
}: PaymentOptionProps) {
|
||||
const { register } = useFormContext()
|
||||
return (
|
||||
<label key={value} className={styles.paymentOption} htmlFor={value}>
|
||||
<div className={styles.titleContainer}>
|
||||
<input
|
||||
aria-hidden
|
||||
hidden
|
||||
type="radio"
|
||||
id={value}
|
||||
value={value}
|
||||
{...register(name)}
|
||||
/>
|
||||
<span className={styles.radio} />
|
||||
<Body asChild>
|
||||
<label htmlFor={value}>{label}</label>
|
||||
</Body>
|
||||
</div>
|
||||
<Image
|
||||
className={styles.paymentOptionIcon}
|
||||
src={PAYMENT_METHOD_ICONS[value]}
|
||||
alt={label}
|
||||
width={48}
|
||||
height={32}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import NextImage from "next/image"
|
||||
|
||||
import type { ImageLoaderProps, ImageProps } from "next/image"
|
||||
import type { ImageLoaderProps } from "next/image"
|
||||
import type { CSSProperties } from "react"
|
||||
|
||||
import type { ImageProps } from "@/types/components/image"
|
||||
|
||||
function imageLoader({ quality, src, width }: ImageLoaderProps) {
|
||||
const hasQS = src.indexOf("?") !== -1
|
||||
@@ -10,6 +13,14 @@ function imageLoader({ quality, src, width }: ImageLoaderProps) {
|
||||
}
|
||||
|
||||
// Next/Image adds & instead of ? before the params
|
||||
export default function Image(props: ImageProps) {
|
||||
return <NextImage {...props} loader={imageLoader} />
|
||||
export default function Image({ focalPoint, style, ...props }: ImageProps) {
|
||||
const styles: CSSProperties = focalPoint
|
||||
? {
|
||||
objectFit: "cover",
|
||||
objectPosition: `${focalPoint.x}% ${focalPoint.y}%`,
|
||||
...style,
|
||||
}
|
||||
: { ...style }
|
||||
|
||||
return <NextImage {...props} style={styles} loader={imageLoader} />
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export default function ImageContainer({
|
||||
height={365}
|
||||
width={600}
|
||||
alt={leftImage.meta.alt || leftImage.title}
|
||||
focalPoint={leftImage.focalPoint}
|
||||
/>
|
||||
<Caption>{leftImage.meta.caption}</Caption>
|
||||
</article>
|
||||
@@ -28,6 +29,7 @@ export default function ImageContainer({
|
||||
height={365}
|
||||
width={600}
|
||||
alt={rightImage.meta.alt || rightImage.title}
|
||||
focalPoint={rightImage.focalPoint}
|
||||
/>
|
||||
<Caption>{leftImage.meta.caption}</Caption>
|
||||
</article>
|
||||
|
||||
@@ -396,6 +396,7 @@ export const renderOptions: RenderOptions = {
|
||||
height={365}
|
||||
src={image.url}
|
||||
width={width}
|
||||
focalPoint={image.focalPoint}
|
||||
{...props}
|
||||
/>
|
||||
<Caption>{image.meta.caption}</Caption>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import JsonToHtml from "@/components/JsonToHtml"
|
||||
|
||||
import ShortcutsList from "../Blocks/ShortcutsList"
|
||||
import Card from "../TempDesignSystem/Card"
|
||||
import TeaserCard from "../TempDesignSystem/TeaserCard"
|
||||
import JoinLoyaltyContact from "./JoinLoyalty"
|
||||
import MyPagesNavigation from "./MyPagesNavigation"
|
||||
|
||||
@@ -40,6 +43,35 @@ export default function Sidebar({ blocks }: SidebarProps) {
|
||||
key={`${block.typename}-${idx}`}
|
||||
/>
|
||||
)
|
||||
case SidebarEnums.blocks.ScriptedCard:
|
||||
return (
|
||||
<Card
|
||||
key={block.scripted_card.system.uid}
|
||||
heading={block.scripted_card.heading}
|
||||
secondaryButton={block.scripted_card.secondaryButton}
|
||||
primaryButton={block.scripted_card.primaryButton}
|
||||
bodyText={block.scripted_card.body_text}
|
||||
scriptedTopTitle={block.scripted_card.scripted_top_title}
|
||||
theme={block.scripted_card.theme ?? "image"}
|
||||
/>
|
||||
)
|
||||
case SidebarEnums.blocks.TeaserCard:
|
||||
return (
|
||||
<TeaserCard
|
||||
title={block.teaser_card.heading}
|
||||
description={block.teaser_card.body_text}
|
||||
style={block.teaser_card.theme}
|
||||
key={block.teaser_card.system.uid}
|
||||
primaryButton={block.teaser_card.primaryButton}
|
||||
secondaryButton={block.teaser_card.secondaryButton}
|
||||
sidePeekButton={block.teaser_card.sidePeekButton}
|
||||
sidePeekContent={block.teaser_card.sidePeekContent}
|
||||
image={block.teaser_card.image}
|
||||
/>
|
||||
)
|
||||
case SidebarEnums.blocks.QuickLinks:
|
||||
return <ShortcutsList {...block.shortcuts} hasTwoColumns={false} />
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
display: grid;
|
||||
container-name: sidebar;
|
||||
container-type: inline-size;
|
||||
gap: var(--Spacing-x3);
|
||||
|
||||
border-top: 1px solid var(--Base-Border-Subtle);
|
||||
padding-top: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -11,8 +15,10 @@
|
||||
@media screen and (min-width: 1367px) {
|
||||
.aside {
|
||||
align-content: flex-start;
|
||||
display: grid;
|
||||
gap: var(--Spacing-x4);
|
||||
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function CardImage({
|
||||
alt={backgroundImage.title}
|
||||
width={180}
|
||||
height={180}
|
||||
focalPoint={backgroundImage.focalPoint}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function Card({
|
||||
|
||||
imageWidth =
|
||||
imageWidth ||
|
||||
(backgroundImage && "dimensions" in backgroundImage
|
||||
(backgroundImage?.dimensions
|
||||
? backgroundImage.dimensions.aspectRatio * imageHeight
|
||||
: 420)
|
||||
|
||||
@@ -53,6 +53,7 @@ export default function Card({
|
||||
alt={backgroundImage.meta.alt || backgroundImage.title}
|
||||
width={imageWidth}
|
||||
height={imageHeight}
|
||||
focalPoint={backgroundImage.focalPoint}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function Card({
|
||||
const { register } = useFormContext()
|
||||
|
||||
return (
|
||||
<label className={styles.label} data-declined={declined}>
|
||||
<label className={styles.label} data-declined={declined} tabIndex={0}>
|
||||
<Caption className={styles.title} type="label" uppercase>
|
||||
{title}
|
||||
</Caption>
|
||||
|
||||
@@ -32,6 +32,7 @@ export default function LoyaltyCard({
|
||||
height={160}
|
||||
className={styles.image}
|
||||
alt={image.meta.alt || image.title}
|
||||
focalPoint={image.focalPoint}
|
||||
/>
|
||||
) : null}
|
||||
<Title as="h5" level="h3" textAlign="center">
|
||||
|
||||
@@ -35,6 +35,7 @@ export default function TeaserCard({
|
||||
className={styles.backgroundImage}
|
||||
width={399}
|
||||
height={201}
|
||||
focalPoint={image.focalPoint}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 399px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,15 @@ export enum PaymentMethodEnum {
|
||||
weChatPay = "weChatPay",
|
||||
payPal = "payPal",
|
||||
klarna = "klarna",
|
||||
americanExpress = "americanExpress",
|
||||
dankort = "dankort",
|
||||
dinersClub = "dinersClub",
|
||||
jcb = "jcb",
|
||||
masterCard = "masterCard",
|
||||
visa = "visa",
|
||||
maestro = "maestro",
|
||||
chinaUnionPay = "chinaUnionPay",
|
||||
discover = "discover",
|
||||
}
|
||||
|
||||
export const PAYMENT_METHOD_TITLES: Record<
|
||||
@@ -33,6 +42,15 @@ export const PAYMENT_METHOD_TITLES: Record<
|
||||
weChatPay: "WeChat Pay",
|
||||
payPal: "PayPal",
|
||||
klarna: "Klarna",
|
||||
americanExpress: "American Express",
|
||||
dankort: "Dankort",
|
||||
dinersClub: "Diners Club",
|
||||
jcb: "JCB",
|
||||
masterCard: "Mastercard",
|
||||
visa: "Visa",
|
||||
maestro: "Maestro",
|
||||
chinaUnionPay: "China UnionPay",
|
||||
discover: "Discover",
|
||||
}
|
||||
|
||||
export const PAYMENT_METHOD_ICONS: Record<
|
||||
@@ -49,4 +67,13 @@ export const PAYMENT_METHOD_ICONS: Record<
|
||||
weChatPay: "/_static/icons/payment/wechat-pay.svg",
|
||||
payPal: "/_static/icons/payment/paypal.svg",
|
||||
klarna: "/_static/icons/payment/klarna.svg",
|
||||
americanExpress: "/_static/icons/payment/american-express.svg",
|
||||
dankort: "/_static/icons/payment/dankort.svg",
|
||||
dinersClub: "/_static/icons/payment/diners-club.svg",
|
||||
jcb: "/_static/icons/payment/jcb.svg",
|
||||
masterCard: "/_static/icons/payment/mastercard.svg",
|
||||
visa: "/_static/icons/payment/visa.svg",
|
||||
maestro: "/_static/icons/payment/maestro.svg",
|
||||
chinaUnionPay: "/_static/icons/payment/china-union-pay.svg",
|
||||
discover: "/_static/icons/payment/discover.svg",
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"Add code": "Tilføj kode",
|
||||
"Add new card": "Tilføj nyt kort",
|
||||
"Address": "Adresse",
|
||||
"Airport": "Lufthavn",
|
||||
"Adults": "voksne",
|
||||
"Airport": "Lufthavn",
|
||||
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle vores morgenmadsbuffeter tilbyder glutenfrie, veganske og allergivenlige muligheder.",
|
||||
"Already a friend?": "Allerede en ven?",
|
||||
"Amenities": "Faciliteter",
|
||||
@@ -23,6 +23,7 @@
|
||||
"Approx.": "Ca.",
|
||||
"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": "pr. dags dato",
|
||||
"As our": "Som vores {level}",
|
||||
"As our Close Friend": "Som vores nære ven",
|
||||
"At latest": "Senest",
|
||||
@@ -35,6 +36,12 @@
|
||||
"Book": "Book",
|
||||
"Book reward night": "Book bonusnat",
|
||||
"Booking number": "Bookingnummer",
|
||||
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# børn}}",
|
||||
"booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}",
|
||||
"booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}",
|
||||
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
|
||||
"Breakfast": "Morgenmad",
|
||||
"Breakfast buffet": "Morgenbuffet",
|
||||
"Breakfast excluded": "Morgenmad ikke inkluderet",
|
||||
@@ -43,7 +50,9 @@
|
||||
"Breakfast selection in next step.": "Valg af morgenmad i næste trin.",
|
||||
"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.",
|
||||
@@ -101,9 +110,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",
|
||||
"First name": "Fornavn",
|
||||
@@ -117,7 +126,9 @@
|
||||
"Get member benefits & offers": "Få medlemsfordele og tilbud",
|
||||
"Go back to edit": "Gå tilbage til redigering",
|
||||
"Go back to overview": "Gå tilbage til oversigten",
|
||||
"guest": "gæst",
|
||||
"Guest information": "Gæsteinformation",
|
||||
"guests": "gæster",
|
||||
"Guests & Rooms": "Gæster & værelser",
|
||||
"Hi": "Hei",
|
||||
"Highest level": "Højeste niveau",
|
||||
@@ -125,6 +136,9 @@
|
||||
"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",
|
||||
@@ -135,9 +149,10 @@
|
||||
"In extra bed": "i ekstra seng",
|
||||
"Included": "Inkluderet",
|
||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke muligt at administrere dine kommunikationspræferencer lige nu, prøv venligst igen senere eller kontakt support, hvis problemet fortsætter.",
|
||||
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
|
||||
"Join at no cost": "Tilmeld dig uden omkostninger",
|
||||
"Join Scandic Friends": "Tilmeld dig Scandic Friends",
|
||||
"King bed": "Kingsize-seng",
|
||||
"km to city center": "km til byens centrum",
|
||||
"Language": "Sprog",
|
||||
"Last name": "Efternavn",
|
||||
"Latest searches": "Seneste søgninger",
|
||||
@@ -157,6 +172,7 @@
|
||||
"Log in here": "Log ind her",
|
||||
"Log in/Join": "Log på/Tilmeld dig",
|
||||
"Log out": "Log ud",
|
||||
"lowercase letter": "lille bogstav",
|
||||
"Main menu": "Hovedmenu",
|
||||
"Manage preferences": "Administrer præferencer",
|
||||
"Map": "Kort",
|
||||
@@ -166,9 +182,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",
|
||||
@@ -178,11 +194,16 @@
|
||||
"My pages": "Mine sider",
|
||||
"My pages menu": "Mine sider menu",
|
||||
"My payment cards": "Mine betalingskort",
|
||||
"MY SAVED CARDS": "MINE SAVEDE KORT",
|
||||
"My wishes": "Mine ønsker",
|
||||
"n/a": "n/a",
|
||||
"Nearby": "I nærheden",
|
||||
"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 breakfast": "Ingen morgenmad",
|
||||
"No content published": "Intet indhold offentliggjort",
|
||||
@@ -195,11 +216,14 @@
|
||||
"Nordic Swan Ecolabel": "Svanemærket",
|
||||
"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",
|
||||
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
|
||||
"Overview": "Oversigt",
|
||||
"Parking": "Parkering",
|
||||
"Parking / Garage": "Parkering / Garage",
|
||||
@@ -211,6 +235,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",
|
||||
@@ -233,9 +258,9 @@
|
||||
"Restaurant & Bar": "Restaurant & Bar",
|
||||
"Restaurants & Bars": "Restaurants & Bars",
|
||||
"Retype new password": "Gentag den nye adgangskode",
|
||||
"Room": "Værelse",
|
||||
"Room & Terms": "Værelse & Vilkår",
|
||||
"Room facilities": "Værelsesfaciliteter",
|
||||
"Room": "Værelse",
|
||||
"Rooms": "Værelser",
|
||||
"Rooms & Guests": "Værelser & gæster",
|
||||
"Sauna and gym": "Sauna and gym",
|
||||
@@ -272,27 +297,31 @@
|
||||
"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.",
|
||||
"Terms and conditions": "Vilkår og betingelser",
|
||||
"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}",
|
||||
"Total Points": "Samlet antal point",
|
||||
"to": "til",
|
||||
"Total incl VAT": "Inkl. moms",
|
||||
"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",
|
||||
"Use code/voucher": "Brug kode/voucher",
|
||||
"User information": "Brugeroplysninger",
|
||||
@@ -314,16 +343,16 @@
|
||||
"Where to": "Hvor",
|
||||
"Which room class suits you the best?": "Hvilken rumklasse passer bedst til dig",
|
||||
"Year": "År",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jeg accepterer vilkårene for Scandic Friends og forstår, at Scandic vil behandle mine personlige oplysninger i henhold til",
|
||||
"Yes, discard changes": "Ja, kasser ændringer",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jeg accepterer vilkårene for Scandic Friends og forstår, at Scandic vil behandle mine personlige oplysninger i henhold til",
|
||||
"Yes, remove my card": "Ja, fjern mit kort",
|
||||
"You can always change your mind later and add breakfast at the hotel.": "Du kan altid ombestemme dig senere og tilføje morgenmad på hotellet.",
|
||||
"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",
|
||||
@@ -333,33 +362,6 @@
|
||||
"Zoo": "Zoo",
|
||||
"Zoom in": "Zoom ind",
|
||||
"Zoom out": "Zoom ud",
|
||||
"as of today": "pr. dags dato",
|
||||
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# børn}}",
|
||||
"booking.guests": "Maks {nrOfGuests, plural, one {# gæst} other {# gæster}}",
|
||||
"booking.nights": "{totalNights, plural, one {# nat} other {# nætter}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# værelse} other {# værelser}}",
|
||||
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
|
||||
"by": "inden",
|
||||
"characters": "tegn",
|
||||
"guest": "gæst",
|
||||
"guests": "gæster",
|
||||
"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",
|
||||
"lowercase letter": "lille bogstav",
|
||||
"n/a": "n/a",
|
||||
"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",
|
||||
"{amount} {currency}": "{amount} {currency}",
|
||||
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
|
||||
"{width} cm × {length} cm": "{width} cm × {length} cm"
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"Add code": "Code hinzufügen",
|
||||
"Add new card": "Neue Karte hinzufügen",
|
||||
"Address": "Adresse",
|
||||
"Airport": "Flughafen",
|
||||
"Adults": "Erwachsene",
|
||||
"Airport": "Flughafen",
|
||||
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle unsere Frühstücksbuffets bieten glutenfreie, vegane und allergikerfreundliche Speisen.",
|
||||
"Already a friend?": "Sind wir schon Freunde?",
|
||||
"Amenities": "Annehmlichkeiten",
|
||||
@@ -23,6 +23,7 @@
|
||||
"Approx.": "Ca.",
|
||||
"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",
|
||||
@@ -35,6 +36,12 @@
|
||||
"Book": "Buchen",
|
||||
"Book reward night": "Bonusnacht buchen",
|
||||
"Booking number": "Buchungsnummer",
|
||||
"booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}",
|
||||
"booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}",
|
||||
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}",
|
||||
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
|
||||
"Breakfast": "Frühstück",
|
||||
"Breakfast buffet": "Frühstücksbuffet",
|
||||
"Breakfast excluded": "Frühstück nicht inbegriffen",
|
||||
@@ -43,7 +50,9 @@
|
||||
"Breakfast selection in next step.": "Frühstücksauswahl in nächsten Schritt.",
|
||||
"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.",
|
||||
@@ -101,9 +110,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",
|
||||
"First name": "Vorname",
|
||||
@@ -117,7 +126,9 @@
|
||||
"Get member benefits & offers": "Holen Sie sich Vorteile und Angebote für Mitglieder",
|
||||
"Go back to edit": "Zurück zum Bearbeiten",
|
||||
"Go back to overview": "Zurück zur Übersicht",
|
||||
"guest": "gast",
|
||||
"Guest information": "Informationen für Gäste",
|
||||
"guests": "gäste",
|
||||
"Guests & Rooms": "Gäste & Zimmer",
|
||||
"Hi": "Hallo",
|
||||
"Highest level": "Höchstes Level",
|
||||
@@ -125,6 +136,9 @@
|
||||
"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",
|
||||
@@ -135,9 +149,10 @@
|
||||
"In extra bed": "im zusätzlichen Bett",
|
||||
"Included": "Iinklusive",
|
||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Es ist derzeit nicht möglich, Ihre Kommunikationseinstellungen zu verwalten. Bitte versuchen Sie es später erneut oder wenden Sie sich an den Support, wenn das Problem weiterhin besteht.",
|
||||
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
|
||||
"Join at no cost": "Kostenlos beitreten",
|
||||
"Join Scandic Friends": "Treten Sie Scandic Friends bei",
|
||||
"King bed": "Kingsize-Bett",
|
||||
"km to city center": "km bis zum Stadtzentrum",
|
||||
"Language": "Sprache",
|
||||
"Last name": "Nachname",
|
||||
"Latest searches": "Letzte Suchanfragen",
|
||||
@@ -157,6 +172,7 @@
|
||||
"Log in here": "Hier einloggen",
|
||||
"Log in/Join": "Log in/Anmelden",
|
||||
"Log out": "Ausloggen",
|
||||
"lowercase letter": "Kleinbuchstabe",
|
||||
"Main menu": "Hauptmenü",
|
||||
"Manage preferences": "Verwalten von Voreinstellungen",
|
||||
"Map": "Karte",
|
||||
@@ -166,9 +182,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",
|
||||
@@ -178,11 +194,16 @@
|
||||
"My pages": "Meine Seiten",
|
||||
"My pages menu": "Meine Seite Menü",
|
||||
"My payment cards": "Meine Zahlungskarten",
|
||||
"MY SAVED CARDS": "MEINE SAVEDEN KARTEN",
|
||||
"My wishes": "Meine Wünsche",
|
||||
"n/a": "n/a",
|
||||
"Nearby": "In der Nähe",
|
||||
"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 breakfast": "Kein Frühstück",
|
||||
"No content published": "Kein Inhalt veröffentlicht",
|
||||
@@ -195,11 +216,14 @@
|
||||
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
|
||||
"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",
|
||||
"OTHER PAYMENT METHODS": "ANDERE BEZAHLMETHODE",
|
||||
"Overview": "Übersicht",
|
||||
"Parking": "Parken",
|
||||
"Parking / Garage": "Parken / Garage",
|
||||
@@ -211,6 +235,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",
|
||||
@@ -233,10 +258,10 @@
|
||||
"Restaurant & Bar": "Restaurant & Bar",
|
||||
"Restaurants & Bars": "Restaurants & Bars",
|
||||
"Retype new password": "Neues Passwort erneut eingeben",
|
||||
"Room": "Zimmer",
|
||||
"Room & Terms": "Zimmer & Bedingungen",
|
||||
"Room facilities": "Zimmerausstattung",
|
||||
"Rooms": "Räume",
|
||||
"Room": "Zimmer",
|
||||
"Rooms & Guests": "Zimmer & Gäste",
|
||||
"Sauna and gym": "Sauna and gym",
|
||||
"Save": "Speichern",
|
||||
@@ -272,27 +297,31 @@
|
||||
"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.",
|
||||
"Terms and conditions": "Geschäftsbedingungen",
|
||||
"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}",
|
||||
"Total Points": "Gesamtpunktzahl",
|
||||
"to": "zu",
|
||||
"Total incl VAT": "Gesamt inkl. MwSt.",
|
||||
"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",
|
||||
"Use code/voucher": "Code/Gutschein nutzen",
|
||||
"User information": "Nutzerinformation",
|
||||
@@ -314,16 +343,16 @@
|
||||
"Where to": "Wohin",
|
||||
"Which room class suits you the best?": "Welche Zimmerklasse passt am besten zu Ihnen?",
|
||||
"Year": "Jahr",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, ich akzeptiere die Geschäftsbedingungen für Scandic Friends und erkenne an, dass Scandic meine persönlichen Daten in Übereinstimmung mit",
|
||||
"Yes, discard changes": "Ja, Änderungen verwerfen",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, ich akzeptiere die Geschäftsbedingungen für Scandic Friends und erkenne an, dass Scandic meine persönlichen Daten in Übereinstimmung mit",
|
||||
"Yes, remove my card": "Ja, meine Karte entfernen",
|
||||
"You can always change your mind later and add breakfast at the hotel.": "Sie können es sich später jederzeit anders überlegen und das Frühstück im Hotel hinzufügen.",
|
||||
"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",
|
||||
@@ -333,33 +362,6 @@
|
||||
"Zoo": "Zoo",
|
||||
"Zoom in": "Vergrößern",
|
||||
"Zoom out": "Verkleinern",
|
||||
"as of today": "Stand heute",
|
||||
"booking.adults": "{totalAdults, plural, one {# erwachsene} other {# erwachsene}}",
|
||||
"booking.children": "{totalChildren, plural, one {# kind} other {# kinder}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# gast} other {# gäste}}",
|
||||
"booking.nights": "{totalNights, plural, one {# nacht} other {# Nächte}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# zimmer} other {# räume}}",
|
||||
"booking.terms": "Ved at betale med en af de tilgængelige betalingsmetoder, accepterer jeg vilkårene for denne booking og de generelle <termsLink>Vilkår og betingelser</termsLink>, og forstår, at Scandic vil behandle min personlige data i forbindelse med denne booking i henhold til <privacyLink>Scandics Privatlivspolitik</privacyLink>. Jeg accepterer, at Scandic kræver et gyldigt kreditkort under min besøg i tilfælde af, at noget er tilbagebetalt.",
|
||||
"by": "bis",
|
||||
"characters": "figuren",
|
||||
"guest": "gast",
|
||||
"guests": "gäste",
|
||||
"hotelPages.rooms.roomCard.person": "person",
|
||||
"hotelPages.rooms.roomCard.persons": "personen",
|
||||
"hotelPages.rooms.roomCard.seeRoomDetails": "Zimmerdetails ansehen",
|
||||
"km to city center": "km bis zum Stadtzentrum",
|
||||
"lowercase letter": "Kleinbuchstabe",
|
||||
"n/a": "n/a",
|
||||
"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",
|
||||
"{amount} {currency}": "{amount} {currency}",
|
||||
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
|
||||
"{width} cm × {length} cm": "{width} cm × {length} cm"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"Approx.": "Approx.",
|
||||
"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",
|
||||
@@ -38,6 +39,12 @@
|
||||
"Book": "Book",
|
||||
"Book reward night": "Book reward night",
|
||||
"Booking number": "Booking number",
|
||||
"booking.adults": "{totalAdults, plural, one {# adult} other {# adults}}",
|
||||
"booking.children": "{totalChildren, plural, one {# child} other {# children}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# guest} other {# guests}}",
|
||||
"booking.nights": "{totalNights, plural, one {# night} other {# nights}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}",
|
||||
"booking.terms": "By paying with any of the payment methods available, I accept the terms for this booking and the general <termsLink>Terms & Conditions</termsLink>, and understand that Scandic will process my personal data for this booking in accordance with <privacyLink>Scandic's Privacy policy</privacyLink>. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.",
|
||||
"Breakfast": "Breakfast",
|
||||
"Breakfast buffet": "Breakfast buffet",
|
||||
"Breakfast excluded": "Breakfast excluded",
|
||||
@@ -46,7 +53,9 @@
|
||||
"Breakfast selection in next step.": "Breakfast selection in next step.",
|
||||
"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.",
|
||||
@@ -105,9 +114,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",
|
||||
"First name": "First name",
|
||||
@@ -121,7 +130,9 @@
|
||||
"Get member benefits & offers": "Get member benefits & offers",
|
||||
"Go back to edit": "Go back to edit",
|
||||
"Go back to overview": "Go back to overview",
|
||||
"guest": "guest",
|
||||
"Guest information": "Guest information",
|
||||
"guests": "guests",
|
||||
"Guests & Rooms": "Guests & Rooms",
|
||||
"Hi": "Hi",
|
||||
"Highest level": "Highest level",
|
||||
@@ -129,6 +140,9 @@
|
||||
"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",
|
||||
@@ -139,9 +153,10 @@
|
||||
"In extra bed": "In extra bed",
|
||||
"Included": "Included",
|
||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.",
|
||||
"Join Scandic Friends": "Join Scandic Friends",
|
||||
"Join at no cost": "Join at no cost",
|
||||
"Join Scandic Friends": "Join Scandic Friends",
|
||||
"King bed": "King bed",
|
||||
"km to city center": "km to city center",
|
||||
"Language": "Language",
|
||||
"Last name": "Last name",
|
||||
"Latest searches": "Latest searches",
|
||||
@@ -161,6 +176,7 @@
|
||||
"Log in here": "Log in here",
|
||||
"Log in/Join": "Log in/Join",
|
||||
"Log out": "Log out",
|
||||
"lowercase letter": "lowercase letter",
|
||||
"Main menu": "Main menu",
|
||||
"Manage preferences": "Manage preferences",
|
||||
"Map": "Map",
|
||||
@@ -170,9 +186,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",
|
||||
@@ -182,11 +198,16 @@
|
||||
"My pages": "My pages",
|
||||
"My pages menu": "My pages menu",
|
||||
"My payment cards": "My payment cards",
|
||||
"MY SAVED CARDS": "MY SAVED CARDS",
|
||||
"My wishes": "My wishes",
|
||||
"n/a": "n/a",
|
||||
"Nearby": "Nearby",
|
||||
"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 breakfast": "No breakfast",
|
||||
"No content published": "No content published",
|
||||
@@ -199,11 +220,14 @@
|
||||
"Nordic Swan Ecolabel": "Nordic Swan Ecolabel",
|
||||
"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",
|
||||
"OTHER PAYMENT METHODS": "OTHER PAYMENT METHODS",
|
||||
"Overview": "Overview",
|
||||
"Parking": "Parking",
|
||||
"Parking / Garage": "Parking / Garage",
|
||||
@@ -215,6 +239,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",
|
||||
@@ -276,27 +301,31 @@
|
||||
"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.",
|
||||
"Terms and conditions": "Terms and conditions",
|
||||
"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}",
|
||||
"Total Points": "Total Points",
|
||||
"to": "to",
|
||||
"Total incl VAT": "Total incl VAT",
|
||||
"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",
|
||||
"Use code/voucher": "Use code/voucher",
|
||||
"User information": "User information",
|
||||
@@ -318,16 +347,16 @@
|
||||
"Where to": "Where to",
|
||||
"Which room class suits you the best?": "Which room class suits you the best?",
|
||||
"Year": "Year",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with",
|
||||
"Yes, discard changes": "Yes, discard changes",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with",
|
||||
"Yes, remove my card": "Yes, remove my card",
|
||||
"You can always change your mind later and add breakfast at the hotel.": "You can always change your mind later and add breakfast at the hotel.",
|
||||
"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",
|
||||
@@ -337,33 +366,6 @@
|
||||
"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.children": "{totalChildren, plural, one {# child} other {# children}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# guest} other {# guests}}",
|
||||
"booking.nights": "{totalNights, plural, one {# night} other {# nights}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# room} other {# rooms}}",
|
||||
"booking.terms": "By paying with any of the payment methods available, I accept the terms for this booking and the general <termsLink>Terms & Conditions</termsLink>, and understand that Scandic will process my personal data for this booking in accordance with <privacyLink>Scandic's Privacy policy</privacyLink>. I also accept that Scandic require a valid credit card during my visit in case anything is left unpaid.",
|
||||
"by": "by",
|
||||
"characters": "characters",
|
||||
"guest": "guest",
|
||||
"guests": "guests",
|
||||
"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",
|
||||
"lowercase letter": "lowercase letter",
|
||||
"n/a": "n/a",
|
||||
"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",
|
||||
"{amount} {currency}": "{amount} {currency}",
|
||||
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
|
||||
"{width} cm × {length} cm": "{width} cm × {length} cm"
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"Add code": "Lisää koodi",
|
||||
"Add new card": "Lisää uusi kortti",
|
||||
"Address": "Osoite",
|
||||
"Airport": "Lentokenttä",
|
||||
"Adults": "Aikuista",
|
||||
"Airport": "Lentokenttä",
|
||||
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Kaikki aamiaisbuffettimme tarjoavat gluteenittomia, vegaanisia ja allergiaystävällisiä vaihtoehtoja.",
|
||||
"Already a friend?": "Oletko jo ystävä?",
|
||||
"Amenities": "Mukavuudet",
|
||||
@@ -23,6 +23,7 @@
|
||||
"Approx.": "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",
|
||||
@@ -35,6 +36,12 @@
|
||||
"Book": "Varaa",
|
||||
"Book reward night": "Kirjapalkinto-ilta",
|
||||
"Booking number": "Varausnumero",
|
||||
"booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}",
|
||||
"booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}",
|
||||
"booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}",
|
||||
"booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset <termsLink>ehdot ja ehtoja</termsLink>, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti <privacyLink>Scandicin tietosuojavaltuuden</privacyLink> mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.",
|
||||
"Breakfast": "Aamiainen",
|
||||
"Breakfast buffet": "Aamiaisbuffet",
|
||||
"Breakfast excluded": "Aamiainen ei sisälly",
|
||||
@@ -43,7 +50,9 @@
|
||||
"Breakfast selection in next step.": "Aamiaisvalinta seuraavassa vaiheessa.",
|
||||
"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.",
|
||||
@@ -101,9 +110,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",
|
||||
"First name": "Etunimi",
|
||||
@@ -117,7 +126,9 @@
|
||||
"Get member benefits & offers": "Hanki jäsenetuja ja -tarjouksia",
|
||||
"Go back to edit": "Palaa muokkaamaan",
|
||||
"Go back to overview": "Palaa yleiskatsaukseen",
|
||||
"guest": "Vieras",
|
||||
"Guest information": "Vieraan tiedot",
|
||||
"guests": "Vieraita",
|
||||
"Guests & Rooms": "Vieraat & Huoneet",
|
||||
"Hi": "Hi",
|
||||
"Highest level": "Korkein taso",
|
||||
@@ -125,6 +136,9 @@
|
||||
"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",
|
||||
@@ -135,9 +149,10 @@
|
||||
"In extra bed": "Oma vuodepaikka",
|
||||
"Included": "Sisälly hintaan",
|
||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Viestintäasetuksiasi ei voi hallita juuri nyt. Yritä myöhemmin uudelleen tai ota yhteyttä tukeen, jos ongelma jatkuu.",
|
||||
"Join Scandic Friends": "Liity jäseneksi",
|
||||
"Join at no cost": "Liity maksutta",
|
||||
"Join Scandic Friends": "Liity jäseneksi",
|
||||
"King bed": "King-vuode",
|
||||
"km to city center": "km keskustaan",
|
||||
"Language": "Kieli",
|
||||
"Last name": "Sukunimi",
|
||||
"Latest searches": "Viimeisimmät haut",
|
||||
@@ -157,6 +172,7 @@
|
||||
"Log in here": "Kirjaudu sisään",
|
||||
"Log in/Join": "Kirjaudu sisään/Liittyä",
|
||||
"Log out": "Kirjaudu ulos",
|
||||
"lowercase letter": "pien kirjain",
|
||||
"Main menu": "Päävalikko",
|
||||
"Manage preferences": "Asetusten hallinta",
|
||||
"Map": "Kartta",
|
||||
@@ -166,9 +182,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",
|
||||
@@ -178,11 +194,16 @@
|
||||
"My pages": "Omat sivut",
|
||||
"My pages menu": "Omat sivut -valikko",
|
||||
"My payment cards": "Minun maksukortit",
|
||||
"MY SAVED CARDS": "MINUN SAVED CARDS",
|
||||
"My wishes": "Toiveeni",
|
||||
"n/a": "n/a",
|
||||
"Nearby": "Lähistöllä",
|
||||
"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 breakfast": "Ei aamiaista",
|
||||
"No content published": "Ei julkaistua sisältöä",
|
||||
@@ -195,11 +216,14 @@
|
||||
"Nordic Swan Ecolabel": "Ympäristömerkki Miljömärkt",
|
||||
"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",
|
||||
"OTHER PAYMENT METHODS": "MUISE KORT",
|
||||
"Overview": "Yleiskatsaus",
|
||||
"Parking": "Pysäköinti",
|
||||
"Parking / Garage": "Pysäköinti / Autotalli",
|
||||
@@ -211,6 +235,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",
|
||||
@@ -233,9 +258,9 @@
|
||||
"Restaurant & Bar": "Ravintola & Baari",
|
||||
"Restaurants & Bars": "Restaurants & Bars",
|
||||
"Retype new password": "Kirjoita uusi salasana uudelleen",
|
||||
"Room": "Huone",
|
||||
"Room & Terms": "Huone & Ehdot",
|
||||
"Room facilities": "Huoneen varustelu",
|
||||
"Room": "Huone",
|
||||
"Rooms": "Huoneet",
|
||||
"Rooms & Guests": "Huoneet & Vieraat",
|
||||
"Rooms & Guestss": "Huoneet & Vieraat",
|
||||
@@ -273,27 +298,31 @@
|
||||
"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ä.",
|
||||
"Terms and conditions": "Käyttöehdot",
|
||||
"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}",
|
||||
"Total Points": "Kokonaispisteet",
|
||||
"to": "to",
|
||||
"Total incl VAT": "Yhteensä sis. alv",
|
||||
"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ä",
|
||||
"Use code/voucher": "Käytä koodia/voucheria",
|
||||
"User information": "Käyttäjän tiedot",
|
||||
@@ -315,16 +344,16 @@
|
||||
"Where to": "Minne",
|
||||
"Which room class suits you the best?": "Mikä huoneluokka sopii sinulle parhaiten?",
|
||||
"Year": "Vuosi",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Kyllä, hyväksyn Scandic Friends -käyttöehdot ja ymmärrän, että Scandic käsittelee minun henkilötietoni asianmukaisesti",
|
||||
"Yes, discard changes": "Kyllä, hylkää muutokset",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Kyllä, hyväksyn Scandic Friends -käyttöehdot ja ymmärrän, että Scandic käsittelee minun henkilötietoni asianmukaisesti",
|
||||
"Yes, remove my card": "Kyllä, poista korttini",
|
||||
"You can always change your mind later and add breakfast at the hotel.": "Voit aina muuttaa mieltäsi myöhemmin ja lisätä aamiaisen hotelliin.",
|
||||
"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",
|
||||
@@ -334,33 +363,6 @@
|
||||
"Zoo": "Eläintarha",
|
||||
"Zoom in": "Lähennä",
|
||||
"Zoom out": "Loitonna",
|
||||
"as of today": "tänään",
|
||||
"booking.adults": "{totalAdults, plural, one {# aikuinen} other {# aikuiset}}",
|
||||
"booking.children": "{totalChildren, plural, one {# lapsi} other {# lasta}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# vieras} other {# vieraita}}",
|
||||
"booking.nights": "{totalNights, plural, one {# yö} other {# yötä}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# huone} other {# sviitti}}",
|
||||
"booking.terms": "Maksamalla minkä tahansa saatavilla olevan maksutavan avulla hyväksyn tämän varauksen ehdot ja yleiset <termsLink>ehdot ja ehtoja</termsLink>, ja ymmärrän, että Scandic käsittelee minun henkilötietoni tässä varauksessa mukaisesti <privacyLink>Scandicin tietosuojavaltuuden</privacyLink> mukaisesti. Hyväksyn myös, että Scandic vaatii validin luottokortin majoituksen ajan, jos jokin jää maksamatta.",
|
||||
"by": "mennessä",
|
||||
"characters": "hahmoja",
|
||||
"guest": "Vieras",
|
||||
"guests": "Vieraita",
|
||||
"hotelPages.rooms.roomCard.person": "henkilö",
|
||||
"hotelPages.rooms.roomCard.persons": "Henkilöä",
|
||||
"hotelPages.rooms.roomCard.seeRoomDetails": "Katso huoneen tiedot",
|
||||
"km to city center": "km keskustaan",
|
||||
"lowercase letter": "pien kirjain",
|
||||
"n/a": "n/a",
|
||||
"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",
|
||||
"{amount} {currency}": "{amount} {currency}",
|
||||
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
|
||||
"{width} cm × {length} cm": "{width} cm × {length} cm"
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"Add code": "Legg til kode",
|
||||
"Add new card": "Legg til nytt kort",
|
||||
"Address": "Adresse",
|
||||
"Airport": "Flyplass",
|
||||
"Adults": "Voksne",
|
||||
"Airport": "Flyplass",
|
||||
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alle våre frokostbufféer tilbyr glutenfrie, veganske og allergivennlige alternativer.",
|
||||
"Already a friend?": "Allerede Friend?",
|
||||
"Amenities": "Fasiliteter",
|
||||
@@ -23,6 +23,7 @@
|
||||
"Approx.": "Ca.",
|
||||
"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 i dag",
|
||||
"As our": "Som vår {level}",
|
||||
"As our Close Friend": "Som vår nære venn",
|
||||
"At latest": "Senest",
|
||||
@@ -35,6 +36,11 @@
|
||||
"Book": "Bestill",
|
||||
"Book reward night": "Bestill belønningskveld",
|
||||
"Booking number": "Bestillingsnummer",
|
||||
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
|
||||
"booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}",
|
||||
"booking.nights": "{totalNights, plural, one {# natt} other {# netter}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}",
|
||||
"Breakfast": "Frokost",
|
||||
"Breakfast buffet": "Breakfast buffet",
|
||||
"Breakfast excluded": "Frokost ekskludert",
|
||||
@@ -43,7 +49,9 @@
|
||||
"Breakfast selection in next step.": "Frokostvalg i neste steg.",
|
||||
"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.",
|
||||
@@ -100,9 +108,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",
|
||||
"First name": "Fornavn",
|
||||
@@ -116,7 +124,9 @@
|
||||
"Get member benefits & offers": "Få medlemsfordeler og tilbud",
|
||||
"Go back to edit": "Gå tilbake til redigering",
|
||||
"Go back to overview": "Gå tilbake til oversikten",
|
||||
"guest": "gjest",
|
||||
"Guest information": "Informasjon til gjester",
|
||||
"guests": "gjester",
|
||||
"Guests & Rooms": "Gjester & rom",
|
||||
"Hi": "Hei",
|
||||
"Highest level": "Høyeste nivå",
|
||||
@@ -124,6 +134,9 @@
|
||||
"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",
|
||||
@@ -133,9 +146,10 @@
|
||||
"In extra bed": "i ekstraseng",
|
||||
"Included": "Inkludert",
|
||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det er ikke mulig å administrere kommunikasjonspreferansene dine akkurat nå, prøv igjen senere eller kontakt support hvis problemet vedvarer.",
|
||||
"Join Scandic Friends": "Bli med i Scandic Friends",
|
||||
"Join at no cost": "Bli med uten kostnad",
|
||||
"Join Scandic Friends": "Bli med i Scandic Friends",
|
||||
"King bed": "King-size-seng",
|
||||
"km to city center": "km til sentrum",
|
||||
"Language": "Språk",
|
||||
"Last name": "Etternavn",
|
||||
"Latest searches": "Siste søk",
|
||||
@@ -155,6 +169,7 @@
|
||||
"Log in here": "Logg inn her",
|
||||
"Log in/Join": "Logg på/Bli med",
|
||||
"Log out": "Logg ut",
|
||||
"lowercase letter": "liten bokstav",
|
||||
"Main menu": "Hovedmeny",
|
||||
"Manage preferences": "Administrer preferanser",
|
||||
"Map": "Kart",
|
||||
@@ -164,9 +179,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",
|
||||
@@ -176,11 +191,16 @@
|
||||
"My pages": "Mine sider",
|
||||
"My pages menu": "Mine sider-menyen",
|
||||
"My payment cards": "Mine betalingskort",
|
||||
"MY SAVED CARDS": "MINE SAVEDE KORT",
|
||||
"My wishes": "Mine ønsker",
|
||||
"n/a": "n/a",
|
||||
"Nearby": "I nærheten",
|
||||
"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 breakfast": "Ingen frokost",
|
||||
"No content published": "Ingen innhold publisert",
|
||||
@@ -193,11 +213,14 @@
|
||||
"Nordic Swan Ecolabel": "Svanemerket",
|
||||
"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",
|
||||
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
|
||||
"Overview": "Oversikt",
|
||||
"Parking": "Parkering",
|
||||
"Parking / Garage": "Parkering / Garasje",
|
||||
@@ -209,6 +232,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",
|
||||
@@ -231,9 +255,9 @@
|
||||
"Restaurant & Bar": "Restaurant & Bar",
|
||||
"Restaurants & Bars": "Restaurants & Bars",
|
||||
"Retype new password": "Skriv inn nytt passord på nytt",
|
||||
"Room": "Rom",
|
||||
"Room & Terms": "Rom & Vilkår",
|
||||
"Room facilities": "Romfasiliteter",
|
||||
"Room": "Rom",
|
||||
"Rooms": "Rom",
|
||||
"Rooms & Guests": "Rom og gjester",
|
||||
"Sauna and gym": "Sauna and gym",
|
||||
@@ -270,27 +294,31 @@
|
||||
"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.",
|
||||
"Terms and conditions": "Vilkår og betingelser",
|
||||
"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}",
|
||||
"Total Points": "Totale poeng",
|
||||
"to": "til",
|
||||
"Total incl VAT": "Sum inkl mva",
|
||||
"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",
|
||||
"Use code/voucher": "Bruk kode/voucher",
|
||||
"User information": "Brukerinformasjon",
|
||||
@@ -312,16 +340,16 @@
|
||||
"Where to": "Hvor skal du",
|
||||
"Which room class suits you the best?": "Hvilken romklasse passer deg best?",
|
||||
"Year": "År",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jeg aksepterer vilkårene for Scandic Friends og forstår at Scandic vil behandle mine personlige opplysninger i henhold til",
|
||||
"Yes, discard changes": "Ja, forkast endringer",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jeg aksepterer vilkårene for Scandic Friends og forstår at Scandic vil behandle mine personlige opplysninger i henhold til",
|
||||
"Yes, remove my card": "Ja, fjern kortet mitt",
|
||||
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ombestemme deg senere og legge til frokost på hotellet.",
|
||||
"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å",
|
||||
@@ -331,32 +359,6 @@
|
||||
"Zoo": "Dyrehage",
|
||||
"Zoom in": "Zoom inn",
|
||||
"Zoom out": "Zoom ut",
|
||||
"as of today": "per i dag",
|
||||
"booking.adults": "{totalAdults, plural, one {# voksen} other {# voksne}}",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
|
||||
"booking.guests": "Maks {nrOfGuests, plural, one {# gjest} other {# gjester}}",
|
||||
"booking.nights": "{totalNights, plural, one {# natt} other {# netter}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# rom} other {# rom}}",
|
||||
"by": "innen",
|
||||
"characters": "tegn",
|
||||
"guest": "gjest",
|
||||
"guests": "gjester",
|
||||
"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",
|
||||
"lowercase letter": "liten bokstav",
|
||||
"n/a": "n/a",
|
||||
"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",
|
||||
"{amount} {currency}": "{amount} {currency}",
|
||||
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
|
||||
"{width} cm × {length} cm": "{width} cm × {length} cm"
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"Add code": "Lägg till kod",
|
||||
"Add new card": "Lägg till nytt kort",
|
||||
"Address": "Adress",
|
||||
"Airport": "Flygplats",
|
||||
"Adults": "Vuxna",
|
||||
"Airport": "Flygplats",
|
||||
"All our breakfast buffets offer gluten free, vegan, and allergy-friendly options.": "Alla våra frukostbufféer erbjuder glutenfria, veganska och allergivänliga alternativ.",
|
||||
"Already a friend?": "Är du redan en vän?",
|
||||
"Amenities": "Bekvämligheter",
|
||||
@@ -23,6 +23,7 @@
|
||||
"Approx.": "Ca.",
|
||||
"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",
|
||||
@@ -35,6 +36,12 @@
|
||||
"Book": "Boka",
|
||||
"Book reward night": "Boka frinatt",
|
||||
"Booking number": "Bokningsnummer",
|
||||
"booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}",
|
||||
"booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# rum} other {# rum}}",
|
||||
"booking.terms": "Genom att betala med någon av de tillgängliga betalningsmetoderna accepterar jag villkoren för denna bokning och de generella <termsLink>Villkoren och villkoren</termsLink>, och förstår att Scandic kommer att behandla min personliga data i samband med denna bokning i enlighet med <privacyLink>Scandics integritetspolicy</privacyLink>. Jag accepterar att Scandic kräver ett giltigt kreditkort under min besök i fall att något är tillbaka betalt.",
|
||||
"Breakfast": "Frukost",
|
||||
"Breakfast buffet": "Frukostbuffé",
|
||||
"Breakfast excluded": "Frukost ingår ej",
|
||||
@@ -43,7 +50,9 @@
|
||||
"Breakfast selection in next step.": "Frukostval i nästa steg.",
|
||||
"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.",
|
||||
@@ -100,9 +109,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",
|
||||
"First name": "Förnamn",
|
||||
@@ -116,7 +125,9 @@
|
||||
"Get member benefits & offers": "Ta del av medlemsförmåner och erbjudanden",
|
||||
"Go back to edit": "Gå tillbaka till redigeringen",
|
||||
"Go back to overview": "Gå tillbaka till översikten",
|
||||
"guest": "gäst",
|
||||
"Guest information": "Information till gästerna",
|
||||
"guests": "gäster",
|
||||
"Guests & Rooms": "Gäster & rum",
|
||||
"Hi": "Hej",
|
||||
"Highest level": "Högsta nivå",
|
||||
@@ -124,6 +135,9 @@
|
||||
"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",
|
||||
@@ -133,9 +147,10 @@
|
||||
"In extra bed": "Egen sängplats",
|
||||
"Included": "Inkluderad",
|
||||
"It is not posible to manage your communication preferences right now, please try again later or contact support if the problem persists.": "Det gick inte att hantera dina kommunikationsinställningar just nu, försök igen senare eller kontakta supporten om problemet kvarstår.",
|
||||
"Join Scandic Friends": "Gå med i Scandic Friends",
|
||||
"Join at no cost": "Gå med utan kostnad",
|
||||
"Join Scandic Friends": "Gå med i Scandic Friends",
|
||||
"King bed": "King size-säng",
|
||||
"km to city center": "km till stadens centrum",
|
||||
"Language": "Språk",
|
||||
"Last name": "Efternamn",
|
||||
"Latest searches": "Senaste sökningarna",
|
||||
@@ -155,6 +170,7 @@
|
||||
"Log in here": "Logga in här",
|
||||
"Log in/Join": "Logga in/Gå med",
|
||||
"Log out": "Logga ut",
|
||||
"lowercase letter": "liten bokstav",
|
||||
"Main menu": "Huvudmeny",
|
||||
"Manage preferences": "Hantera inställningar",
|
||||
"Map": "Karta",
|
||||
@@ -164,9 +180,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",
|
||||
@@ -176,11 +192,16 @@
|
||||
"My pages": "Mina sidor",
|
||||
"My pages menu": "Mina sidor meny",
|
||||
"My payment cards": "Mina betalningskort",
|
||||
"MY SAVED CARDS": "MINE SAVEDE KORT",
|
||||
"My wishes": "Mina önskningar",
|
||||
"n/a": "n/a",
|
||||
"Nearby": "I närheten",
|
||||
"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 breakfast": "Ingen frukost",
|
||||
"No content published": "Inget innehåll publicerat",
|
||||
@@ -193,11 +214,14 @@
|
||||
"Nordic Swan Ecolabel": "Svanenmärkt",
|
||||
"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",
|
||||
"OTHER PAYMENT METHODS": "ANDRE BETALINGSMETODER",
|
||||
"Overview": "Översikt",
|
||||
"Parking": "Parkering",
|
||||
"Parking / Garage": "Parkering / Garage",
|
||||
@@ -209,6 +233,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",
|
||||
@@ -231,9 +256,9 @@
|
||||
"Restaurant & Bar": "Restaurang & Bar",
|
||||
"Restaurants & Bars": "Restaurants & Bars",
|
||||
"Retype new password": "Upprepa nytt lösenord",
|
||||
"Room": "Rum",
|
||||
"Room & Terms": "Rum & Villkor",
|
||||
"Room facilities": "Rumfaciliteter",
|
||||
"Room": "Rum",
|
||||
"Rooms": "Rum",
|
||||
"Rooms & Guests": "Rum och gäster",
|
||||
"Sauna and gym": "Sauna and gym",
|
||||
@@ -270,27 +295,31 @@
|
||||
"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.",
|
||||
"Terms and conditions": "Allmänna villkor",
|
||||
"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}",
|
||||
"Total Points": "Poäng totalt",
|
||||
"to": "till",
|
||||
"Total incl VAT": "Totalt inkl moms",
|
||||
"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": "Använd bonuscheck",
|
||||
"Use code/voucher": "Använd kod/voucher",
|
||||
"User information": "Användarinformation",
|
||||
@@ -312,16 +341,16 @@
|
||||
"Where to": "Vart",
|
||||
"Which room class suits you the best?": "Vilken rumsklass passar dig bäst?",
|
||||
"Year": "År",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jag accepterar villkoren för Scandic Friends och förstår att Scandic kommer att bearbeta mina personliga uppgifter i enlighet med",
|
||||
"Yes, discard changes": "Ja, ignorera ändringar",
|
||||
"Yes, I accept the Terms and conditions for Scandic Friends and understand that Scandic will process my personal data in accordance with": "Ja, jag accepterar villkoren för Scandic Friends och förstår att Scandic kommer att bearbeta mina personliga uppgifter i enlighet med",
|
||||
"Yes, remove my card": "Ja, ta bort mitt kort",
|
||||
"You can always change your mind later and add breakfast at the hotel.": "Du kan alltid ändra dig senare och lägga till frukost på hotellet.",
|
||||
"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å",
|
||||
@@ -331,33 +360,6 @@
|
||||
"Zoo": "Djurpark",
|
||||
"Zoom in": "Zooma in",
|
||||
"Zoom out": "Zooma ut",
|
||||
"as of today": "från och med idag",
|
||||
"booking.adults": "{totalAdults, plural, one {# vuxen} other {# vuxna}}",
|
||||
"booking.children": "{totalChildren, plural, one {# barn} other {# barn}}",
|
||||
"booking.guests": "Max {nrOfGuests, plural, one {# gäst} other {# gäster}}",
|
||||
"booking.nights": "{totalNights, plural, one {# natt} other {# nätter}}",
|
||||
"booking.rooms": "{totalRooms, plural, one {# rum} other {# rum}}",
|
||||
"booking.terms": "Genom att betala med någon av de tillgängliga betalningsmetoderna accepterar jag villkoren för denna bokning och de generella <termsLink>Villkoren och villkoren</termsLink>, och förstår att Scandic kommer att behandla min personliga data i samband med denna bokning i enlighet med <privacyLink>Scandics integritetspolicy</privacyLink>. Jag accepterar att Scandic kräver ett giltigt kreditkort under min besök i fall att något är tillbaka betalt.",
|
||||
"by": "innan",
|
||||
"characters": "tecken",
|
||||
"guest": "gäst",
|
||||
"guests": "gäster",
|
||||
"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",
|
||||
"lowercase letter": "liten bokstav",
|
||||
"n/a": "n/a",
|
||||
"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",
|
||||
"{amount} {currency}": "{amount} {currency}",
|
||||
"{difference}{amount} {currency}": "{difference}{amount} {currency}",
|
||||
"{width} cm × {length} cm": "{width} cm × {length} cm"
|
||||
|
||||
88
lib/graphql/Fragments/Alert.graphql
Normal file
88
lib/graphql/Fragments/Alert.graphql
Normal file
@@ -0,0 +1,88 @@
|
||||
#import "./PageLink/AccountPageLink.graphql"
|
||||
#import "./PageLink/ContentPageLink.graphql"
|
||||
#import "./PageLink/HotelPageLink.graphql"
|
||||
#import "./PageLink/LoyaltyPageLink.graphql"
|
||||
|
||||
#import "./AccountPage/Ref.graphql"
|
||||
#import "./ContentPage/Ref.graphql"
|
||||
#import "./HotelPage/Ref.graphql"
|
||||
#import "./LoyaltyPage/Ref.graphql"
|
||||
|
||||
fragment Alert on Alert {
|
||||
type
|
||||
heading
|
||||
text
|
||||
phone_contact {
|
||||
display_text
|
||||
phone_number
|
||||
footnote
|
||||
}
|
||||
has_link
|
||||
link {
|
||||
title
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageLink
|
||||
...ContentPageLink
|
||||
...HotelPageLink
|
||||
...LoyaltyPageLink
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
has_sidepeek_button
|
||||
sidepeek_button {
|
||||
cta_text
|
||||
}
|
||||
sidepeek_content {
|
||||
heading
|
||||
content {
|
||||
embedded_itemsConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageLink
|
||||
...ContentPageLink
|
||||
...HotelPageLink
|
||||
...LoyaltyPageLink
|
||||
}
|
||||
}
|
||||
}
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment AlertRef on Alert {
|
||||
link {
|
||||
linkConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageRef
|
||||
...ContentPageRef
|
||||
...HotelPageRef
|
||||
...LoyaltyPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sidepeek_content {
|
||||
content {
|
||||
embedded_itemsConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...AccountPageRef
|
||||
...ContentPageRef
|
||||
...HotelPageRef
|
||||
...LoyaltyPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
23
lib/graphql/Fragments/Sidebar/QuickLinks.graphql
Normal file
23
lib/graphql/Fragments/Sidebar/QuickLinks.graphql
Normal file
@@ -0,0 +1,23 @@
|
||||
#import "../AccountPage/Ref.graphql"
|
||||
#import "../ContentPage/Ref.graphql"
|
||||
#import "../LoyaltyPage/Ref.graphql"
|
||||
|
||||
#import "../PageLink/AccountPageLink.graphql"
|
||||
#import "../PageLink/ContentPageLink.graphql"
|
||||
#import "../PageLink/LoyaltyPageLink.graphql"
|
||||
|
||||
#import "../Blocks/Shortcuts.graphql"
|
||||
|
||||
fragment QuickLinksSidebar_ContentPage on ContentPageSidebarShortcuts {
|
||||
__typename
|
||||
shortcuts {
|
||||
...Shortcuts
|
||||
}
|
||||
}
|
||||
|
||||
fragment QuickLinksSidebar_ContentPageRefs on ContentPageSidebarShortcuts {
|
||||
shortcuts {
|
||||
__typename
|
||||
...ShortcutsRefs
|
||||
}
|
||||
}
|
||||
38
lib/graphql/Fragments/Sidebar/ScriptedCard.graphql
Normal file
38
lib/graphql/Fragments/Sidebar/ScriptedCard.graphql
Normal file
@@ -0,0 +1,38 @@
|
||||
#import "../AccountPage/Ref.graphql"
|
||||
#import "../ContentPage/Ref.graphql"
|
||||
#import "../LoyaltyPage/Ref.graphql"
|
||||
|
||||
#import "../PageLink/AccountPageLink.graphql"
|
||||
#import "../PageLink/ContentPageLink.graphql"
|
||||
#import "../PageLink/LoyaltyPageLink.graphql"
|
||||
|
||||
#import "../Blocks/Card.graphql"
|
||||
#import "../Blocks/Refs/Card.graphql"
|
||||
|
||||
fragment ScriptedCardSidebar_ContentPage on ContentPageSidebarScriptedCard {
|
||||
__typename
|
||||
scripted_card {
|
||||
theme
|
||||
scripted_cardConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...CardBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ScriptedCardSidebar_ContentPageRefs on ContentPageSidebarScriptedCard {
|
||||
scripted_card {
|
||||
scripted_cardConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...CardBlockRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
lib/graphql/Fragments/Sidebar/TeaserCard.graphql
Normal file
38
lib/graphql/Fragments/Sidebar/TeaserCard.graphql
Normal file
@@ -0,0 +1,38 @@
|
||||
#import "../AccountPage/Ref.graphql"
|
||||
#import "../ContentPage/Ref.graphql"
|
||||
#import "../LoyaltyPage/Ref.graphql"
|
||||
|
||||
#import "../PageLink/AccountPageLink.graphql"
|
||||
#import "../PageLink/ContentPageLink.graphql"
|
||||
#import "../PageLink/LoyaltyPageLink.graphql"
|
||||
|
||||
#import "../Blocks/TeaserCard.graphql"
|
||||
#import "../Blocks/Refs/TeaserCard.graphql"
|
||||
|
||||
fragment TeaserCardSidebar_ContentPage on ContentPageSidebarTeaserCard {
|
||||
__typename
|
||||
teaser_card {
|
||||
theme
|
||||
teaser_cardConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...TeaserCardBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment TeaserCardSidebar_ContentPageRefs on ContentPageSidebarTeaserCard {
|
||||
teaser_card {
|
||||
teaser_cardConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...TeaserCardBlockRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,10 +19,12 @@ query GetContactConfig($locale: String!) {
|
||||
phone {
|
||||
number
|
||||
name
|
||||
footnote
|
||||
}
|
||||
phone_loyalty {
|
||||
name
|
||||
number
|
||||
footnote
|
||||
}
|
||||
title
|
||||
visiting_address {
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#import "../../Fragments/Sidebar/Content.graphql"
|
||||
#import "../../Fragments/Sidebar/DynamicContent.graphql"
|
||||
#import "../../Fragments/Sidebar/JoinLoyaltyContact.graphql"
|
||||
#import "../../Fragments/Sidebar/TeaserCard.graphql"
|
||||
#import "../../Fragments/Sidebar/ScriptedCard.graphql"
|
||||
#import "../../Fragments/Sidebar/QuickLinks.graphql"
|
||||
|
||||
query GetContentPage($locale: String!, $uid: String!) {
|
||||
content_page(uid: $uid, locale: $locale) {
|
||||
@@ -28,6 +31,9 @@ query GetContentPage($locale: String!, $uid: String!) {
|
||||
...ContentSidebar_ContentPage
|
||||
...DynamicContentSidebar_ContentPage
|
||||
...JoinLoyaltyContactSidebar_ContentPage
|
||||
...ScriptedCardSidebar_ContentPage
|
||||
...TeaserCardSidebar_ContentPage
|
||||
...QuickLinksSidebar_ContentPage
|
||||
}
|
||||
system {
|
||||
...System
|
||||
@@ -77,6 +83,22 @@ query GetContentPageRefs($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
sidebar {
|
||||
__typename
|
||||
...ContentSidebar_ContentPageRefs
|
||||
...JoinLoyaltyContactSidebar_ContentPageRefs
|
||||
...ScriptedCardSidebar_ContentPageRefs
|
||||
...TeaserCardSidebar_ContentPageRefs
|
||||
...QuickLinksSidebar_ContentPageRefs
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetContentPageBlocksRefs($locale: String!, $uid: String!) {
|
||||
content_page(locale: $locale, uid: $uid) {
|
||||
blocks {
|
||||
__typename
|
||||
...Accordion_ContentPageRefs
|
||||
@@ -87,14 +109,6 @@ query GetContentPageRefs($locale: String!, $uid: String!) {
|
||||
...TextCols_ContentPageRef
|
||||
...UspGrid_ContentPageRefs
|
||||
}
|
||||
sidebar {
|
||||
__typename
|
||||
...ContentSidebar_ContentPageRefs
|
||||
...JoinLoyaltyContactSidebar_ContentPageRefs
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
39
lib/graphql/Query/SiteConfig.graphql
Normal file
39
lib/graphql/Query/SiteConfig.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
#import "../Fragments/System.graphql"
|
||||
|
||||
#import "../Fragments/Alert.graphql"
|
||||
|
||||
query GetSiteConfig($locale: String!) {
|
||||
all_site_config(limit: 1, locale: $locale) {
|
||||
items {
|
||||
sitewide_alert {
|
||||
booking_widget_disabled
|
||||
alertConnection {
|
||||
edges {
|
||||
node {
|
||||
...Alert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetSiteConfigRef($locale: String!) {
|
||||
all_site_config(limit: 1, locale: $locale) {
|
||||
items {
|
||||
sitewide_alert {
|
||||
alertConnection {
|
||||
edges {
|
||||
node {
|
||||
...AlertRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,22 @@ export const getProfileSafely = cache(
|
||||
}
|
||||
)
|
||||
|
||||
export const getCreditCardsSafely = cache(
|
||||
async function getMemoizedCreditCardsSafely() {
|
||||
return serverClient().user.safeCreditCards()
|
||||
}
|
||||
)
|
||||
|
||||
export const getHotelData = cache(async function getMemoizedHotelData(
|
||||
hotelId: string,
|
||||
language: string
|
||||
) {
|
||||
return serverClient().hotel.hotelData.get({
|
||||
hotelId,
|
||||
language,
|
||||
})
|
||||
})
|
||||
|
||||
export const getFooter = cache(async function getMemoizedFooter() {
|
||||
return serverClient().contentstack.base.footer()
|
||||
})
|
||||
|
||||
@@ -14,7 +14,8 @@ import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import { systemSchema } from "../schemas/system"
|
||||
|
||||
import { Image } from "@/types/image"
|
||||
import { AlertTypeEnum } from "@/types/enums/alert"
|
||||
import type { Image } from "@/types/image"
|
||||
|
||||
// Help me write this zod schema based on the type ContactConfig
|
||||
export const validateContactConfigSchema = z.object({
|
||||
@@ -39,10 +40,12 @@ export const validateContactConfigSchema = z.object({
|
||||
phone: z.object({
|
||||
number: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
footnote: z.string().nullable(),
|
||||
}),
|
||||
phone_loyalty: z.object({
|
||||
number: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
footnote: z.string().nullable(),
|
||||
}),
|
||||
visiting_address: z.object({
|
||||
zip: z.string().nullable(),
|
||||
@@ -538,6 +541,7 @@ export const headerRefsSchema = z
|
||||
})
|
||||
|
||||
const linkUnionSchema = z.discriminatedUnion("__typename", [
|
||||
pageLinks.accountPageSchema,
|
||||
pageLinks.contentPageSchema,
|
||||
pageLinks.hotelPageSchema,
|
||||
pageLinks.loyaltyPageSchema,
|
||||
@@ -660,3 +664,166 @@ export const headerSchema = z
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export const alertSchema = z
|
||||
.object({
|
||||
type: z.nativeEnum(AlertTypeEnum),
|
||||
text: z.string(),
|
||||
heading: z.string(),
|
||||
phone_contact: z.object({
|
||||
display_text: z.string(),
|
||||
phone_number: z.string().nullable(),
|
||||
footnote: z.string().nullable(),
|
||||
}),
|
||||
has_link: z.boolean(),
|
||||
link: linkAndTitleSchema,
|
||||
has_sidepeek_button: z.boolean(),
|
||||
sidepeek_button: z.object({
|
||||
cta_text: z.string(),
|
||||
}),
|
||||
sidepeek_content: z.object({
|
||||
heading: z.string(),
|
||||
content: z.object({
|
||||
json: z.any(),
|
||||
embedded_itemsConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z
|
||||
.discriminatedUnion("__typename", [
|
||||
pageLinks.accountPageSchema,
|
||||
pageLinks.contentPageSchema,
|
||||
pageLinks.hotelPageSchema,
|
||||
pageLinks.loyaltyPageSchema,
|
||||
])
|
||||
.transform((data) => {
|
||||
const link = pageLinks.transform(data)
|
||||
if (link) {
|
||||
return link
|
||||
}
|
||||
return data
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.transform(
|
||||
({
|
||||
type,
|
||||
heading,
|
||||
text,
|
||||
phone_contact,
|
||||
has_link,
|
||||
link,
|
||||
has_sidepeek_button,
|
||||
sidepeek_button,
|
||||
sidepeek_content,
|
||||
}) => {
|
||||
const hasLink = has_link && link.link
|
||||
return {
|
||||
type,
|
||||
text,
|
||||
heading,
|
||||
phoneContact:
|
||||
phone_contact.display_text && phone_contact.phone_number
|
||||
? {
|
||||
displayText: phone_contact.display_text,
|
||||
phoneNumber: phone_contact.phone_number,
|
||||
footnote: phone_contact.footnote,
|
||||
}
|
||||
: null,
|
||||
hasSidepeekButton: !!has_sidepeek_button,
|
||||
link: hasLink
|
||||
? {
|
||||
url: link.link.url,
|
||||
title: link.title,
|
||||
}
|
||||
: null,
|
||||
sidepeekButton:
|
||||
!hasLink && has_sidepeek_button ? sidepeek_button : null,
|
||||
sidepeekContent:
|
||||
!hasLink && has_sidepeek_button ? sidepeek_content : null,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const siteConfigSchema = z
|
||||
.object({
|
||||
all_site_config: z.object({
|
||||
items: z
|
||||
.array(
|
||||
z.object({
|
||||
sitewide_alert: z.object({
|
||||
booking_widget_disabled: z.boolean(),
|
||||
alertConnection: z.object({
|
||||
edges: z
|
||||
.array(
|
||||
z.object({
|
||||
node: alertSchema,
|
||||
})
|
||||
)
|
||||
.max(1),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
)
|
||||
.max(1),
|
||||
}),
|
||||
})
|
||||
.transform((data) => {
|
||||
if (!data.all_site_config.items.length) {
|
||||
return {
|
||||
sitewideAlert: null,
|
||||
bookingWidgetDisabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
const { sitewide_alert } = data.all_site_config.items[0]
|
||||
|
||||
return {
|
||||
sitewideAlert: sitewide_alert.alertConnection.edges[0]?.node || null,
|
||||
bookingWidgetDisabled: sitewide_alert.booking_widget_disabled,
|
||||
}
|
||||
})
|
||||
|
||||
const sidepeekContentRefSchema = z.object({
|
||||
content: z.object({
|
||||
embedded_itemsConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.discriminatedUnion("__typename", [
|
||||
pageLinks.accountPageRefSchema,
|
||||
pageLinks.contentPageRefSchema,
|
||||
pageLinks.hotelPageRefSchema,
|
||||
pageLinks.loyaltyPageRefSchema,
|
||||
]),
|
||||
})
|
||||
),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
const alertConnectionRefSchema = z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
link: linkRefsSchema,
|
||||
sidepeek_content: sidepeekContentRefSchema,
|
||||
}),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const siteConfigRefSchema = z.object({
|
||||
all_site_config: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
sitewide_alert: z.object({
|
||||
alertConnection: alertConnectionRefSchema,
|
||||
}),
|
||||
system: systemSchema,
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
|
||||
import {
|
||||
GetCurrentFooter,
|
||||
@@ -11,6 +10,10 @@ import {
|
||||
} from "@/lib/graphql/Query/Current/Header.graphql"
|
||||
import { GetFooter, GetFooterRef } from "@/lib/graphql/Query/Footer.graphql"
|
||||
import { GetHeader, GetHeaderRef } from "@/lib/graphql/Query/Header.graphql"
|
||||
import {
|
||||
GetSiteConfig,
|
||||
GetSiteConfigRef,
|
||||
} from "@/lib/graphql/Query/SiteConfig.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
import { contentstackBaseProcedure, router } from "@/server/trpc"
|
||||
@@ -31,13 +34,50 @@ import {
|
||||
type GetCurrentHeaderData,
|
||||
headerRefsSchema,
|
||||
headerSchema,
|
||||
siteConfigRefSchema,
|
||||
siteConfigSchema,
|
||||
validateContactConfigSchema,
|
||||
validateCurrentFooterConfigSchema,
|
||||
validateCurrentHeaderConfigSchema,
|
||||
validateFooterConfigSchema,
|
||||
validateFooterRefConfigSchema,
|
||||
} from "./output"
|
||||
import { getConnections, getFooterConnections } from "./utils"
|
||||
import {
|
||||
getContactConfigCounter,
|
||||
getContactConfigFailCounter,
|
||||
getContactConfigSuccessCounter,
|
||||
getCurrentFooterCounter,
|
||||
getCurrentFooterFailCounter,
|
||||
getCurrentFooterRefCounter,
|
||||
getCurrentFooterSuccessCounter,
|
||||
getCurrentHeaderCounter,
|
||||
getCurrentHeaderFailCounter,
|
||||
getCurrentHeaderRefCounter,
|
||||
getCurrentHeaderSuccessCounter,
|
||||
getFooterCounter,
|
||||
getFooterFailCounter,
|
||||
getFooterRefCounter,
|
||||
getFooterRefFailCounter,
|
||||
getFooterRefSuccessCounter,
|
||||
getFooterSuccessCounter,
|
||||
getHeaderCounter,
|
||||
getHeaderFailCounter,
|
||||
getHeaderRefsCounter,
|
||||
getHeaderRefsFailCounter,
|
||||
getHeaderRefsSuccessCounter,
|
||||
getHeaderSuccessCounter,
|
||||
getSiteConfigCounter,
|
||||
getSiteConfigFailCounter,
|
||||
getSiteConfigRefCounter,
|
||||
getSiteConfigRefFailCounter,
|
||||
getSiteConfigRefSuccessCounter,
|
||||
getSiteConfigSuccessCounter,
|
||||
} from "./telemetry"
|
||||
import {
|
||||
getAlertPhoneContactData,
|
||||
getConnections,
|
||||
getFooterConnections,
|
||||
} from "./utils"
|
||||
|
||||
import type {
|
||||
FooterDataRaw,
|
||||
@@ -47,157 +87,77 @@ import type {
|
||||
GetHeader as GetHeaderData,
|
||||
GetHeaderRefs,
|
||||
} from "@/types/trpc/routers/contentstack/header"
|
||||
import type {
|
||||
GetSiteConfigData,
|
||||
GetSiteConfigRefData,
|
||||
} from "@/types/trpc/routers/contentstack/siteConfig"
|
||||
|
||||
const meter = metrics.getMeter("trpc.contentstack.base")
|
||||
// OpenTelemetry metrics: ContactConfig
|
||||
const getContactConfigCounter = meter.createCounter(
|
||||
"trpc.contentstack.contactConfig.get"
|
||||
)
|
||||
const getContactConfigSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.contactConfig.get-success"
|
||||
)
|
||||
const getContactConfigFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.contactConfig.get-fail"
|
||||
)
|
||||
// OpenTelemetry metrics: CurrentHeader
|
||||
const getCurrentHeaderRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.ref.get"
|
||||
)
|
||||
const getCurrentHeaderRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.ref.get-success"
|
||||
)
|
||||
const getCurrentHeaderRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.ref.get-fail"
|
||||
)
|
||||
const getCurrentHeaderCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.get"
|
||||
)
|
||||
const getCurrentHeaderSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.get-success"
|
||||
)
|
||||
const getCurrentHeaderFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.get-fail"
|
||||
)
|
||||
async function getContactConfig(lang: Lang) {
|
||||
getContactConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.contactConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const response = await request<ContactConfigData>(
|
||||
GetContactConfig,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [`${lang}:contact`],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// OpenTelemetry metrics: Header
|
||||
const getHeaderRefsCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.ref.get"
|
||||
)
|
||||
const getHeaderRefsSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.ref.get-success"
|
||||
)
|
||||
const getHeaderRefsFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.ref.get-fail"
|
||||
)
|
||||
const getHeaderCounter = meter.createCounter("trpc.contentstack.header.get")
|
||||
const getHeaderSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.get-success"
|
||||
)
|
||||
const getHeaderFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.get-fail"
|
||||
)
|
||||
if (!response.data) {
|
||||
const notFoundError = notFound(response)
|
||||
|
||||
// OpenTelemetry metrics: CurrentHeader
|
||||
const getCurrentFooterRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.ref.get"
|
||||
)
|
||||
const getCurrentFooterRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.ref.get-success"
|
||||
)
|
||||
const getCurrentFooterRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.ref.get-fail"
|
||||
)
|
||||
const getCurrentFooterCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.get"
|
||||
)
|
||||
const getCurrentFooterSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.get-success"
|
||||
)
|
||||
const getCurrentFooterFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.get-fail"
|
||||
)
|
||||
getContactConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
// OpenTelemetry metrics: Footer
|
||||
const getFooterRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.ref.get"
|
||||
)
|
||||
const getFooterRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.ref.get-success"
|
||||
)
|
||||
const getFooterRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.ref.get-fail"
|
||||
)
|
||||
const getFooterCounter = meter.createCounter("trpc.contentstack.footer.get")
|
||||
const getFooterSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.get-success"
|
||||
)
|
||||
const getFooterFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.get-fail"
|
||||
)
|
||||
console.error(
|
||||
"contentstack.config not found error",
|
||||
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
|
||||
response.data
|
||||
)
|
||||
|
||||
if (!validatedContactConfigConfig.success) {
|
||||
getContactConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedContactConfigConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.contactConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedContactConfigConfig.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
getContactConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.contactConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
return validatedContactConfigConfig.data.all_contact_config.items[0]
|
||||
}
|
||||
|
||||
export const baseQueryRouter = router({
|
||||
contact: contentstackBaseProcedure.query(async ({ ctx }) => {
|
||||
const { lang } = ctx
|
||||
getContactConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.contactConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const response = await request<ContactConfigData>(
|
||||
GetContactConfig,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [`${lang}:contact`],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
const notFoundError = notFound(response)
|
||||
|
||||
getContactConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"contentstack.config not found error",
|
||||
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
|
||||
response.data
|
||||
)
|
||||
|
||||
if (!validatedContactConfigConfig.success) {
|
||||
getContactConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedContactConfigConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.contactConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedContactConfigConfig.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
getContactConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.contactConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
return validatedContactConfigConfig.data.all_contact_config.items[0]
|
||||
return await getContactConfig(ctx.lang)
|
||||
}),
|
||||
header: contentstackBaseProcedure.query(async ({ ctx }) => {
|
||||
const { lang } = ctx
|
||||
@@ -652,4 +612,149 @@ export const baseQueryRouter = router({
|
||||
|
||||
return validatedFooterConfig.data
|
||||
}),
|
||||
siteConfig: contentstackBaseProcedure.query(async ({ ctx }) => {
|
||||
const { lang } = ctx
|
||||
|
||||
getSiteConfigRefCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.ref start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const responseRef = await request<GetSiteConfigRefData>(
|
||||
GetSiteConfigRef,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, "siteConfig")],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!responseRef.data) {
|
||||
const notFoundError = notFound(responseRef)
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs not found error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: { code: notFoundError.code },
|
||||
})
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfigRef = siteConfigRefSchema.safeParse(
|
||||
responseRef.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfigRef.success) {
|
||||
getSiteConfigRefFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfigRef.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig.refs validation error",
|
||||
JSON.stringify({
|
||||
query: {
|
||||
lang,
|
||||
},
|
||||
error: validatedSiteConfigRef.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
getSiteConfigRefSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig.refs success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
getSiteConfigCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig start",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
const [siteConfigResponse, contactConfig] = await Promise.all([
|
||||
request<GetSiteConfigData>(
|
||||
GetSiteConfig,
|
||||
{
|
||||
locale: lang,
|
||||
},
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [`${lang}:siteConfig`],
|
||||
},
|
||||
}
|
||||
),
|
||||
getContactConfig(lang),
|
||||
])
|
||||
|
||||
if (!siteConfigResponse.data) {
|
||||
const notFoundError = notFound(siteConfigResponse)
|
||||
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "not_found",
|
||||
error: JSON.stringify({ code: notFoundError.code }),
|
||||
})
|
||||
|
||||
console.error(
|
||||
"contentstack.siteConfig not found error",
|
||||
JSON.stringify({ query: { lang }, error: { code: notFoundError.code } })
|
||||
)
|
||||
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
const validatedSiteConfig = siteConfigSchema.safeParse(
|
||||
siteConfigResponse.data
|
||||
)
|
||||
|
||||
if (!validatedSiteConfig.success) {
|
||||
getSiteConfigFailCounter.add(1, {
|
||||
lang,
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(validatedSiteConfig.error),
|
||||
})
|
||||
console.error(
|
||||
"contentstack.siteConfig validation error",
|
||||
JSON.stringify({
|
||||
query: { lang },
|
||||
error: validatedSiteConfig.error,
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
getSiteConfigSuccessCounter.add(1, { lang })
|
||||
console.info(
|
||||
"contentstack.siteConfig success",
|
||||
JSON.stringify({ query: { lang } })
|
||||
)
|
||||
|
||||
const { sitewideAlert } = validatedSiteConfig.data
|
||||
|
||||
return {
|
||||
...validatedSiteConfig.data,
|
||||
sitewideAlert: sitewideAlert
|
||||
? {
|
||||
...sitewideAlert,
|
||||
phone_contact: contactConfig
|
||||
? getAlertPhoneContactData(sitewideAlert, contactConfig)
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
112
server/routers/contentstack/base/telemetry.ts
Normal file
112
server/routers/contentstack/base/telemetry.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
const meter = metrics.getMeter("trpc.contentstack.base")
|
||||
// OpenTelemetry metrics: ContactConfig
|
||||
export const getContactConfigCounter = meter.createCounter(
|
||||
"trpc.contentstack.contactConfig.get"
|
||||
)
|
||||
export const getContactConfigSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.contactConfig.get-success"
|
||||
)
|
||||
export const getContactConfigFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.contactConfig.get-fail"
|
||||
)
|
||||
// OpenTelemetry metrics: CurrentHeader
|
||||
export const getCurrentHeaderRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.ref.get"
|
||||
)
|
||||
export const getCurrentHeaderRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.ref.get-success"
|
||||
)
|
||||
export const getCurrentHeaderRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.ref.get-fail"
|
||||
)
|
||||
export const getCurrentHeaderCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.get"
|
||||
)
|
||||
export const getCurrentHeaderSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.get-success"
|
||||
)
|
||||
export const getCurrentHeaderFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentHeader.get-fail"
|
||||
)
|
||||
|
||||
// OpenTelemetry metrics: Header
|
||||
export const getHeaderRefsCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.ref.get"
|
||||
)
|
||||
export const getHeaderRefsSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.ref.get-success"
|
||||
)
|
||||
export const getHeaderRefsFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.ref.get-fail"
|
||||
)
|
||||
export const getHeaderCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.get"
|
||||
)
|
||||
export const getHeaderSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.get-success"
|
||||
)
|
||||
export const getHeaderFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.header.get-fail"
|
||||
)
|
||||
|
||||
// OpenTelemetry metrics: CurrentFooter
|
||||
export const getCurrentFooterRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.ref.get"
|
||||
)
|
||||
export const getCurrentFooterRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.ref.get-success"
|
||||
)
|
||||
export const getCurrentFooterRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.ref.get-fail"
|
||||
)
|
||||
export const getCurrentFooterCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.get"
|
||||
)
|
||||
export const getCurrentFooterSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.get-success"
|
||||
)
|
||||
export const getCurrentFooterFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.currentFooter.get-fail"
|
||||
)
|
||||
|
||||
// OpenTelemetry metrics: Footer
|
||||
export const getFooterRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.ref.get"
|
||||
)
|
||||
export const getFooterRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.ref.get-success"
|
||||
)
|
||||
export const getFooterRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.ref.get-fail"
|
||||
)
|
||||
export const getFooterCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.get"
|
||||
)
|
||||
export const getFooterSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.get-success"
|
||||
)
|
||||
export const getFooterFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.footer.get-fail"
|
||||
)
|
||||
|
||||
// OpenTelemetry metrics: SiteConfig
|
||||
export const getSiteConfigRefCounter = meter.createCounter(
|
||||
"trpc.contentstack.SiteConfig.ref.get"
|
||||
)
|
||||
export const getSiteConfigRefSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.SiteConfig.ref.get-success"
|
||||
)
|
||||
export const getSiteConfigRefFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.SiteConfig.ref.get-fail"
|
||||
)
|
||||
export const getSiteConfigCounter = meter.createCounter(
|
||||
"trpc.contentstack.SiteConfig.get"
|
||||
)
|
||||
export const getSiteConfigSuccessCounter = meter.createCounter(
|
||||
"trpc.contentstack.SiteConfig.get-success"
|
||||
)
|
||||
export const getSiteConfigFailCounter = meter.createCounter(
|
||||
"trpc.contentstack.SiteConfig.get-fail"
|
||||
)
|
||||
@@ -1,11 +1,12 @@
|
||||
import type {
|
||||
FooterLinkItem,
|
||||
FooterRefDataRaw,
|
||||
} from "@/types/components/footer/footer"
|
||||
import { System } from "@/types/requests/system"
|
||||
import { Edges } from "@/types/requests/utils/edges"
|
||||
import { NodeRefs } from "@/types/requests/utils/refs"
|
||||
import { getValueFromContactConfig } from "@/utils/contactConfig"
|
||||
|
||||
import type { FooterRefDataRaw } from "@/types/components/footer/footer"
|
||||
import type { System } from "@/types/requests/system"
|
||||
import type { Edges } from "@/types/requests/utils/edges"
|
||||
import type { NodeRefs } from "@/types/requests/utils/refs"
|
||||
import type { HeaderRefs } from "@/types/trpc/routers/contentstack/header"
|
||||
import type { Alert } from "@/types/trpc/routers/contentstack/siteConfig"
|
||||
import type { ContactConfig } from "./output"
|
||||
|
||||
export function getConnections({ header }: HeaderRefs) {
|
||||
const connections: System["system"][] = [header.system]
|
||||
@@ -68,3 +69,21 @@ export function getFooterConnections(refs: FooterRefDataRaw) {
|
||||
|
||||
return connections
|
||||
}
|
||||
|
||||
export function getAlertPhoneContactData(
|
||||
alert: Alert,
|
||||
contactConfig: ContactConfig
|
||||
) {
|
||||
if (alert.phoneContact) {
|
||||
const { displayText, phoneNumber, footnote } = alert.phoneContact
|
||||
|
||||
return {
|
||||
displayText,
|
||||
phoneNumber: getValueFromContactConfig(phoneNumber, contactConfig),
|
||||
footnote: footnote
|
||||
? getValueFromContactConfig(footnote, contactConfig)
|
||||
: null,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -39,6 +39,18 @@ import {
|
||||
joinLoyaltyContactRefsSchema,
|
||||
joinLoyaltyContactSchema,
|
||||
} from "../schemas/sidebar/joinLoyaltyContact"
|
||||
import {
|
||||
quickLinksRefschema,
|
||||
quickLinksSchema,
|
||||
} from "../schemas/sidebar/quickLinks"
|
||||
import {
|
||||
scriptedCardRefschema,
|
||||
scriptedCardsSchema,
|
||||
} from "../schemas/sidebar/scriptedCard"
|
||||
import {
|
||||
teaserCardRefschema,
|
||||
teaserCardsSchema,
|
||||
} from "../schemas/sidebar/teaserCard"
|
||||
import { systemSchema } from "../schemas/system"
|
||||
|
||||
import { ContentPageEnum } from "@/types/enums/contentPage"
|
||||
@@ -122,10 +134,31 @@ export const contentPageJoinLoyaltyContact = z
|
||||
})
|
||||
.merge(joinLoyaltyContactSchema)
|
||||
|
||||
export const contentPageSidebarScriptedCard = z
|
||||
.object({
|
||||
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.ScriptedCard),
|
||||
})
|
||||
.merge(scriptedCardsSchema)
|
||||
|
||||
export const contentPageSidebarTeaserCard = z
|
||||
.object({
|
||||
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.TeaserCard),
|
||||
})
|
||||
.merge(teaserCardsSchema)
|
||||
|
||||
export const contentPageSidebarQuicklinks = z
|
||||
.object({
|
||||
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.QuickLinks),
|
||||
})
|
||||
.merge(quickLinksSchema)
|
||||
|
||||
export const sidebarSchema = z.discriminatedUnion("__typename", [
|
||||
contentPageSidebarContent,
|
||||
contentPageSidebarDynamicContent,
|
||||
contentPageJoinLoyaltyContact,
|
||||
contentPageSidebarScriptedCard,
|
||||
contentPageSidebarTeaserCard,
|
||||
contentPageSidebarQuicklinks,
|
||||
])
|
||||
|
||||
const navigationLinksSchema = z
|
||||
@@ -238,9 +271,30 @@ const contentPageSidebarJoinLoyaltyContactRef = z
|
||||
})
|
||||
.merge(joinLoyaltyContactRefsSchema)
|
||||
|
||||
const contentPageSidebarScriptedCardRef = z
|
||||
.object({
|
||||
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.ScriptedCard),
|
||||
})
|
||||
.merge(scriptedCardRefschema)
|
||||
|
||||
const contentPageSidebarTeaserCardRef = z
|
||||
.object({
|
||||
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.TeaserCard),
|
||||
})
|
||||
.merge(teaserCardRefschema)
|
||||
|
||||
const contentPageSidebarQuickLinksRef = z
|
||||
.object({
|
||||
__typename: z.literal(ContentPageEnum.ContentStack.sidebar.QuickLinks),
|
||||
})
|
||||
.merge(quickLinksRefschema)
|
||||
|
||||
const contentPageSidebarRefsItem = z.discriminatedUnion("__typename", [
|
||||
contentPageSidebarContentRef,
|
||||
contentPageSidebarJoinLoyaltyContactRef,
|
||||
contentPageSidebarScriptedCardRef,
|
||||
contentPageSidebarTeaserCardRef,
|
||||
contentPageSidebarQuickLinksRef,
|
||||
])
|
||||
|
||||
const contentPageHeaderRefs = z.object({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { metrics } from "@opentelemetry/api"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { GetContentPageRefs } from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
|
||||
import {
|
||||
GetContentPageBlocksRefs,
|
||||
GetContentPageRefs,
|
||||
} from "@/lib/graphql/Query/ContentPage/ContentPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { notFound } from "@/server/errors/trpc"
|
||||
|
||||
@@ -41,18 +44,30 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
|
||||
query: { lang, uid },
|
||||
})
|
||||
)
|
||||
const refsResponse = await request<GetContentPageRefsSchema>(
|
||||
GetContentPageRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
}
|
||||
)
|
||||
if (!refsResponse.data) {
|
||||
const notFoundError = notFound(refsResponse)
|
||||
const [mainRefsResponse, blockRefsResponse] = await Promise.all([
|
||||
request<GetContentPageRefsSchema>(
|
||||
GetContentPageRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
}
|
||||
),
|
||||
request<GetContentPageRefsSchema>(
|
||||
GetContentPageBlocksRefs,
|
||||
{ locale: lang, uid },
|
||||
{
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
tags: [generateTag(lang, uid)],
|
||||
},
|
||||
}
|
||||
),
|
||||
])
|
||||
if (!mainRefsResponse.data) {
|
||||
const notFoundError = notFound(mainRefsResponse)
|
||||
getContentPageRefsFailCounter.add(1, {
|
||||
lang,
|
||||
uid,
|
||||
@@ -73,8 +88,14 @@ export async function fetchContentPageRefs(lang: Lang, uid: string) {
|
||||
)
|
||||
throw notFoundError
|
||||
}
|
||||
|
||||
return refsResponse.data
|
||||
const responseData = {
|
||||
...mainRefsResponse.data,
|
||||
content_page: {
|
||||
...mainRefsResponse.data.content_page,
|
||||
blocks: blockRefsResponse.data.content_page.blocks,
|
||||
},
|
||||
}
|
||||
return responseData
|
||||
}
|
||||
|
||||
export function validateContentPageRefs(
|
||||
@@ -171,17 +192,30 @@ export function getConnections({ content_page }: ContentPageRefs) {
|
||||
connections.push(...block.content)
|
||||
}
|
||||
break
|
||||
|
||||
case ContentPageEnum.ContentStack.sidebar.JoinLoyaltyContact:
|
||||
if (block.join_loyalty_contact?.button) {
|
||||
connections.push(block.join_loyalty_contact.button)
|
||||
}
|
||||
break
|
||||
case ContentPageEnum.ContentStack.sidebar.ScriptedCard:
|
||||
if (block.scripted_card?.length) {
|
||||
connections.push(...block.scripted_card)
|
||||
}
|
||||
break
|
||||
case ContentPageEnum.ContentStack.sidebar.TeaserCard:
|
||||
if (block.teaser_card?.length) {
|
||||
connections.push(...block.teaser_card)
|
||||
}
|
||||
break
|
||||
case ContentPageEnum.ContentStack.sidebar.QuickLinks:
|
||||
if (block.shortcuts.shortcuts.length) {
|
||||
connections.push(...block.shortcuts.shortcuts)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return connections
|
||||
}
|
||||
|
||||
@@ -4,11 +4,7 @@ import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
|
||||
|
||||
import { BlocksEnums } from "@/types/enums/blocks"
|
||||
|
||||
export const shortcutsSchema = z.object({
|
||||
typename: z
|
||||
.literal(BlocksEnums.block.Shortcuts)
|
||||
.optional()
|
||||
.default(BlocksEnums.block.Shortcuts),
|
||||
export const shortcutsBlockSchema = z.object({
|
||||
shortcuts: z
|
||||
.object({
|
||||
subtitle: z.string().nullable(),
|
||||
@@ -62,6 +58,15 @@ export const shortcutsSchema = z.object({
|
||||
}),
|
||||
})
|
||||
|
||||
export const shortcutsSchema = z
|
||||
.object({
|
||||
typename: z
|
||||
.literal(BlocksEnums.block.Shortcuts)
|
||||
.optional()
|
||||
.default(BlocksEnums.block.Shortcuts),
|
||||
})
|
||||
.merge(shortcutsBlockSchema)
|
||||
|
||||
export const shortcutsRefsSchema = z.object({
|
||||
shortcuts: z.object({
|
||||
shortcuts: z
|
||||
|
||||
@@ -11,6 +11,11 @@ const metaData = z.object({
|
||||
Value: z.string().nullable(),
|
||||
})
|
||||
|
||||
export const focalPointSchema = z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Defines a media asset, original or conversion
|
||||
*/
|
||||
@@ -94,6 +99,7 @@ export const imageVaultAssetSchema = z.object({
|
||||
* Name of the user that added the asset to ImageVault
|
||||
*/
|
||||
AddedBy: z.string(),
|
||||
FocalPoint: focalPointSchema.optional(),
|
||||
})
|
||||
|
||||
export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform(
|
||||
@@ -124,6 +130,7 @@ export const imageVaultAssetTransformedSchema = imageVaultAssetSchema.transform(
|
||||
height: mediaConversion.Height,
|
||||
aspectRatio,
|
||||
},
|
||||
focalPoint: rawData.FocalPoint || { x: 50, y: 50 },
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
|
||||
import { ContentEnum } from "@/types/enums/content"
|
||||
import { SidebarEnums } from "@/types/enums/sidebar"
|
||||
import { System } from "@/types/requests/system"
|
||||
|
||||
export const contentSchema = z.object({
|
||||
typename: z
|
||||
|
||||
16
server/routers/contentstack/schemas/sidebar/quickLinks.ts
Normal file
16
server/routers/contentstack/schemas/sidebar/quickLinks.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { shortcutsBlockSchema, shortcutsRefsSchema } from "../blocks/shortcuts"
|
||||
|
||||
import { SidebarEnums } from "@/types/enums/sidebar"
|
||||
|
||||
export const quickLinksSchema = z
|
||||
.object({
|
||||
typename: z
|
||||
.literal(SidebarEnums.blocks.QuickLinks)
|
||||
.optional()
|
||||
.default(SidebarEnums.blocks.QuickLinks),
|
||||
})
|
||||
.merge(shortcutsBlockSchema)
|
||||
|
||||
export const quickLinksRefschema = shortcutsRefsSchema
|
||||
66
server/routers/contentstack/schemas/sidebar/scriptedCard.ts
Normal file
66
server/routers/contentstack/schemas/sidebar/scriptedCard.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
cardBlockRefsSchema,
|
||||
cardBlockSchema,
|
||||
transformCardBlock,
|
||||
transformCardBlockRefs,
|
||||
} from "../blocks/cardsGrid"
|
||||
|
||||
import { SidebarEnums } from "@/types/enums/sidebar"
|
||||
|
||||
export const scriptedCardsSchema = z.object({
|
||||
typename: z
|
||||
.literal(SidebarEnums.blocks.ScriptedCard)
|
||||
.optional()
|
||||
.default(SidebarEnums.blocks.ScriptedCard),
|
||||
scripted_card: z
|
||||
.object({
|
||||
theme: z
|
||||
.enum([
|
||||
"one",
|
||||
"two",
|
||||
"three",
|
||||
"primaryDim",
|
||||
"primaryDark",
|
||||
"primaryInverted",
|
||||
"primaryStrong",
|
||||
])
|
||||
.nullable(),
|
||||
scripted_cardConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: cardBlockSchema,
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform((data) => {
|
||||
return {
|
||||
theme: data.theme,
|
||||
...transformCardBlock(data.scripted_cardConnection.edges[0].node),
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
export const scriptedCardRefschema = z.object({
|
||||
scripted_card: z
|
||||
.object({
|
||||
scripted_cardConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: cardBlockRefsSchema,
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform((data) => {
|
||||
let card = null
|
||||
if (data.scripted_cardConnection.edges.length) {
|
||||
card = transformCardBlockRefs(
|
||||
data.scripted_cardConnection.edges[0].node
|
||||
)
|
||||
}
|
||||
return card
|
||||
}),
|
||||
})
|
||||
54
server/routers/contentstack/schemas/sidebar/teaserCard.ts
Normal file
54
server/routers/contentstack/schemas/sidebar/teaserCard.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
teaserCardBlockRefsSchema,
|
||||
teaserCardBlockSchema,
|
||||
transformCardBlockRefs,
|
||||
transformTeaserCardBlock,
|
||||
} from "../blocks/cardsGrid"
|
||||
|
||||
import { SidebarEnums } from "@/types/enums/sidebar"
|
||||
|
||||
export const teaserCardsSchema = z.object({
|
||||
typename: z
|
||||
.literal(SidebarEnums.blocks.TeaserCard)
|
||||
.optional()
|
||||
.default(SidebarEnums.blocks.TeaserCard),
|
||||
teaser_card: z
|
||||
.object({
|
||||
theme: z.enum(["featured", "default"]).nullable().default("default"),
|
||||
teaser_cardConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: teaserCardBlockSchema,
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform((data) => {
|
||||
return {
|
||||
...transformTeaserCardBlock(data.teaser_cardConnection.edges[0].node),
|
||||
theme: data.theme,
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
export const teaserCardRefschema = z.object({
|
||||
teaser_card: z
|
||||
.object({
|
||||
teaser_cardConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: teaserCardBlockRefsSchema,
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
.transform((data) => {
|
||||
let card = null
|
||||
if (data.teaser_cardConnection.edges.length) {
|
||||
card = transformCardBlockRefs(data.teaser_cardConnection.edges[0].node)
|
||||
}
|
||||
return card
|
||||
}),
|
||||
})
|
||||
@@ -196,9 +196,11 @@ export const creditCardSchema = z
|
||||
attribute: z.object({
|
||||
cardName: z.string().optional(),
|
||||
alias: z.string(),
|
||||
truncatedNumber: z.string(),
|
||||
truncatedNumber: z.string().transform((s) => s.slice(-4)),
|
||||
expirationDate: z.string(),
|
||||
cardType: z.string(),
|
||||
cardType: z
|
||||
.string()
|
||||
.transform((s) => s.charAt(0).toLowerCase() + s.slice(1)),
|
||||
}),
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
@@ -208,6 +210,9 @@ export const creditCardSchema = z
|
||||
id: apiResponse.id,
|
||||
type: apiResponse.attribute.cardType,
|
||||
truncatedNumber: apiResponse.attribute.truncatedNumber,
|
||||
alias: apiResponse.attribute.alias,
|
||||
expirationDate: apiResponse.attribute.expirationDate,
|
||||
cardType: apiResponse.attribute.cardType,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -206,6 +206,56 @@ function parsedUser(data: User, isMFA: boolean) {
|
||||
return user
|
||||
}
|
||||
|
||||
async function getCreditCards(session: Session) {
|
||||
getCreditCardsCounter.add(1)
|
||||
console.info("api.profile.creditCards start", JSON.stringify({}))
|
||||
const apiResponse = await api.get(api.endpoints.v1.creditCards, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.token.access_token}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCreditCardsFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.profile.creditCards error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const verifiedData = creditCardsSchema.safeParse(apiJson)
|
||||
if (!verifiedData.success) {
|
||||
getCreditCardsFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(verifiedData.error),
|
||||
})
|
||||
console.error(
|
||||
"api.profile.creditCards validation error ",
|
||||
JSON.stringify({ error: verifiedData.error })
|
||||
)
|
||||
return null
|
||||
}
|
||||
getCreditCardsSuccessCounter.add(1)
|
||||
console.info("api.profile.creditCards success", JSON.stringify({}))
|
||||
return verifiedData.data.data
|
||||
}
|
||||
|
||||
export const userQueryRouter = router({
|
||||
get: protectedProcedure
|
||||
.use(async function (opts) {
|
||||
@@ -675,53 +725,14 @@ export const userQueryRouter = router({
|
||||
}),
|
||||
|
||||
creditCards: protectedProcedure.query(async function ({ ctx }) {
|
||||
getCreditCardsCounter.add(1)
|
||||
console.info("api.profile.creditCards start", JSON.stringify({}))
|
||||
const apiResponse = await api.get(api.endpoints.v1.creditCards, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||
},
|
||||
})
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
const text = await apiResponse.text()
|
||||
getCreditCardsFailCounter.add(1, {
|
||||
error_type: "http_error",
|
||||
error: JSON.stringify({
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
}),
|
||||
})
|
||||
console.error(
|
||||
"api.profile.creditCards error ",
|
||||
JSON.stringify({
|
||||
error: {
|
||||
status: apiResponse.status,
|
||||
statusText: apiResponse.statusText,
|
||||
text,
|
||||
},
|
||||
})
|
||||
)
|
||||
return await getCreditCards(ctx.session)
|
||||
}),
|
||||
safeCreditCards: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||
if (!ctx.session) {
|
||||
return null
|
||||
}
|
||||
|
||||
const apiJson = await apiResponse.json()
|
||||
const verifiedData = creditCardsSchema.safeParse(apiJson)
|
||||
if (!verifiedData.success) {
|
||||
getCreditCardsFailCounter.add(1, {
|
||||
error_type: "validation_error",
|
||||
error: JSON.stringify(verifiedData.error),
|
||||
})
|
||||
console.error(
|
||||
"api.profile.creditCards validation error ",
|
||||
JSON.stringify({ error: verifiedData.error })
|
||||
)
|
||||
return null
|
||||
}
|
||||
getCreditCardsSuccessCounter.add(1)
|
||||
console.info("api.profile.creditCards success", JSON.stringify({}))
|
||||
return verifiedData.data.data
|
||||
return await getCreditCards(ctx.session)
|
||||
}),
|
||||
|
||||
membershipCards: protectedProcedure.query(async function ({ ctx }) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Hotel } from "@/types/hotel"
|
||||
import { CreditCard } from "@/types/user"
|
||||
|
||||
export interface SectionProps {
|
||||
nextPath: string
|
||||
@@ -28,7 +28,9 @@ export interface BreakfastSelectionProps extends SectionProps {
|
||||
export interface DetailsProps extends SectionProps {}
|
||||
|
||||
export interface PaymentProps {
|
||||
hotel: Hotel
|
||||
hotelId: string
|
||||
otherPaymentOptions: string[]
|
||||
savedCreditCards: CreditCard[] | null
|
||||
}
|
||||
|
||||
export interface SectionPageProps {
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
export type ApiImage = {
|
||||
import type { ImageProps as NextImageProps } from "next/image"
|
||||
|
||||
import type { ImageVaultAsset } from "./imageVault"
|
||||
|
||||
export interface FocalPoint {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface Dimensions {
|
||||
width: number
|
||||
height: number
|
||||
aspectRatio: number
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
alt: string | undefined | null
|
||||
caption: string | undefined | null
|
||||
}
|
||||
|
||||
export interface ApiImage {
|
||||
id: string
|
||||
url: string
|
||||
title: string
|
||||
meta: {
|
||||
alt: string
|
||||
caption: string
|
||||
}
|
||||
meta: Meta
|
||||
dimensions?: Dimensions
|
||||
focalPoint?: FocalPoint
|
||||
}
|
||||
|
||||
export interface ImageProps extends NextImageProps {
|
||||
focalPoint?: FocalPoint
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import { z } from "zod"
|
||||
|
||||
import { imageVaultAssetSchema } from "@/server/routers/contentstack/schemas/imageVault"
|
||||
|
||||
import type { FocalPoint } from "./image"
|
||||
|
||||
export type ImageVaultAssetResponse = z.infer<typeof imageVaultAssetSchema>
|
||||
|
||||
export type ImageVaultAsset = {
|
||||
@@ -23,4 +25,5 @@ export type ImageVaultAsset = {
|
||||
aspectRatio: number
|
||||
}
|
||||
meta: { alt: string | undefined | null; caption: string | undefined | null }
|
||||
focalPoint: FocalPoint
|
||||
}
|
||||
|
||||
5
types/enums/alert.ts
Normal file
5
types/enums/alert.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum AlertTypeEnum {
|
||||
Info = "info",
|
||||
Warning = "warning",
|
||||
Alarm = "alarm",
|
||||
}
|
||||
@@ -15,6 +15,9 @@ export namespace ContentPageEnum {
|
||||
Content = "ContentPageSidebarContent",
|
||||
DynamicContent = "ContentPageSidebarDynamicContent",
|
||||
JoinLoyaltyContact = "ContentPageSidebarJoinLoyaltyContact",
|
||||
ScriptedCard = "ContentPageSidebarScriptedCard",
|
||||
TeaserCard = "ContentPageSidebarTeaserCard",
|
||||
QuickLinks = "ContentPageSidebarShortcuts",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,8 @@ export namespace SidebarEnums {
|
||||
Content = "Content",
|
||||
DynamicContent = "DynamicContent",
|
||||
JoinLoyaltyContact = "JoinLoyaltyContact",
|
||||
ScriptedCard = "ScriptedCard",
|
||||
TeaserCard = "TeaserCard",
|
||||
QuickLinks = "QuickLinks",
|
||||
}
|
||||
}
|
||||
|
||||
14
types/trpc/routers/contentstack/siteConfig.ts
Normal file
14
types/trpc/routers/contentstack/siteConfig.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
alertSchema,
|
||||
siteConfigRefSchema,
|
||||
siteConfigSchema,
|
||||
} from "@/server/routers/contentstack/base/output"
|
||||
|
||||
export type GetSiteConfigRefData = z.infer<typeof siteConfigRefSchema>
|
||||
|
||||
export type GetSiteConfigData = z.input<typeof siteConfigSchema>
|
||||
export type SiteConfig = z.output<typeof siteConfigSchema>
|
||||
|
||||
export type Alert = z.output<typeof alertSchema>
|
||||
@@ -33,6 +33,7 @@ export function insertResponseToImageVaultAsset(
|
||||
height: mediaConversion.Height,
|
||||
aspectRatio,
|
||||
},
|
||||
focalPoint: response.FocalPoint || { x: 50, y: 50 },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user