Skip to content
Snippets Groups Projects
Verified Commit b5b7b294 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

feat: new language control #276

parent 873f9d2b
No related branches found
No related tags found
No related merge requests found
/**
* 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} 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
*
* @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 {Components.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 {Object} actions Callbacks
* @property {string} actions.click="throw Error" Callback when clicked
* @property {Object} features Features
* @property {boolean} features.removeOnSelected=false Remove the element when a language is selected
* @property {Object} classes CSS classes
* @property {boolean} disabled=false 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",
},
classes: {},
disabled: false,
features: {
removeOnSelected: false,
},
actions: {
click: () => {
throw new Error("the click action is not defined");
},
},
});
}
/**
* Private method that provides a mapping of transformer methods keyed by their names.
* These transformer methods define custom transformations for given input values.
*
* @returns {Object} An object containing transformer methods. Each method is keyed by its name.
* The provided transformer "my-transformer" processes an input value based on its type:
* - Appends "!" if the value is a string.
* - Increments the value by 1 if the type is "Zahl".
* - Returns the value unchanged for other types.
*/
[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} This method does not return a value.
*/
connectedCallback() {
super.connectedCallback();
this[detectedLanguagesSymbol] = detectUserLanguagePreference();
const stored = localStorage.getItem(buildStorageKey.call(this));
if (stored) {
if (this.getOption("features.removeOnSelected")) {
this.remove();
}
return;
}
this.style.display = "block";
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 || []);
}
const translations = getTranslation(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));
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.style.display = "none";
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 getTranslation(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);
/** generated from locale-picker.pcss **/
@import "../../style/normalize.pcss";
@import "../../style/display.pcss";
@import "../../style/mixin/button.pcss";
@import "../../style/button.pcss";
@import "../../style/mixin/typography.pcss";
@import "../../style/mixin/hover.pcss";
@import "../../style/control.pcss";
@import "../../style/property.pcss";
@import "../../style/ripple.pcss";
[data-monster-role=container] {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto;
gap: 0.3rem;
}
[data-monster-role=button-language],
[data-monster-role=button-no-thanks] {
grid-row: 1;
}
[data-monster-role=other-languages] {
grid-column: 1 / -1;
grid-row: 2;
}
\ No newline at end of file
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment