diff --git a/src/components/_common/illustration/static/IllustrationStatic.tsx b/src/components/_common/illustration/static/IllustrationStatic.tsx index f405a034c..8e93f0505 100644 --- a/src/components/_common/illustration/static/IllustrationStatic.tsx +++ b/src/components/_common/illustration/static/IllustrationStatic.tsx @@ -6,6 +6,7 @@ import { classNames } from 'utils/classnames'; import { buildImageCacheUrl, NextImageProps } from 'components/_common/image/NextImage'; import { XpImage } from 'components/_common/image/XpImage'; import { useSWRImmutableOnScrollIntoView } from 'utils/fetch/useSWRImmutableOnScrollIntoView'; +import { Language } from 'translations'; import styleCommon from 'components/_common/illustration/Illustration.module.scss'; import styleStatic from './IllustrationStatic.module.scss'; @@ -16,6 +17,7 @@ type ValidIcon = DefinedIcon & Required>; type StaticIconProps = { icon: ValidIcon; isEditorView: boolean; + language: Language; className?: string; }; @@ -31,13 +33,13 @@ const fetchSvgData = (url: string) => .then((res) => (res.ok ? res.text() : null)) .catch((_) => null); -const SvgIcon = ({ icon, isEditorView, className }: StaticIconProps) => { +const SvgIcon = ({ icon, isEditorView, className, language }: StaticIconProps) => { const elementId = useId(); const { data: svgData } = useSWRImmutableOnScrollIntoView({ url: buildImageCacheUrl({ ...nextImageProps, - src: getMediaUrl(icon.mediaUrl, isEditorView), + src: getMediaUrl(icon.mediaUrl, isEditorView, language), isEditorView, }), fetchFunc: fetchSvgData, @@ -80,7 +82,7 @@ type Props = { }; export const IllustrationStatic = ({ illustration, className }: Props) => { - const { editorView } = usePageContentProps(); + const { editorView, language } = usePageContentProps(); if (!illustration) { return null; @@ -96,10 +98,20 @@ export const IllustrationStatic = ({ illustration, className }: Props) => { return ( {isValidIcon(icon1?.icon) && ( - + )} {isValidIcon(icon2?.icon) && ( - + )} ); diff --git a/src/components/_common/image/XpImage.tsx b/src/components/_common/image/XpImage.tsx index 550f51385..3a8126460 100644 --- a/src/components/_common/image/XpImage.tsx +++ b/src/components/_common/image/XpImage.tsx @@ -11,9 +11,9 @@ type Props = { NextImageProps; export const XpImage = ({ imageProps, alt, ...rest }: Props) => { - const { editorView } = usePageContentProps(); + const { editorView, language } = usePageContentProps(); - const imageUrl = getMediaUrl(imageProps.mediaUrl, !!editorView); + const imageUrl = getMediaUrl(imageProps.mediaUrl, !!editorView, language); if (!imageUrl) { return null; } diff --git a/src/components/_common/parsed-html/ParsedHtml.tsx b/src/components/_common/parsed-html/ParsedHtml.tsx index 5a7bf3a09..048821472 100644 --- a/src/components/_common/parsed-html/ParsedHtml.tsx +++ b/src/components/_common/parsed-html/ParsedHtml.tsx @@ -78,7 +78,7 @@ type Props = { }; export const ParsedHtml = ({ htmlProps }: Props) => { - const { editorView } = usePageContentProps(); + const { editorView, language } = usePageContentProps(); if (!htmlProps) { return null; @@ -123,7 +123,7 @@ export const ParsedHtml = ({ htmlProps }: Props) => { ); } diff --git a/src/components/_common/qbrick-video/QbrickVideo.tsx b/src/components/_common/qbrick-video/QbrickVideo.tsx index 745424d54..ad0680b36 100644 --- a/src/components/_common/qbrick-video/QbrickVideo.tsx +++ b/src/components/_common/qbrick-video/QbrickVideo.tsx @@ -36,7 +36,9 @@ export const QbrickVideo = (props: QbrickVideoProps) => { const durationAsString = getTimestampFromDuration(duration); - const imageUrl = poster?.startsWith('http') ? poster : getMediaUrl(poster, !!editorView); + const imageUrl = poster?.startsWith('http') + ? poster + : getMediaUrl(poster, !!editorView, contentLanguage); return (
diff --git a/src/components/parts/link-panel/LinkPanelPart.tsx b/src/components/parts/link-panel/LinkPanelPart.tsx index d71459e75..bf2359214 100644 --- a/src/components/parts/link-panel/LinkPanelPart.tsx +++ b/src/components/parts/link-panel/LinkPanelPart.tsx @@ -28,7 +28,7 @@ export type PartConfigLinkPanel = { } & LinkWithIngressMixin; export const LinkPanelPart = ({ config }: PartComponentProps) => { - const { editorView } = usePageContentProps(); + const { editorView, language } = usePageContentProps(); if (!config) { return ; @@ -40,7 +40,7 @@ export const LinkPanelPart = ({ config }: PartComponentProps const linkProps = getSelectableLinkProps(link); - const bgUrl = background?.mediaUrl && getMediaUrl(background.mediaUrl, isEditorView); + const bgUrl = background?.mediaUrl && getMediaUrl(background.mediaUrl, isEditorView, language); const selectedVariant = variant?._selected; const variantConfig = selectedVariant && variant[selectedVariant]; @@ -86,10 +86,7 @@ export const LinkPanelPart = ({ config }: PartComponentProps }), }} > - +
)} diff --git a/src/utils/fetch/fetch-page-props.ts b/src/utils/fetch/fetch-page-props.ts index d6640717a..02bc5d968 100644 --- a/src/utils/fetch/fetch-page-props.ts +++ b/src/utils/fetch/fetch-page-props.ts @@ -68,7 +68,7 @@ export const fetchPageProps = async ({ // Media content should redirect to the mediaUrl generated by XP (temporary redirect) if (isMediaContent(content)) { - const mediaUrl = getMediaUrl(content.mediaUrl, isDraft); + const mediaUrl = getMediaUrl(content.mediaUrl, isDraft, content.language); if (!mediaUrl) { return notFoundProps; } diff --git a/src/utils/languages.ts b/src/utils/languages.ts index 2a8678034..9edeae729 100644 --- a/src/utils/languages.ts +++ b/src/utils/languages.ts @@ -1,5 +1,6 @@ import { ContentProps } from 'types/content-props/_content-common'; import { LanguageProps } from 'types/language'; +import { Language } from 'translations'; export const getContentLanguages = (content: ContentProps): LanguageProps[] => content.languages || []; @@ -7,3 +8,12 @@ export const getContentLanguages = (content: ContentProps): LanguageProps[] => const norwegianLanguagesSet: ReadonlySet = new Set(['no', 'nn', 'nb']); export const isNorwegianLanguage = (language: string) => norwegianLanguagesSet.has(language); + +export const pageLanguageToLayerLanguage: { [key in Language]?: Language } = { + nn: 'nn', + se: 'se', + en: 'en', + ru: 'en', + uk: 'en', + pl: 'en', +} as const; diff --git a/src/utils/urls.ts b/src/utils/urls.ts index 6edddc9bb..179f314c2 100644 --- a/src/utils/urls.ts +++ b/src/utils/urls.ts @@ -1,5 +1,7 @@ import { ContentProps } from 'types/content-props/_content-common'; import { logger } from 'srcCommon/logger'; +import { Language } from 'translations'; +import { pageLanguageToLayerLanguage } from './languages'; export const appOriginProd = 'https://www.nav.no'; export const xpContentPathPrefix = '/www.nav.no'; @@ -91,10 +93,22 @@ export const getInternalAbsoluteUrl = (url: string, isEditorView: boolean) => { }; // Media url must always be absolute, to prevent internal nextjs routing loopbacks on redirects -export function getMediaUrl(url: string, isEditorView: boolean): string; -export function getMediaUrl(url: string | undefined, isEditorView: boolean): string | undefined; -export function getMediaUrl(url: string | undefined, isEditorView: boolean) { - return url?.replace( +export function getMediaUrl(url: string, isEditorView: boolean, language?: Language): string; +export function getMediaUrl( + url: string | undefined, + isEditorView: boolean, + language?: Language +): string | undefined; +export function getMediaUrl( + url: string | undefined, + isEditorView: boolean, + language: Language = 'no' +) { + if (!url) { + return undefined; + } + + return transformToXpLayerUrl(url, isEditorView, language).replace( internalUrlPrefixPattern, isEditorView ? `${adminOrigin}${xpDraftPathPrefix}` : xpOrigin ); @@ -126,3 +140,12 @@ export const routerQueryToXpPathOrId = (routerQuery: string | string[]) => { return `${xpContentPathPrefix}${path}`; }; + +// Direct links to XP assets or services should point to the appropriate layer for the specified language +// The /_/ repo mappings are defined in the vhost config on the XP servers +export const transformToXpLayerUrl = (url: string, isEditorView: boolean, language: Language) => { + const path = getInternalRelativePath(url, isEditorView); + const layer = pageLanguageToLayerLanguage[language]; + + return layer ? path.replace(/^\/_/, `/_/${layer}`) : path; +}; diff --git a/src/utils/usePublicUrl.ts b/src/utils/usePublicUrl.ts index 8d7de1b64..8bcecb88e 100644 --- a/src/utils/usePublicUrl.ts +++ b/src/utils/usePublicUrl.ts @@ -1,21 +1,37 @@ import { usePageContentProps } from 'store/pageContext'; -import { getInternalRelativePath, isAppUrl, isInternalUrl, stripXpPathPrefix } from './urls'; +import { + getInternalRelativePath, + isAppUrl, + isXpUrl, + stripXpPathPrefix, + transformToXpLayerUrl, +} from './urls'; type ReturnValue = { url: string; - canRouteClientSide: boolean; // If this is true, navigation to the url can be done client-side with next router + canRouteClientSide: boolean; // If true, navigation to the url can be done client-side with next router }; export const usePublicUrl = (href: string): ReturnValue => { - const { editorView } = usePageContentProps(); + const { editorView, language } = usePageContentProps(); - if (isInternalUrl(href)) { + if (isXpUrl(href)) { + return { + url: transformToXpLayerUrl(href, !!editorView, language), + canRouteClientSide: false, + }; + } + + if (isAppUrl(href)) { const internalPath = getInternalRelativePath(href, !!editorView); return { url: internalPath, - canRouteClientSide: isAppUrl(internalPath), + canRouteClientSide: true, }; } - return { url: stripXpPathPrefix(href) || '/', canRouteClientSide: false }; + return { + url: stripXpPathPrefix(href) || '/', + canRouteClientSide: false, + }; };