Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel

### Changed

### Fixed
- Add multilingual support for the banner message.

### Removed

Expand Down
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