Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions public/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ window.__APP_CONFIG__ = {
// Base URL of your Dataverse backend
backendUrl: 'http://localhost:8000',
// Optional banner shown at the top of the app when set. Basic HTML markup is supported.
bannerMessage:
"You are using the new Dataverse <strong>Modern version</strong>. This is an early release and some features from the original site are not yet available. Please see the <a href='https://dataverse.harvard.edu/modern/featured-item/harvard/1'>Project Roadmap</a> for details.",
// Use a string for one message across all languages, or map language codes to localized messages.
bannerMessage: {
en: "You are using the new Dataverse <strong>Modern version</strong>. This is an early release and some features from the original site are not yet available. Please see the <a href='https://dataverse.harvard.edu/modern/featured-item/harvard/1'>Project Roadmap</a> for details.",
es: "Está utilizando la nueva <strong>versión moderna</strong> de Dataverse. Esta es una versión preliminar y algunas funciones del sitio original aún no están disponibles. Consulte la <a href='https://dataverse.harvard.edu/modern/featured-item/harvard/1'>hoja de ruta del proyecto</a> para más detalles."
},
// OIDC provider settings
oidc: {
clientId: 'test',
Expand Down
4 changes: 3 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ declare global {

let CONFIG: AppConfig | undefined

const BannerMessageSchema = z.union([z.string(), z.record(z.string(), z.string())])

const AppConfigSchema = z.object({
backendUrl: z.url(),
bannerMessage: z.string().optional(),
bannerMessage: BannerMessageSchema.optional(),
oidc: z.object({
clientId: z.string(),
authorizationEndpoint: z.url(),
Expand Down
40 changes: 37 additions & 3 deletions src/sections/layout/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import DOMPurify from 'dompurify'
import { Outlet } from 'react-router-dom'
import { Container } from '@iqss/dataverse-design-system'
import { useTranslation } from 'react-i18next'
import styles from './Layout.module.scss'
import { FooterFactory } from './footer/FooterFactory'
import TopBarProgressIndicator from './topbar-progress-indicator/TopbarProgressIndicator'
import { HeaderFactory } from './header/HeaderFactory'
import { HistoryTrackerProvider } from '@/router/HistoryTrackerProvider'
import { requireAppConfig } from '@/config'
import type { AppConfig } from '@/config'

export function Layout() {
const { bannerMessage } = requireAppConfig()
const sanitizedBannerMessage = bannerMessage
? DOMPurify.sanitize(bannerMessage, { USE_PROFILES: { html: true } })
const { i18n } = useTranslation()
const { bannerMessage, defaultLanguage } = requireAppConfig()
const localizedBannerMessage = getLocalizedBannerMessage(
bannerMessage,
i18n.resolvedLanguage ?? i18n.language,
defaultLanguage
)
const sanitizedBannerMessage = localizedBannerMessage
? DOMPurify.sanitize(localizedBannerMessage, { USE_PROFILES: { html: true } })
: null

return (
Expand All @@ -34,3 +42,29 @@ export function Layout() {
</HistoryTrackerProvider>
)
}

function getLocalizedBannerMessage(
bannerMessage: AppConfig['bannerMessage'],
selectedLanguage: string | undefined,
defaultLanguage: string
): string | undefined {
if (!bannerMessage || typeof bannerMessage === 'string') return bannerMessage

const normalizedMessages = Object.fromEntries(
Object.entries(bannerMessage).map(([language, message]) => [language.toLowerCase(), message])
)
const selectedLanguageCode = selectedLanguage?.toLowerCase()
const defaultLanguageCode = defaultLanguage.toLowerCase()
const candidateLanguages = [
selectedLanguageCode,
selectedLanguageCode?.split('-')[0],
defaultLanguageCode,
defaultLanguageCode.split('-')[0]
].filter((language): language is string => Boolean(language))

for (const language of candidateLanguages) {
if (normalizedMessages[language]) return normalizedMessages[language]
}

return Object.values(bannerMessage)[0]
}
40 changes: 40 additions & 0 deletions tests/component/sections/layout/Layout.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FooterMother } from './footer/FooterMother'
import { Layout } from '../../../../src/sections/layout/Layout'
import { applyTestAppConfig } from '../../../support/bootstrapAppConfig'
import type { AppConfig } from '@/config'
import i18next from '@/i18n'

describe('Layout', () => {
const sandbox: SinonSandbox = createSandbox()
Expand All @@ -17,6 +18,8 @@ describe('Layout', () => {
sandbox.restore()
Cypress.env('bannerMessage', defaultBannerMessageEnv)
applyTestAppConfig()
cy.clearAllLocalStorage()
cy.wrap(i18next.changeLanguage('en'))
})

it('renders the header', () => {
Expand Down Expand Up @@ -60,4 +63,41 @@ describe('Layout', () => {
cy.get('script').should('not.exist')
})
})

it('renders the banner message for the selected header language', () => {
Cypress.env('bannerMessage', {
en: 'English <strong>banner</strong>',
es: 'Banner en español <strong>moderno</strong>'
})
applyTestAppConfig()

cy.customMount(<Layout />)

cy.findByRole('alert').within(() => {
cy.findByText('English', { exact: false }).should('exist')
cy.findByText('Banner en español', { exact: false }).should('not.exist')
})

cy.findByRole('button', { name: 'Toggle navigation' }).click()
cy.get('#language-switcher-dropdown').click()
cy.findByText('Español').click()

cy.findByRole('alert').within(() => {
cy.findByText('Banner en español', { exact: false }).should('exist')
cy.get('strong').should('contain.text', 'moderno')
cy.findByText('English', { exact: false }).should('not.exist')
})
})

it('falls back to the default language banner when the selected language is not configured', () => {
Cypress.env('bannerMessage', {
en: 'Default language banner'
})
applyTestAppConfig()

cy.wrap(i18next.changeLanguage('es'))
cy.customMount(<Layout />)

cy.findByRole('alert').should('contain.text', 'Default language banner')
})
})
Loading