/** * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved. * Node module: @schukai/monster * * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3). * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html * * For those who do not wish to adhere to the AGPLv3, a commercial license is available. * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms. * For more information about purchasing a commercial license, please contact schukai GmbH. */ import { instanceSymbol } from "../../constants.mjs"; import { addAttributeToken } from "../../dom/attributes.mjs"; import { ATTRIBUTE_ERRORMESSAGE, ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; import { CustomControl } from "../../dom/customcontrol.mjs"; import { CustomElement, updaterTransformerMethodsSymbol, } from "../../dom/customelement.mjs"; import { assembleMethodSymbol, registerCustomElement, } from "../../dom/customelement.mjs"; import { findTargetElementFromEvent } from "../../dom/events.mjs"; import { isFunction, isObject } from "../../types/is.mjs"; import { LocalePickerStyleSheet } from "./stylesheet/locale-picker.mjs"; import { fireCustomEvent } from "../../dom/events.mjs"; import { detectUserLanguagePreference } from "../../i18n/util.mjs"; import { Formatter } from "../../text/formatter.mjs"; import "../form/button.mjs"; import "../form/select.mjs"; export { LocalePicker }; /** * @private * @type {symbol} */ const localePickerElementSymbol = Symbol("localePickerElement"); /** * @private * @type {symbol} */ const otherLanguagesElementSymbol = Symbol("otherLanguagesElement"); /** * @private * @type {symbol} */ const buttonLanguageElementSymbol = Symbol("buttonLanguageElement"); /** * @private * @type {symbol} */ const buttonNoThanksElementSymbol = Symbol("buttonNoThanksElement"); /** * @private * @type {symbol} */ const detectedLanguagesSymbol = Symbol("detectedLanguages"); /** * A LocalePicker * * @fragments /fragments/components/accessibility/locale-picker/ * * @example /examples/components/accessibility/locale-picker-simple Simple example * @example /examples/components/accessibility/locale-picker-reset Reset Selection * * @issue https://localhost.alvine.dev:8443/development/issues/closed/276.html * * @since 3.97.0 * @copyright schukai GmbH * @summary A beautiful LocalePicker that can make your life easier and also looks good. */ class LocalePicker extends CustomElement { /** * This method is called by the `instanceof` operator. * @returns {symbol} */ static get [instanceSymbol]() { return Symbol.for( "@schukai/monster/components/accessibility/locale-picker@@instance", ); } /** * * @return {LocalePicker} */ [assembleMethodSymbol]() { super[assembleMethodSymbol](); initControlReferences.call(this); initEventHandler.call(this); return this; } /** * To set the options via the HTML Tag, the attribute `data-monster-options` must be used. * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} * * The individual configuration values can be found in the table. * * @property {Object} templates Template definitions * @property {string} templates.main Main template * @property {Object} labels Label definitions * @property {string} labels.headline Headline * @property {string} labels.text Text * @property {string} labels.button-label Button label * @property {string} labels.button-no-thanks Button no thanks * @property {string} labels.headline-other Headline other languages * @property {Function} callbacks.getTranslation Callback to get the translation for the labels * @property {Object} features Features * @property {boolean} features.removeOnSelected Remove the element when a language is selected * @property {boolean} features.showAlways Show the element always * @property {boolean} disabled Disabled state */ get defaults() { return Object.assign({}, super.defaults, { templates: { main: getTemplate(), }, labels: { headline: "Welcome to our Website", text: "This page is currently displayed in ${currentLabel}. However, we also offer this page in your preferred language. Would you like to switch?", "button-label": "Switch to ${preferred.label}", "button-no-thanks": "No, thanks", "headline-other": "Other languages", }, callbacks: { getTranslation: getTranslations, }, disabled: false, features: { removeOnSelected: false, showAlways: false, }, }); } /** * Private method that provides a mapping of transformer methods keyed by their names. * These transformer methods define custom transformations for given input values. * * @private * @return {Object} An object containing transformer methods for internal use. */ [updaterTransformerMethodsSymbol]() { return { "replace-placeholder": (value) => { const formatter = new Formatter(this[detectedLanguagesSymbol]); return formatter.format(value); }, }; } /** * Lifecycle method that is called when the custom element is appended into a document-connected element. * Invokes the parent class's connectedCallback method and retrieves the user's preferred language. * Logs the preferred language to the console. * * @return {void} */ connectedCallback() { super.connectedCallback(); this[detectedLanguagesSymbol] = detectUserLanguagePreference(); if ( !isObject(this[detectedLanguagesSymbol]?.preferred) && this.getOption("features.showAlways") !== true ) { this.hide(); if (this.getOption("features.removeOnSelected")) { this.remove(); } return; } if (!isObject(this[detectedLanguagesSymbol]?.preferred)) { this[detectedLanguagesSymbol].preferred = { fullLang: "en", baseLang: "en", label: "English", }; } const stored = localStorage.getItem(buildStorageKey.call(this)); if (stored && this.getOption("features.showAlways") !== true) { if (this.getOption("features.removeOnSelected")) { this.remove(); } return; } this.show(); if ( this[otherLanguagesElementSymbol] instanceof HTMLElement && this[detectedLanguagesSymbol].offerable && this[detectedLanguagesSymbol].offerable.length > 1 ) { this[otherLanguagesElementSymbol].classList.remove("hidden"); this[otherLanguagesElementSymbol].setOption( "mapping.labelTemplate", "${label}", ); this[otherLanguagesElementSymbol].setOption( "mapping.valueTemplate", "${href}", ); this[otherLanguagesElementSymbol].importOptions( this[detectedLanguagesSymbol]?.offerable || [], ); } if ( this[detectedLanguagesSymbol].offerable && this[detectedLanguagesSymbol].offerable.length > 0 ) { const getTranslationCallback = this.getOption("callbacks.getTranslation"); if (isFunction(getTranslationCallback)) { const translations = getTranslationCallback( this[detectedLanguagesSymbol].offerable[0].baseLang, ); this.setOption("labels", translations.labels); if (this[otherLanguagesElementSymbol]) { this[otherLanguagesElementSymbol].setOption( "labels.select-an-option", translations.selectAnOption, ); } } } } /** * Resets the locale picker by removing the stored value from the local storage. * * @returns {LocalePicker} */ reset() { localStorage.removeItem(buildStorageKey.call(this)); this.show(); return this; } /** * Hides the locale picker. * * @returns {LocalePicker} */ hide() { this.style.display = "none"; if (!this[localePickerElementSymbol]) { return this; } this[localePickerElementSymbol].style.display = "none"; return this; } /** * Shows the locale picker. * @returns {LocalePicker} */ show() { this.style.display = "block"; if (!this[localePickerElementSymbol]) { return this; } this[localePickerElementSymbol].style.display = "block"; return this; } /** * @return {string} */ static getTag() { return "monster-locale-picker"; } /** * @return {CSSStyleSheet[]} */ static getCSSStyleSheet() { return [LocalePickerStyleSheet]; } } /** * @private * @return {initEventHandler} */ function initEventHandler() { const self = this; const element = this[localePickerElementSymbol]; const type = "click"; element.addEventListener(type, function (event) { const callback = self.getOption("actions.click"); fireCustomEvent(self, "monster-locale-picker-clicked", { element: self, }); if (!isFunction(callback)) { return; } const element = findTargetElementFromEvent( event, ATTRIBUTE_ROLE, "control", ); if (!(element instanceof Node && self.hasNode(element))) { return; } callback.call(self, event); }); this[buttonNoThanksElementSymbol].setOption("actions.click", () => { localStorage.setItem(buildStorageKey.call(this), "1"); this.hide(); if (this.getOption("features.removeOnSelected")) { this.remove(); } }); this[buttonLanguageElementSymbol].setOption("actions.click", () => { localStorage.setItem(buildStorageKey.call(this), "1"); window.location.href = this[detectedLanguagesSymbol].offerable?.[0]?.href; }); this[otherLanguagesElementSymbol].addEventListener("change", (event) => { const element = findTargetElementFromEvent( event, ATTRIBUTE_ROLE, "other-languages", ); if (element) { const selected = element?.value; if (selected) { localStorage.setItem(buildStorageKey.call(this), "1"); window.location.href = selected; } } }); return this; } /** * @private * @returns {string} */ function buildStorageKey() { return "locale-picker-" + this[detectedLanguagesSymbol].current; } /** * @private * @param lang * @returns {Object} */ function getTranslations(lang) { const map = { en: { headline: "Welcome to our Website", text: "This page is currently displayed in ${currentLabel}. However, we also offer this page in your preferred language. Would you like to switch?", "button-label": "Switch to ${preferred.label}", "button-no-thanks": "No, thanks", "headline-other": "Other languages", }, de: { headline: "Willkommen auf unserer Webseite", text: "Diese Seite wird aktuell auf ${currentLabel} angezeigt. Wir bieten jedoch auch diese Seite in Ihrer bevorzugten Sprache an. Möchten Sie wechseln?", "button-label": "Wechseln zu ${preferred.label}", "button-no-thanks": "Nein, danke", "headline-other": "Andere Sprachen", }, fr: { headline: "Bienvenue sur notre site web", text: "Cette page est actuellement affichée en ${currentLabel}. Cependant, nous proposons également cette page dans votre langue préférée. Souhaitez-vous changer?", "button-label": "Changer pour ${preferred.label}", "button-no-thanks": "Non, merci", "headline-other": "Autres langues", }, es: { headline: "Bienvenido a nuestro sitio web", text: "Esta página se muestra actualmente en ${currentLabel}. Sin embargo, también ofrecemos esta página en su idioma preferido. ¿Le gustaría cambiar?", "button-label": "Cambiar a ${preferred.label}", "button-no-thanks": "No, gracias", "headline-other": "Otros idiomas", }, it: { headline: "Benvenuti sul nostro sito web", text: "Questa pagina è attualmente visualizzata in ${currentLabel}. Tuttavia, offriamo anche questa pagina nella tua lingua preferita. Vuoi cambiare?", "button-label": "Cambia nella ${preferred.label}", "button-no-thanks": "No, grazie", "headline-other": "Altre lingue", }, pt: { headline: "Bem-vindo ao nosso site", text: "Esta página está atualmente exibida em ${currentLabel}. No entanto, também oferecemos esta página no seu idioma preferido. Gostaria de mudar?", "button-label": "Mudar para ${preferred.label}", "button-no-thanks": "Não, obrigado", "headline-other": "Outros idiomas", }, nl: { headline: "Welkom op onze website", text: "Deze pagina wordt momenteel weergegeven in ${currentLabel}. We bieden deze pagina echter ook aan in uw voorkeurstaal. Wilt u overschakelen?", "button-label": "Overschakelen naar ${preferred.label}", "button-no-thanks": "Nee, bedankt", "headline-other": "Andere talen", }, pl: { headline: "Witamy na naszej stronie", text: "Ta strona jest obecnie wyświetlana po ${currentLabel}. Oferujemy jednak również tę stronę w Twoim preferowanym języku. Czy chcesz przełączyć?", "button-label": "Przełącz na ${preferred.label}", "button-no-thanks": "Nie, dziękuję", "headline-other": "Inne języki", }, ru: { headline: "Добро пожаловать на наш сайт", text: "Эта страница в настоящее время отображается на ${currentLabel}. Однако мы также предлагаем эту страницу на вашем предпочтительном языке. Хотите переключиться?", "button-label": "Переключиться на ${preferred.label}", "button-no-thanks": "Нет, спасибо", "headline-other": "Другие языки", }, cs: { headline: "Vítejte na našem webu", text: "Tato stránka je aktuálně zobrazena v ${currentLabel}. Nabízíme však tuto stránku také ve vašem preferovaném jazyce. Chcete přejít?", "button-label": "Přejít na ${preferred.label}", "button-no-thanks": "Ne, děkuji", "headline-other": "Další jazyky", }, sk: { headline: "Vitajte na našej webovej stránke", text: "Táto stránka je v súčasnosti zobrazená v ${currentLabel}. Ponúkame však túto stránku aj vo vašom preferovanom jazyku. Chcete prejsť?", "button-label": "Prepnúť na ${preferred.label}", "button-no-thanks": "Nie, ďakujem", "headline-other": "Iné jazyky", }, bg: { headline: "Добре дошли на нашия уебсайт", text: "Тази страница в момента се показва на ${currentLabel}. Въпреки това, предлагаме също тази страница на Вашия предпочитан език. Желаете ли да превключите?", "button-label": "Превключете на ${preferred.label}", "button-no-thanks": "Не, благодаря", "headline-other": "Други езици", }, hr: { headline: "Dobrodošli na našu web stranicu", text: "Ova stranica trenutno je prikazana na ${currentLabel}. Međutim, nudimo i ovu stranicu na vašem preferiranom jeziku. Želite li prebaciti?", "button-label": "Prebaci na ${preferred.label}", "button-no-thanks": "Ne, hvala", "headline-other": "Drugi jezici", }, fi: { headline: "Tervetuloa verkkosivustollemme", text: "Tämä sivu on tällä hetkellä näkyvissä ${currentLabel}. Tarjoamme kuitenkin tätä sivua myös suosimallasi kielellä. Haluaisitko vaihtaa?", "button-label": "Vaihda ${preferred.label}", "button-no-thanks": "Ei kiitos", "headline-other": "Muut kielet", }, sv: { headline: "Välkommen till vår webbplats", text: "Denna sida visas för närvarande på ${currentLabel}. Vi erbjuder dock även denna sida på ditt föredragna språk. Skulle du vilja byta?", "button-label": "Byt till ${preferred.label}", "button-no-thanks": "Nej tack", "headline-other": "Andra språk", }, el: { headline: "Καλώς ήρθατε στην ιστοσελίδα μας", text: "Αυτή η σελίδα εμφανίζεται προς το παρόν στα ${currentLabel}. Ωστόσο, προσφέρουμε επίσης αυτή τη σελίδα στην προτιμώμενη γλώσσα σας. Θα θέλατε να αλλάξετε;", "button-label": "Αλλαγή σε ${preferred.label}", "button-no-thanks": "Όχι, ευχαριστώ", "headline-other": "Άλλες γλώσσες", }, hu: { headline: "Üdvözöljük weboldalunkon", text: "Ez az oldal jelenleg ${currentLabel} nyelven jelenik meg. Azonban kínáljuk ezt az oldalt a preferált nyelvén is. Szeretne váltani?", "button-label": "Váltás ${preferred.label} nyelvre", "button-no-thanks": "Nem, köszönöm", "headline-other": "További nyelvek", }, ro: { headline: "Bine ați venit pe site-ul nostru", text: "Această pagină este afișată în prezent în ${currentLabel}. Totuși, oferim de asemenea această pagină în limba dumneavoastră preferată. Doriți să schimbați?", "button-label": "Schimbați în ${preferred.label}", "button-no-thanks": "Nu, mulțumesc", "headline-other": "Alte limbi", }, da: { headline: "Velkommen til vores hjemmeside", text: "Denne side vises i øjeblikket på ${currentLabel}. Vi tilbyder dog også denne side på dit foretrukne sprog. Vil du skifte?", "button-label": "Skift til ${preferred.label}", "button-no-thanks": "Nej tak", "headline-other": "Andre sprog", }, no: { headline: "Velkommen til vår nettside", text: "Denne siden vises for øyeblikket på ${currentLabel}. Vi tilbyr imidlertid også denne siden på ditt foretrukne språk. Ønsker du å bytte?", "button-label": "Bytt til ${preferred.label}", "button-no-thanks": "Nei, takk", "headline-other": "Andre språk", }, hi: { headline: "हमारी वेबसाइट पर आपका स्वागत है", text: "यह पृष्ठ वर्तमान में ${currentLabel} में प्रदर्शित हो रहा है। हालांकि, हम इस पृष्ठ को आपकी पसंदीदा भाषा में भी प्रदान करते हैं। क्या आप स्विच करना चाहेंगे?", "button-label": "${preferred.label} में स्विच करें", "button-no-thanks": "नहीं, धन्यवाद", "headline-other": "अन्य भाषाएँ", }, bn: { headline: "আমাদের ওয়েবসাইটে আপনাকে স্বাগতম", text: "এই পৃষ্ঠাটি বর্তমানে ${currentLabel} প্রদর্শিত হচ্ছে। তবে, আমরা এই পৃষ্ঠাটি আপনার পছন্দের ভাষায়ও অফার করি। আপনি কি সুইচ করতে চান?", "button-label": "${preferred.label}-এ সুইচ করুন", "button-no-thanks": "না, ধন্যবাদ", "headline-other": "অন্যান্য ভাষাসমূহ", }, ta: { headline: "எங்கள் இணையதளத்திற்கு வருக", text: "இந்த பக்கம் தற்போது ${currentLabel} என்ற மொழியில் காட்சியளிக்கப்படுகிறது. எனினும், நாங்கள் இந்த பக்கத்தை உங்கள் விருப்ப மொழியிலும் வழங்குகிறோம். நீங்கள் மாற்ற விரும்புகிறீர்களா?", "button-label": "${preferred.label}-க்கு மாற்றவும்", "button-no-thanks": "இல்லை, நன்றி", "headline-other": "மற்ற மொழிகள்", }, te: { headline: "మా వెబ్సైట్కు స్వాగతం", text: "ఈ పేజీ ప్రస్తుతం ${currentLabel}లో ప్రదర్శితం అవుతున్నది. అయితే, మేము ఈ పేజీని మీ ఇష్టపడే భాషలో కూడా అందిస్తున్నాము. మీరు మార్చాలనుకుంటున్నారా?", "button-label": "${preferred.label}కి మార్చండి", "button-no-thanks": "కాదు, ధన్యవాదాలు", "headline-other": "ఇతర భాషలు", }, mr: { headline: "आपले आमच्या वेबसाइटवर स्वागत आहे", text: "हे पान सध्या ${currentLabel}मध्ये दाखविले जात आहे. परंतु, आम्ही हे पान आपल्या पसंतीच्या भाषेतही देत आहोत. आपण स्विच करू इच्छिता का?", "button-label": "${preferred.label}मध्ये स्विच करा", "button-no-thanks": "नाही, धन्यवाद", "headline-other": "इतर भाषा", }, zh: { headline: "欢迎访问我们的网站", text: "本页面当前显示为${currentLabel}。然而,我们还提供您偏好的语言版本。您想切换吗?", "button-label": "切换到${preferred.label}", "button-no-thanks": "不,谢谢", "headline-other": "其他语言", }, ja: { headline: "私たちのウェブサイトへようこそ", text: "このページは現在${currentLabel}で表示されています。しかし、私たちはあなたの好みの言語でこのページを提供しています。切り替えますか?", "button-label": "${preferred.label}に切り替える", "button-no-thanks": "いいえ、結構です", "headline-other": "他の言語", }, }; const selectAnOption = { en: "Select a language", de: "Wählen Sie eine Sprache", fr: "Sélectionnez une langue", es: "Seleccione un idioma", it: "Seleziona una lingua", pt: "Selecione um idioma", nl: "Selecteer een taal", pl: "Wybierz język", ru: "Выберите язык", cs: "Vyberte jazyk", sk: "Vyberte jazyk", bg: "Изберете език", hr: "Odaberite jezik", fi: "Valitse kieli", sv: "Välj ett språk", el: "Επιλέξτε γλώσσα", hu: "Válasszon egy nyelvet", ro: "Selectați o limbă", da: "Vælg et sprog", no: "Velg et språk", hi: "एक भाषा चुनें", bn: "একটি ভাষা নির্বাচন করুন", ta: "ஒரு மொழியைத் தேர்ந்தெடுக்கவும்", te: "భాషను ఎంచుకోండి", mr: "एक भाषा निवडा", zh: "选择一种语言", ja: "言語を選択してください", }; const result = {}; if (map[lang]) { result["labels"] = map[lang]; } else { result["labels"] = map["en"]; } if (selectAnOption[lang]) { result["selectAnOption"] = selectAnOption[lang]; } else { result["selectAnOption"] = selectAnOption["en"]; } return result; } /** * @private * @return {void} */ function initControlReferences() { this[localePickerElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}="control"]`, ); this[otherLanguagesElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}="other-languages"]`, ); this[buttonNoThanksElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}="button-no-thanks"]`, ); this[buttonLanguageElementSymbol] = this.shadowRoot.querySelector( `[${ATTRIBUTE_ROLE}="button-language"]`, ); } /** * @private * @return {string} */ function getTemplate() { // language=HTML return ` <div data-monster-role="control" part="control"> <h2 data-monster-role="headline" part="headline" data-monster-replace="path:labels.headline"></h2> <p data-monster-replace="path:labels.text | call:replace-placeholder"></p> <div data-monster-role="container" part="container"> <monster-button data-monster-role="button-language" data-monster-replace="path:labels.button-label | call:replace-placeholder"></monster-button> <monster-button data-monster-role="button-no-thanks" data-monster-replace="path:labels.button-no-thanks | call:replace-placeholder"></monster-button> <monster-select class="hidden" data-monster-role="other-languages"></monster-select> </div> </div>`; } registerCustomElement(LocalePicker);