From 1034877d03b307eb905497f8633f68a40ca92b56 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Thu, 25 Jan 2024 06:02:40 +0100 Subject: [PATCH] Add translation menu button to default navigation bar REDMINE-20592 --- entry_types/scrolled/config/locales/de.yml | 6 -- entry_types/scrolled/config/locales/en.yml | 6 -- .../spec/frontend/MediaPlayerControls-spec.js | 10 +-- .../spec/frontend/useTextTracks-spec.js | 17 +++-- .../TranslationsMenu-spec.js | 71 +++++++++++++++++++ .../src/entryState/entryTranslations.js | 6 ++ .../scrolled/package/src/entryState/index.js | 1 + .../src/entryState/watchCollections.js | 1 + .../package/src/frontend/ThemeIcon.js | 4 +- .../package/src/frontend/icons/world.svg | 3 + .../scrolled/package/src/frontend/index.js | 2 + .../package/src/testHelpers/normalizeSeed.js | 10 ++- .../defaultNavigation/DefaultNavigation.js | 2 + .../defaultNavigation/TranslationsMenu.js | 60 ++++++++++++++++ .../TranslationsMenu.module.css | 50 +++++++++++++ pageflow.gemspec | 2 +- 16 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 entry_types/scrolled/package/spec/widgets/defaultNavigation/TranslationsMenu-spec.js create mode 100644 entry_types/scrolled/package/src/entryState/entryTranslations.js create mode 100644 entry_types/scrolled/package/src/frontend/icons/world.svg create mode 100644 entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.js create mode 100644 entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.module.css diff --git a/entry_types/scrolled/config/locales/de.yml b/entry_types/scrolled/config/locales/de.yml index 13c2c945bc..ded9142b36 100644 --- a/entry_types/scrolled/config/locales/de.yml +++ b/entry_types/scrolled/config/locales/de.yml @@ -1060,9 +1060,3 @@ de: type_text: Text eingeben type_title: Titel url_placeholder: URL eingeben oder einfügen - public: - third_party_consent: - opt_in_prompt: - twitter: Ich bin damit einverstanden, dass Inhalte von Twitter angezeigt werden. - twitter: - default_title: Tweet diff --git a/entry_types/scrolled/config/locales/en.yml b/entry_types/scrolled/config/locales/en.yml index 9fa366f060..ea8465fa81 100644 --- a/entry_types/scrolled/config/locales/en.yml +++ b/entry_types/scrolled/config/locales/en.yml @@ -898,9 +898,3 @@ en: type_text: Type some text type_title: Title url_placeholder: Type or paste URL - public: - third_party_consent: - opt_in_prompt: - twitter: I agree with being shown content from Twitter. - twitter: - default_title: Tweet diff --git a/entry_types/scrolled/package/spec/frontend/MediaPlayerControls-spec.js b/entry_types/scrolled/package/spec/frontend/MediaPlayerControls-spec.js index 3b5b84ae20..dc14eae73c 100644 --- a/entry_types/scrolled/package/spec/frontend/MediaPlayerControls-spec.js +++ b/entry_types/scrolled/package/spec/frontend/MediaPlayerControls-spec.js @@ -11,7 +11,7 @@ import '@testing-library/jest-dom/extend-expect'; describe('MediaPlayerControls', () => { useFakeTranslations({ - 'pageflow_scrolled.public.languages.en': 'English', + 'pageflow_scrolled.public.languages.fr': 'French', 'pageflow_scrolled.public.text_track_modes.none': 'Off', 'pageflow_scrolled.public.text_track_modes.auto_off': 'Auto' }); @@ -26,8 +26,8 @@ describe('MediaPlayerControls', () => { parentFileModelType: 'Pageflow::AudioFile', configuration: { kind: 'subtitles', - srclang: 'en', - label: 'English' + srclang: 'fr', + label: 'French' } }] }; @@ -51,9 +51,9 @@ describe('MediaPlayerControls', () => { {seed} ); - fireEvent.click(getByRole('menuitemradio', {name: 'English'})); + fireEvent.click(getByRole('menuitemradio', {name: 'French'})); - expect(getByRole('menuitemradio', {name: 'English'})).toHaveAttribute('aria-checked', 'true'); + expect(getByRole('menuitemradio', {name: 'French'})).toHaveAttribute('aria-checked', 'true'); }); it('supports turning text tracks off', () => { diff --git a/entry_types/scrolled/package/spec/frontend/useTextTracks-spec.js b/entry_types/scrolled/package/spec/frontend/useTextTracks-spec.js index 456a737915..1455a03dd0 100644 --- a/entry_types/scrolled/package/spec/frontend/useTextTracks-spec.js +++ b/entry_types/scrolled/package/spec/frontend/useTextTracks-spec.js @@ -20,6 +20,7 @@ describe('useTextTracks', () => { const videoFile2CaptionsDePermaId = 4100; const seed = { + entry: {locale: 'en'}, videoFiles: [ {id: videoFile1Id, permaId: videoFile1PermaId}, {id: videoFile2Id, permaId: videoFile2PermaId} @@ -108,7 +109,7 @@ describe('useTextTracks', () => { expect(result.current).toMatchObject({ activeFileId: videoFile1CaptionsDeId, mode: 'user', - autoDisplayLabel: 'Auto (Off)' + autoDisplayLabel: 'Auto (English)' }); }); @@ -122,7 +123,7 @@ describe('useTextTracks', () => { expect(result.current).toMatchObject({ activeFileId: null, mode: 'off', - autoDisplayLabel: 'Auto (Off)' + autoDisplayLabel: 'Auto (English)' }); }); @@ -155,12 +156,16 @@ describe('useTextTracks', () => { const {result} = renderHookInEntry(() => useTextTracks({ file: useFile({collectionName: 'videoFiles', permaId: videoFile1PermaId}), captionsByDefault: true - }), {seed}); + }), { + seed: { + ...seed, + entry: {locale: 'fr'} + } + }); expect(result.current).toMatchObject({ activeFileId: videoFile1CaptionsDeId, - mode: 'auto', - autoDisplayLabel: 'Auto (Unknown)' + mode: 'auto' }); }); @@ -170,8 +175,8 @@ describe('useTextTracks', () => { captionsByDefault: true }), { seed: { + ...seed, entry: {locale: 'en'}, - ...seed } }); diff --git a/entry_types/scrolled/package/spec/widgets/defaultNavigation/TranslationsMenu-spec.js b/entry_types/scrolled/package/spec/widgets/defaultNavigation/TranslationsMenu-spec.js new file mode 100644 index 0000000000..e0c3dadaac --- /dev/null +++ b/entry_types/scrolled/package/spec/widgets/defaultNavigation/TranslationsMenu-spec.js @@ -0,0 +1,71 @@ +import React from 'react'; +import {TranslationsMenu} from 'widgets/defaultNavigation/TranslationsMenu'; + +import '@testing-library/jest-dom/extend-expect'; +import {renderInEntry} from 'support'; + +describe('TranslationsMenu', () => { + it('marks current page in rendered list of translations', async () => { + const {getByRole} = renderInEntry(, { + seed: { + entry: { + id: 5, + }, + entryTranslations: [ + { + id: 5, + displayLocale: 'Deutsch', + url: '/entry-de' + }, + { + id: 6, + displayLocale: 'English', + url: '/entry-en' + } + ] + } + }); + + expect(getByRole('link', {name: 'English'})).toHaveAttribute('href', '/entry-en'); + expect(getByRole('listitem', {current: 'page'})).toHaveTextContent('Deutsch'); + }); + + it('includes shortend uppercased locale in button', async () => { + const {getByRole} = renderInEntry(, { + seed: { + entry: { + id: 5, + locale: 'de-ch' + }, + entryTranslations: [ + { + id: 5, + displayLocale: 'Deutsch', + url: '/entry-de' + }, + { + id: 6, + displayLocale: 'English', + url: '/entry-en' + } + ] + } + }); + + expect(getByRole('button')).toHaveTextContent(/^DE$/); + }); + + it('renders nothing if no translations are present', async () => { + const {container} = renderInEntry(, { + seed: { + entry: { + id: 5, + locale: 'de-ch' + }, + entryTranslations: [] + } + }); + + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/entry_types/scrolled/package/src/entryState/entryTranslations.js b/entry_types/scrolled/package/src/entryState/entryTranslations.js new file mode 100644 index 0000000000..5cc5add162 --- /dev/null +++ b/entry_types/scrolled/package/src/entryState/entryTranslations.js @@ -0,0 +1,6 @@ +import {useEntryStateConfig} from "./EntryStateProvider"; + +export function useEntryTranslations() { + const config = useEntryStateConfig(); + return config.entryTranslations; +} diff --git a/entry_types/scrolled/package/src/entryState/index.js b/entry_types/scrolled/package/src/entryState/index.js index 2606448607..145a0237e2 100644 --- a/entry_types/scrolled/package/src/entryState/index.js +++ b/entry_types/scrolled/package/src/entryState/index.js @@ -1,6 +1,7 @@ export {useAdditionalSeedData} from './additionalSeedData'; export {useContentElementConsentVendor} from './consentVendors'; export {useShareProviders, useShareUrl} from './sharing'; +export {useEntryTranslations} from './entryTranslations'; export {useEntryMetadata} from './metadata'; export { useEntryStructure, diff --git a/entry_types/scrolled/package/src/entryState/watchCollections.js b/entry_types/scrolled/package/src/entryState/watchCollections.js index 3bc17a57fb..e964408bff 100644 --- a/entry_types/scrolled/package/src/entryState/watchCollections.js +++ b/entry_types/scrolled/package/src/entryState/watchCollections.js @@ -8,6 +8,7 @@ export function watchCollections(entry, {dispatch}) { name: 'entries', attributes: [ 'locale', + {id: () => entry.id}, {permaId: () => entry.id}, // Make sure key attribute is present {shareProviders: 'share_providers'}, {shareUrl: 'share_url'}, diff --git a/entry_types/scrolled/package/src/frontend/ThemeIcon.js b/entry_types/scrolled/package/src/frontend/ThemeIcon.js index d4672421ac..8c2461b795 100644 --- a/entry_types/scrolled/package/src/frontend/ThemeIcon.js +++ b/entry_types/scrolled/package/src/frontend/ThemeIcon.js @@ -6,6 +6,7 @@ import share from './icons/share.svg'; import unmuted from './icons/unmuted.svg'; import gear from './icons/gear.svg'; import copyright from './icons/copyright.svg'; +import world from './icons/world.svg'; import email from './icons/social/email.svg'; import facebook from './icons/social/facebook.svg'; @@ -27,6 +28,7 @@ const icons = { unmuted, gear, copyright, + world, email, facebook, @@ -46,7 +48,7 @@ const icons = { * @param {string} props.name - * Either: copyright, expand, gear, information, muted, share, unmuted, * email, facebook, linkedIn, telegram, twitter, whatsApp, - * arrowLeft, arrowRight, + * arrowLeft, arrowRight, world * @params {number} [props.width] - Image width. * @params {number} [props.height] - Image height. */ diff --git a/entry_types/scrolled/package/src/frontend/icons/world.svg b/entry_types/scrolled/package/src/frontend/icons/world.svg new file mode 100644 index 0000000000..0470513f0f --- /dev/null +++ b/entry_types/scrolled/package/src/frontend/icons/world.svg @@ -0,0 +1,3 @@ + + + diff --git a/entry_types/scrolled/package/src/frontend/index.js b/entry_types/scrolled/package/src/frontend/index.js index cea354e538..2a4c1d6643 100644 --- a/entry_types/scrolled/package/src/frontend/index.js +++ b/entry_types/scrolled/package/src/frontend/index.js @@ -62,7 +62,9 @@ export { useAdditionalSeedData, useChapters, useCredits, + useEntryMetadata, useEntryStateDispatch, + useEntryTranslations, useFile, useFileRights, useFileWithInlineRights, diff --git a/entry_types/scrolled/package/src/testHelpers/normalizeSeed.js b/entry_types/scrolled/package/src/testHelpers/normalizeSeed.js index 014dc50390..6ca4f7cef7 100644 --- a/entry_types/scrolled/package/src/testHelpers/normalizeSeed.js +++ b/entry_types/scrolled/package/src/testHelpers/normalizeSeed.js @@ -46,10 +46,13 @@ export function normalizeSeed({ additionalSeedData, consentVendors, contentElementConsentVendors, - fileLicenses + fileLicenses, + entryTranslations } = {}) { const entries = entry ? [entry] : [{}]; - const normalizedEntries = normalizeCollection(entries); + const normalizedEntries = normalizeCollection(entries, { + locale: 'en' + }); const normalizedContentElements = normalizeCollection(contentElements, { typeName: 'textBlock', @@ -87,7 +90,8 @@ export function normalizeSeed({ }, consentVendors: consentVendors || [], contentElementConsentVendors: contentElementConsentVendors || {}, - fileLicenses: fileLicenses || {} + fileLicenses: fileLicenses || {}, + entryTranslations: entryTranslations || [] }, collections: { entries: normalizedEntries, diff --git a/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js b/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js index ac4e23ed54..c28a481b5b 100644 --- a/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js +++ b/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js @@ -16,6 +16,7 @@ import { import {HamburgerIcon} from './HamburgerIcon' import {ChapterLink} from "./ChapterLink"; import {LegalInfoMenu} from "./LegalInfoMenu"; +import {TranslationsMenu} from "./TranslationsMenu"; import {SharingMenu} from "./SharingMenu"; import {ToggleMuteButton} from './ToggleMuteButton'; import {Logo} from './Logo'; @@ -136,6 +137,7 @@ export function DefaultNavigation({configuration}) {
{!configuration.hideToggleMuteButton && } + {!theme.options.hideLegalInfoButton &&} {!hideSharingButton && }
diff --git a/entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.js b/entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.js new file mode 100644 index 0000000000..6ae539a9bd --- /dev/null +++ b/entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.js @@ -0,0 +1,60 @@ +import React from 'react'; +import classNames from 'classnames'; +import headerStyles from "./DefaultNavigation.module.css"; +import styles from "./TranslationsMenu.module.css"; + +import { + ThemeIcon, + Tooltip, + useI18n, + useEntryMetadata, + useEntryTranslations +} from 'pageflow-scrolled/frontend'; + +export function TranslationsMenu({tooltipOffset = 0}) { + const {t} = useI18n(); + const entry = useEntryMetadata(); + const translations = useEntryTranslations(); + + if (translations.length < 2) { + return null; + } + + const content = ( +
+
    + {translations.map(({id, url, displayLocale}) => { + if (entry.id === id) { + return ( +
  • + {displayLocale} +
  • + ); + } + else { + return ( +
  • + {displayLocale} +
  • + ); + } + })} +
+
+ ); + + return ( + + + + ) +} diff --git a/entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.module.css b/entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.module.css new file mode 100644 index 0000000000..cd169920cd --- /dev/null +++ b/entry_types/scrolled/package/src/widgets/defaultNavigation/TranslationsMenu.module.css @@ -0,0 +1,50 @@ +.tooltip { + margin: -5px 0; +} + +.list { + list-style: none; + padding: 0; + margin: 0; +} + +.list > li { + margin: 0; + border-top: solid 1px var(--default-navigation-separator-color); +} + +.list > li:first-child { + border-top: none; +} + +.list a, +.list strong { + display: block; + font-size: 16px; + font-weight: bold; + text-decoration: none; + line-height: 42px; +} + +.list a { + color: var(--theme-widget-primary-color); +} + +.list strong { + color: var(--theme-accent-color); +} + +.tag { + position: absolute; + bottom: 10px; + right: 5px; + background: currentColor; + border-radius: 3px; + font-size: 11px; + padding: 2px; + font-weight: bold; +} + +.tag > span { + color: #fff; +} diff --git a/pageflow.gemspec b/pageflow.gemspec index 0ba0803cb5..713a96f375 100644 --- a/pageflow.gemspec +++ b/pageflow.gemspec @@ -130,7 +130,7 @@ Gem::Specification.new do |s| s.add_dependency 'http_accept_language', '~> 2.0' # Shared translations - s.add_dependency 'pageflow-public-i18n', '~> 1.23' + s.add_dependency 'pageflow-public-i18n', '~> 1.24' # Password encryption s.add_dependency 'bcrypt', '~> 3.1.7'