diff --git a/README.md b/README.md index 11c8651..ecb59f9 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,17 @@ On `config.json`: "client": { "favicon": "/images/favicon.webp", "css": "/path/to/custom.css", + "i18n": { + "lang": "fr", + "force": "false", + "files": [ + { + "name": "yourCustomLangName", + "file": "/path/to/file.json" + }, + ... + ] + } } } ``` @@ -126,6 +137,10 @@ Where: * `client.favicon` is an URL to a distant favicon * `client.css` is an URL to a distant CSS that'll be the last one loaded by the Hubl +* `client.i18n.lang` is the fallback langage in case the visitor's browser one does not contain the string +* `client.i18n.force` allows to ignore the visitor's browser langage and force the `client.i18n.lang` one +* `client.i18n.files[].name` allows to use custom client lang file. +* `client.i18n.files[].file` is the path the the json lang file ### Allow to login to your application @@ -144,6 +159,56 @@ Where: * `authority` is the OpenID Provider. Usually, if you use `djangoldp_account` it's the same as your djangoldp server. +### Custom lang files + +Each client can overwrite langs files with their own or even create custom langs. + +#### Overwrite langs + +On `config.json`: + +```json +{ + "client": { + "i18n": { + "files": [ + { + "name": "fr", + "file": "/path/to/custom-fr.json" + } + ] + } + } +} +``` + +#### Custom langs + +Needs `client.i18n.force` to `true` and `client.i18n.lang` to the custom lang name. + +Your custom JSON file **must** contain every keys, from the template and from every bloxes, prefixed by the blox namespace. See example on `src/locales/fr.json`. + +On `config.json`: + +```json +{ + "client": { + "i18n": { + "lang": "pirate", + "force": "true", + "files": [ + { + "name": "pirate", + "file": "/path/to/yarr.json" + } + ] + } + } +} +``` + +Setting custom langs will not allow user to choose their own lang. + ## Optional modules ### Adding modules @@ -573,6 +638,14 @@ Components can get the route of a module with `window.hubl.getRoute('componentNa Did you properly created subscriptions on your DjangoLDP's server? You can quickly create them with `./manage.py create_subscriptions` +### I see some technical strings instead of my fallback language strings + +By default, the template fallback to `fr` lang, which is the most complete. If you're using your own fallback lang within your configuration, please ensure that this lang is completly translated. + +### I see some technical strings instead of my custom language + +Using a custom language file does not allow to use the default fallback language. + ## Built With * [Sib-Core](https://git.startinblox.com/framework/sib-core/) - A SOLID-Compliant framework diff --git a/package-lock.json b/package-lock.json index dd775bb..935f8bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1159,9 +1159,9 @@ } }, "@startinblox/hubl-styling-framework": { - "version": "1.8.8", - "resolved": "https://registry.npmjs.org/@startinblox/hubl-styling-framework/-/hubl-styling-framework-1.8.8.tgz", - "integrity": "sha512-SKVRUV95f45qIGSgpIMvgbQaZ6mPNNCXQY+6LlU18lGMvq0vwQ3AmOuTkp4VbfUWvSLYXTUE1N61YmIFikMIfw==" + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/@startinblox/hubl-styling-framework/-/hubl-styling-framework-1.8.10.tgz", + "integrity": "sha512-3mT3H9Wgrgvja0JVu9TLEuvz1OtTeiN0UHuZx5IPLFDk5njGoDldElFEr3nw+BOMY1nJzSb8kHqW5cyIsP80cQ==" }, "@types/q": { "version": "1.5.4", diff --git a/package.json b/package.json index 2eaf66c..cfebc9f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ ] }, "dependencies": { - "@startinblox/hubl-styling-framework": "^1.8.8", + "@startinblox/hubl-styling-framework": "^1.8.10", "cross-env": "^7.0.3", "fs-extra": "^9.0.1", "normalize.css": "^8.0.1", diff --git a/src/components/getRoute.js b/src/components/getRoute.js index 20fe2cb..60fb9d8 100644 --- a/src/components/getRoute.js +++ b/src/components/getRoute.js @@ -1,5 +1,5 @@ window.hubl.getRoute = (type, returnFirst = false) => { - let availables = window.hubl.components.filter(c => c.type == type); + let availables = window.hubl.components.filter(c => c.type == type || c.uniq == type); if (availables.length > 1) { if (returnFirst) { return availables[0].route; diff --git a/src/components/hubl-auto-login.js b/src/components/hubl-auto-login.js index 2ef3268..2d20afa 100644 --- a/src/components/hubl-auto-login.js +++ b/src/components/hubl-auto-login.js @@ -1,6 +1,6 @@ import { Sib -} from 'https://cdn.skypack.dev/@startinblox/core@0.15'; +} from 'https://cdn.skypack.dev/@startinblox/core@0.16'; export const HublAutoLogin = { name: 'hubl-auto-login', diff --git a/src/components/hubl-reactivity.js b/src/components/hubl-reactivity.js index 79c134d..8c566e3 100644 --- a/src/components/hubl-reactivity.js +++ b/src/components/hubl-reactivity.js @@ -2,7 +2,7 @@ import { store, Sib, StoreMixin -} from 'https://cdn.skypack.dev/@startinblox/core@0.15'; +} from 'https://cdn.skypack.dev/@startinblox/core@0.16'; export const HublReactivity = { name: 'hubl-reactivity', diff --git a/src/components/hubl-search-users.js b/src/components/hubl-search-users.js index 0153735..9bb88bd 100644 --- a/src/components/hubl-search-users.js +++ b/src/components/hubl-search-users.js @@ -1,6 +1,6 @@ import { widgetFactory -} from 'https://cdn.skypack.dev/@startinblox/core@0.15'; +} from 'https://cdn.skypack.dev/@startinblox/core@0.16'; const HublSearchUsers = widgetFactory( 'hubl-search-users', diff --git a/src/components/hubl-status.js b/src/components/hubl-status.js index f6d6fc0..299b882 100644 --- a/src/components/hubl-status.js +++ b/src/components/hubl-status.js @@ -1,7 +1,7 @@ import { widgetFactory, Helpers -} from 'https://cdn.skypack.dev/@startinblox/core@0.15'; +} from 'https://cdn.skypack.dev/@startinblox/core@0.16'; import SlimSelect from 'https://cdn.skypack.dev/slim-select@1.23'; const HublStatus = widgetFactory( diff --git a/src/dependencies.pug b/src/dependencies.pug index 8e6040e..b0ae0cd 100644 --- a/src/dependencies.pug +++ b/src/dependencies.pug @@ -1,4 +1,4 @@ -script(type="module" src="https://cdn.skypack.dev/@startinblox/core@0.15" defer) +script(type="module" src="https://cdn.skypack.dev/@startinblox/core@0.16" defer) //- script(type="module" src="/lib/sib-core/dist/index.js" defer) script(type="module" src="https://cdn.skypack.dev/@startinblox/router@0.11" defer) @@ -7,45 +7,48 @@ script(type="module" src="https://cdn.skypack.dev/@startinblox/router@0.11" defe - const componentSet = new Set(components.map(c=>c.type)); if componentSet.has("autoLogin") || componentSet.has("registering") - script(type="module" src="https://cdn.skypack.dev/@startinblox/oidc@0.13" defer) + script(type="module" src="https://cdn.skypack.dev/@startinblox/oidc@0.14" defer) //- script(type="module" src="/lib/sib-auth/index.js" defer) if componentSet.has("chat") || componentSet.has("circles") || componentSet.has("projects") - script(type="module" src="https://cdn.skypack.dev/@startinblox/component-chat@4.2" defer) + script(type="module" src="https://cdn.skypack.dev/@startinblox/component-chat@5.0" defer) //- script(type="module" src="/lib/solid-xmpp-chat/dist/index.js" defer) if componentSet.has("dashboard") - script(type="module" src="https://cdn.skypack.dev/@startinblox/component-dashboard@3.1" defer) + script(type="module" src="https://cdn.skypack.dev/@startinblox/component-dashboard@4.0" defer) //- script(type="module" src="/lib/solid-dashboard/dist/index.js" defer) -if componentSet.has("events") +//- Disabled - Not in core@0.16 +//- if componentSet.has("events") script(type="module", src="https://cdn.skypack.dev/@startinblox/component-event@2.1", defer) //- script(type="module", src="/lib/solid-event/solid-event.js", defer) -if componentSet.has("events") || componentSet.has("polls") || componentSet.has("resources") +//- Disabled - Not in core@0.16 +//- if componentSet.has("events") || componentSet.has("polls") || componentSet.has("resources") script(type="module" src="https://cdn.skypack.dev/@startinblox/component-conversation@0.9" defer) if componentSet.has("jobBoard") - script(type="module" src="https://cdn.skypack.dev/@startinblox/component-job-board@4.1" defer) + script(type="module" src="https://cdn.skypack.dev/@startinblox/component-job-board@5.0" defer) //- script(type="module" src="/lib/solid-job-board/dist/index.js" defer) if componentSet.has("notification") - script(type="module" src="https://cdn.skypack.dev/@startinblox/component-notifications@0.11" defer) + script(type="module" src="https://cdn.skypack.dev/@startinblox/component-notifications@0.12" defer) //- script(type="module" src="/lib/sib-notifications/index.js" defer) -//- Disabled - Not in core@0.15 +//- Disabled - Not in core@0.16 //- if componentSet.has("polls") //- script(type="module" src="https://cdn.skypack.dev/@startinblox/component-poll@1.2" defer) //- //- script(type="module" src="/lib/sib-polls-component/index.js" defer) -if componentSet.has("resources") +if componentSet.has("profileDirectory") + script(type="module" src="https://cdn.skypack.dev/@startinblox/component-directory@5.0" defer) + //- script(type="module" src="/lib/solid-directory/dist/index.js" defer) + +//- Disabled - Not in core@0.16 +//- if componentSet.has("resources") script(type="module", src="https://cdn.skypack.dev/@startinblox/component-resource@2.0", defer) //- script(type="module" src="/lib/solid-resource/solid-resource.js" defer) if componentSet.has("themeChecker") script(src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js" defer) link(rel='stylesheet', href='https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css') - -if componentSet.has("profileDirectory") - script(type="module" src="https://cdn.skypack.dev/@startinblox/component-directory@4.1" defer) - //- script(type="module" src="/lib/solid-directory/dist/index.js" defer) diff --git a/src/hubl-router.pug b/src/hubl-router.pug index 3d0fb8d..a4eada9 100644 --- a/src/hubl-router.pug +++ b/src/hubl-router.pug @@ -5,12 +5,13 @@ Eg. ``` window.hubl.getRoute('chat', true) + window.hubl.getRoute('uxnzsa') // Where uxnzsa is the uniq of the component ``` will return the route of the first chat component, if exists, or triggers an error. - let routes = new Set(); const getRoute = (type, returnFirst = false) => { - let availables = components.filter(c=>c.type==type); + let availables = components.filter(c=>c.type==type||c.uniq==type); if(availables.length > 1) { if(returnFirst) { return availables[0].route; @@ -52,5 +53,12 @@ for component of components routes.add(route); component.route = route; } -- const hublComponents = `window.hubl={};window.hubl.components = ${JSON.stringify(components)};`; +- + const defaultComponent = components.filter(e=>e.defaultRoute != undefined); + let defaultRoute = "dashboard"; + if(defaultComponent.length == 1) { + defaultRoute = defaultComponent[0].uniq; + } + +- const hublComponents = `window.hubl={};window.hubl.components = ${JSON.stringify(components)};window.hubl.defaultRoute = "${defaultRoute}";`; script!=hublComponents \ No newline at end of file diff --git a/src/index.pug b/src/index.pug index b20fa89..cc35403 100644 --- a/src/index.pug +++ b/src/index.pug @@ -33,6 +33,7 @@ html(lang="en") script(type="module" src="/components/hubl-search-users.js" defer) script(type="module" src="/components/hubl-status.js" defer) script(type="module" src="/components/hubl-reactivity.js" defer) + script(src="index.js" defer) include dependencies.pug include context.pug @@ -40,7 +41,6 @@ html(lang="en") //- swal2 does not work with skypack script(src="https://cdn.jsdelivr.net/npm/sweetalert2@10" defer) - script(src="index.js" defer) body.bg-color-grey @@ -196,6 +196,19 @@ html(lang="en") span(data-trans="errors.somethingGoesWrong") span   a(data-trans='errors.reload' href='/') + + if client.i18n + hubl-fallback-lang( + hidden + lang=client.i18n.lang?client.i18n.lang:"fr" + )&attributes({"force": client.i18n.force}) + if client.i18n.files + for clientI18n in client.i18n.files + hubl-lang( + hidden + lang=clientI18n.name + file=clientI18n.file + ) div( id="swal-content-text" diff --git a/src/locales/en.json b/src/locales/en.json index f3bf7cc..7efb7b7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -242,5 +242,59 @@ "errors": { "somethingGoesWrong": "Something goes wrong, try to", "reload": "reload" + }, + "goButton": "GO", + "directory": { + "back": "Back", + "members": "members", + "module.name": "Members directory", + "picture.delete": "Remove the picture", + "picture.upload": "Upload the picture", + "profile.city": "City", + "profile.edit": "Edit my profile", + "profile.email": "E-mail", + "profile.firstName": "Firstname", + "profile.job": "Job", + "profile.name": "Name", + "profile.phone": "Phone", + "profile.smile": "Show us your best smile", + "profile.smileParagraph": "Pictures help us to know ourselves but also to recognize us, so don't be afraid to show", + "profile.smileParagraph2": "your pretty face and avoid weird avatars.", + "profile.website": "Website", + "save": "Save", + "search.by.city": "Search by city", + "search.by.job": "Search by job", + "search.by.name": "Search by name and/or firstname", + "search.by.skill": "Add a skill filter", + "sendMessage": "Send a message", + "settings.receiveMail": "I would like to receive notifications by email", + "skills.main": "Main skills:", + "skills": "Skills", + "profileHidden.lineOne": "Your profile is not visible on this directory", + "profileHidden.lineTwoOne": "To activate it, ", + "profileHidden.lineTwoTwo": "go to your profile", + "profileHidden.lineTwoThree": ". You need to add a photo.", + "myProfileHidden.lineOne": "Your profile is not visible to other members", + "myProfileHidden.lineTwo": "To activate it, you need to add a photo.", + "activeProfiles": "Show only active profiles", + "goButton": "GO" + }, + "jobboard": { + "joboffers": "Job offers", + "joboffer.description": "Here you will find job offers", + "joboffer.edit": "Modify the job offer", + "joboffer.shownUntil": "THE OFFER WILL BE VISIBLE UNTIL", + "joboffer.title": "Title", + "joboffer.save": "SAVE MY OFFER", + "joboffer.requiredSkills": "REQUIRED SKILLS", + "joboffer.desc": "DESCRIPTION", + "joboffer.create": "POST A NEW JOB OFFER", + "joboffer.post": "Post a job offer", + "search.by.skills": "Search by skills", + "search.noResult": "There are no results for your search", + "search.tryAnother": "Try another skill ", + "edit": "Edit", + "back": "Back", + "goButton": "GO" } } diff --git a/src/locales/es.json b/src/locales/es.json index d7f2f19..ec826d4 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -242,5 +242,59 @@ "errors": { "somethingGoesWrong": "Algo sale mal, intenta", "reload": "recargar" + }, + "goButton": "GO", + "directory": { + "back": "Atrás", + "members": "Miembrxs", + "module.name": "Directorio de miembrxs", + "picture.delete": "Eliminar una foto", + "picture.upload": "Subir una foto", + "profile.city": "Ciudad", + "profile.edit": "Modificar mi perfil", + "profile.email": "E-mail", + "profile.firstName": "Nombre", + "profile.job": "Trabajo", + "profile.name": "Apellido", + "profile.phone": "Teléfono", + "profile.smile": "Muéstranos tu mejor sonrisa", + "profile.smileParagraph": "Las fotos nos ayudan a reconocernos entre nosotrxs y humanizar el listado ¡No tengas miedo de mostrarte!", + "profile.smileParagraph2": "Te invitamos a evitar avatares y usar fotos personales", + "profile.website": "Sitio web", + "save": "Guardar", + "search.by.city": "Buscar por ciudad", + "search.by.job": "Buscar por trabajo", + "search.by.name": "Buscar por nombre y/o apellido", + "search.by.skill": "Agregar un filtro de aptitudes", + "sendMessage": "Envía un mensaje", + "settings.receiveMail": "Me gustaría recibir notificaciones por correo electrónico", + "skills.main": "Aptitudes principales :", + "skills": "Aptitudes", + "profileHidden.lineOne": "Su perfil no es visible en este directorio", + "profileHidden.lineTwoOne": "Para activarlo, ", + "profileHidden.lineTwoTwo": "Ve a tu perfil", + "profileHidden.lineTwoThree": ". Debes añadir una foto.", + "myProfileHidden.lineOne": "Su perfil no es visible para otros miembros", + "myProfileHidden.lineTwo": "Para activarlo, debes añadir una foto", + "activeProfiles": "Mostrar solo perfiles activos", + "goButton": "GO" + }, + "jobboard": { + "joboffers": "Oportunidades de trabajo", + "joboffer.description": "Aquí encontrarás oportunidades de trabajo", + "joboffer.edit": "Modificar la oportunidad de trabajo", + "joboffer.shownUntil": "LA OPORTUNIDAD SERÁ VISIBLE HASTA", + "joboffer.title": "Título", + "joboffer.save": "GUARDAR LA OPORTUNIDAD", + "joboffer.requiredSkills": "APTITUDES REQUERIDAS", + "joboffer.desc": "DESCRIPCIÓN", + "joboffer.create": "PUBLICAR UNA NUEVA OPORTUNIDAD DE TRABAJO", + "joboffer.post": "Publicar una oportunidad", + "search.by.skills": "Buscar por oportunidad", + "search.noResult": "No hay resultados para su búsqueda", + "search.tryAnother": "Prueba otra habilidad ", + "edit": "Modificar", + "back": "Atrás", + "goButton": "GO" } } diff --git a/src/locales/fr.json b/src/locales/fr.json index 0f298ee..05fa724 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -244,5 +244,59 @@ "errors": { "somethingGoesWrong": "Quelque chose ne va pas, essayez de", "reload": "recharger" + }, + "goButton": "GO", + "directory": { + "back": "Retour", + "members": "membres", + "module.name": "Annuaire des membres", + "picture.delete": "Supprimer une photo", + "picture.upload": "Télécharger une photo", + "profile.city": "Ville", + "profile.edit": "Modifier mon profil", + "profile.email": "E-mail", + "profile.firstName": "Prénom", + "profile.job": "Métier", + "profile.name": "Nom", + "profile.phone": "Téléphone", + "profile.smile": "Montre-nous ton plus beau sourire", + "profile.smileParagraph": "Les photos nous aident à nous connaître mais aussi à nous reconnaître, alors n'aie pas peur de montrer", + "profile.smileParagraph2": "ton joli minois et évite les avatars bizarres.", + "profile.website": "Site", + "save": "Enregistrer", + "search.by.city": "Rechercher par ville", + "search.by.job": "Rechercher par métier", + "search.by.name": "Rechercher par nom et/ou prénom", + "search.by.skill": "Ajouter un filtre compétence", + "sendMessage": "Envoyer un message", + "settings.receiveMail": "Je souhaite recevoir les notifications par mail", + "skills.main": "Compétences principales :", + "skills": "Compétences", + "profileHidden.lineOne": "Votre profil n'est pas visible sur cet annuaire.", + "profileHidden.lineTwoOne": "Pour l'activer, ", + "profileHidden.lineTwoTwo": "rendez-vous sur votre profil", + "profileHidden.lineTwoThree": ". Vous devez ajouter une photo.", + "myProfileHidden.lineOne": "Votre profil n'est pas visible des autres membres.", + "myProfileHidden.lineTwo": "Pour l'activer, vous devez ajouter une photo.", + "activeProfiles": "Afficher uniquement les profils actifs", + "goButton": "GO" + }, + "jobboard": { + "joboffers": "Offres de missions", + "joboffer.description": "Tu trouveras ici des offres de missions", + "joboffer.edit": "Modifier l'offre de mission", + "joboffer.shownUntil": "L'OFFRE SERA VISIBLE JUSQU'AU", + "joboffer.title": "Titre", + "joboffer.save": "ENREGISTRER MON OFFRE", + "joboffer.requiredSkills": "COMPÉTENCES REQUISES", + "joboffer.desc": "DESCRIPTIF", + "joboffer.create": "POSTER UNE NOUVELLE OFFRE DE MISSION", + "joboffer.post": "Poster une offre", + "search.by.skills": "Rechercher par compétence", + "search.noResult": "Il n'y a aucun résultat pour ta recherche", + "search.tryAnother": "Essaye une autre compétence", + "edit": "Editer", + "back": "Retour", + "goButton": "GO" } } \ No newline at end of file diff --git a/src/scripts/intl.js b/src/scripts/intl.js index fbf0abc..1b5e916 100644 --- a/src/scripts/intl.js +++ b/src/scripts/intl.js @@ -6,17 +6,49 @@ class JsI18n { constructor() { this.locale = ""; //Current locale this.locales = new Array(); //Available locales + this.defaultLocale = "fr"; } /* Method for automatically detecting the language, does not work in every browser. */ detectLanguage() { + const customLangs = document.querySelectorAll('hubl-lang'); + if(customLangs) { + for(let lang of customLangs) { + let name = lang.getAttribute('lang'), + file = lang.getAttribute('file'); + if(window.hubl.intl.locales[name.toString()] == undefined) { + return fetch(file).then((result) => { + if (result.ok) { + result.json().then(e => { + window.hubl.intl.addLocale(name, e); + }); + } + window.hubl.intl.resumeDetection(); + }); + } + } + } + this.resumeDetection(); + } + + resumeDetection() { + const langComponent = document.querySelector('hubl-fallback-lang'); + if(langComponent) { + if(langComponent.hasAttribute('lang')) { + this.defaultLocale = langComponent.getAttribute('lang'); + if(langComponent.hasAttribute('force')) { + localStorage.setItem('language', this.defaultLocale); + } + } + } if (localStorage.getItem('language') || (window.navigator.language !== null && window.navigator.language !== undefined)) { + this.fetchLocale(this.defaultLocale); this.setLocale(localStorage.getItem('language') || window.navigator.language.slice(0, 2)); } else { console.error('Language not found'); - this.setLocale('fr'); + this.setLocale(this.defaultLocale); } }; @@ -105,38 +137,36 @@ class JsI18n { this.locales[locale.toString()] = translations; } + fetchLocale(locale) { + if(this.locales[locale.toString()] == undefined) { + return fetch(`/locales/${locale}.json`).then((result) => { + if (result.ok) { + result.json().then(e => { + this.addLocale(locale, e); + }); + } + }); + } else { + return (new Promise()).resolve(); + } + } + /* Sets the locale to use when translating. */ setLocale(locale) { try { - fetch(`/locales/${locale}.json`).then((result) => { - if (result.ok) { - result.json().then(e => { - this.addLocale(locale, e); - this.processPage(); - }).catch(() => { - if (locale != "fr") { - console.warn(`Locale not found: ${locale}, fallback to french`); - this.setLocale("fr"); - } else { - console.error("Language not found"); - } - }); - } else { - if (locale != "fr") { - console.warn(`Locale not found: ${locale}, fallback to french`); - this.setLocale("fr"); - } else { - console.error("Language not found"); - } + this.fetchLocale(locale).then(() => { + if(this.locale) { + localStorage.setItem('language', this.locale); } + this.processPage(); + this.locale = locale; }); - this.locale = locale; } catch { - if (locale != "fr") { - console.warn(`Locale not found: ${locale}, fallback to french`); - this.setLocale("fr"); + if (locale != this.defaultLocale) { + console.warn(`Locale not found: ${locale}, fallback to ${this.defaultLocale}`); + this.setLocale(this.defaultLocale); } else { console.error("Language not found"); } @@ -148,12 +178,22 @@ class JsI18n { */ t(key) { var translations = this.locales[this.locale]; - if (translations != undefined) { + if (translations == undefined) { + translations = this.locales[this.defaultLocale]; + } + if(translations != undefined) { let translation = key.toString().split('.').reduce((o, i) => (o ? o[i] : undefined), translations); if (typeof translation == "string") { return translation; } else { - return translations[key.toString()]; + try { + let keySplitted = key.toString().split('.'); + let first = keySplitted.shift(); + translation = translations[first][keySplitted.join('.')]; + return translation; + } catch { + return translations[key.toString()]; + } } } return undefined; @@ -176,21 +216,20 @@ class JsI18n { } //Global -jsI18n = new JsI18n; +window.hubl.intl = new JsI18n; document.addEventListener("DOMContentLoaded", () => { - // Detect the lang & initialize, based on the browser or "language" item from localstorage - jsI18n.detectLanguage(); + window.hubl.intl.detectLanguage(); let timer; (new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.target.attributes["data-trans"] != null) { // Render the target of the mutation instantly - jsI18n.processNode(mutation.target); + window.hubl.intl.processNode(mutation.target); // Then wait one arbitrary second to re-render the whole document in case a widget re-rendered clearTimeout(timer); - timer = setTimeout(() => jsI18n.processNode(document.querySelector('body')), 500); + timer = setTimeout(() => window.hubl.intl.processNode(document.querySelector('body')), 500); } }); }).observe(document.body, { @@ -198,9 +237,9 @@ document.addEventListener("DOMContentLoaded", () => { childList: true })); document.addEventListener('widgetRendered', event => { - jsI18n.processNode(event.target); + window.hubl.intl.processNode(event.target); // Then wait one arbitrary second to re-render the whole document in case a widget re-rendered clearTimeout(timer); - timer = setTimeout(() => jsI18n.processNode(document.querySelector('body')), 500); + timer = setTimeout(() => window.hubl.intl.processNode(document.querySelector('body')), 500); }); }); \ No newline at end of file diff --git a/src/scripts/login-element-visibility.js b/src/scripts/login-element-visibility.js index e46f07b..2367b0b 100644 --- a/src/scripts/login-element-visibility.js +++ b/src/scripts/login-element-visibility.js @@ -36,7 +36,7 @@ window.addEventListener("navigate", e => { window.dispatchEvent( new CustomEvent('requestNavigation', { detail: { - route: window.hubl.getRoute("dashboard", true) + route: window.hubl.getRoute((window.hubl.defaultRoute || "dashboard"), true) } }), ); diff --git a/src/styles/communities/_index.scss b/src/styles/communities/_index.scss index 2cb70cc..c6f6fc5 100644 --- a/src/styles/communities/_index.scss +++ b/src/styles/communities/_index.scss @@ -67,7 +67,7 @@ } solid-form-text-label, - hubl-input-type-password, + solid-form-password, hubl-input-type-email { color: #5D7393; } @@ -76,7 +76,7 @@ text-align: left; } - input[type="submit"] { + [type="submit"] { float: initial !important; } } \ No newline at end of file diff --git a/src/styles/forms/_index.scss b/src/styles/forms/_index.scss index 47e633d..b0269de 100644 --- a/src/styles/forms/_index.scss +++ b/src/styles/forms/_index.scss @@ -1,5 +1,5 @@ .search-form { - input[type="submit"] { + [type="submit"] { margin-bottom: 20px; @media (min-width: 768.01px) { margin-bottom: 0; diff --git a/src/styles/index.scss b/src/styles/index.scss index 2dd160b..e6edfdd 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -106,8 +106,10 @@ hubl-create-contact { /* Fix on Join button in admin (circles + projects) */ /* Styles on buttons and .children-link don't work because this input is inside too many elements. */ /* And no I can't add that stupid icon because it'a an input. */ +/* But now, with 0.16, it's a button and you can :D */ .join-button { - input { + input, + button { padding: 9px 20px; border-radius: 16.5px; height: 33px; diff --git a/src/views/page-messages.pug b/src/views/page-messages.pug index 29e32c0..3133479 100644 --- a/src/views/page-messages.pug +++ b/src/views/page-messages.pug @@ -26,7 +26,6 @@ div.segment.full.padding-top-small.padding-right-large.padding-bottom-small.padd data-authentication='login' data-auto-login='true' data-websocket-url=component.endpoints.xmpp - data-i18n='en' bind-resources uniq=component.uniq ) diff --git a/src/views/page-registering.pug b/src/views/page-registering.pug index 5f6be36..216a9a0 100644 --- a/src/views/page-registering.pug +++ b/src/views/page-registering.pug @@ -58,15 +58,6 @@ class-logo='community-logo' default-logo=`${client.logo || '/images/logo.webp'}` ) - solid-widget(name='hubl-input-type-password') - template - label ${label} - input( - type="password" - name="user.password" - required - data-holder - ) solid-widget(name='hubl-input-type-email') template label ${label} @@ -88,7 +79,7 @@ widget-user.first_name='solid-form-text-label' widget-user.last_name='solid-form-text-label' widget-user.email='hubl-input-type-email' - widget-user.password='hubl-input-type-password' + widget-user.password='solid-form-password' widget-user.username='solid-form-hidden' class-user.first_name='segment margin-bottom-medium full padding-left-small sm-padding-none text-large text-left' class-user.last_name='segment margin-bottom-medium full padding-left-small sm-padding-none text-large text-left' @@ -98,10 +89,11 @@ required-user.last_name required-user.email required-user.password - pattern-user.first_name='.+' - pattern-user.last_name='.+' + minlength-user.first_name='1' + minlength-user.last_name='1' value-user.username='generate-an-username' submit-button='' + submit-widget="button" id='user-creation-form' next=getRoute('dashboard', true) ) \ No newline at end of file diff --git a/src/views/partials/admin/page-admin-chat-create.pug b/src/views/partials/admin/page-admin-chat-create.pug index 1042fde..85ce2ff 100644 --- a/src/views/partials/admin/page-admin-chat-create.pug +++ b/src/views/partials/admin/page-admin-chat-create.pug @@ -56,5 +56,6 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac next=`admin-${getRoute('chat', true)}` submit-button='' + submit-widget="button" data-trans='label-user.first_name=user.create.labelFirstname;label-user.last_name=user.create.labelLastname;label-user.username=user.create.labelUsername;label-user.email=user.create.labelEmail;submit-button=user.create.buttonSubmit' ) diff --git a/src/views/partials/admin/page-admin-chat.pug b/src/views/partials/admin/page-admin-chat.pug index 9aaf87a..0d7fdf0 100644 --- a/src/views/partials/admin/page-admin-chat.pug +++ b/src/views/partials/admin/page-admin-chat.pug @@ -10,10 +10,11 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac id="admin-community-filter" fields='cell1' label-cell1='' - data-trans='label-cell1=communities.searchBy' widget-cell1='solid-form-label-text' class-cell1="segment margin-bottom-medium third sm-full padding-right-small sm-padding-none text-small text-semibold text-uppercase text-color-heading" - submit-button="GO" + submit-button="" + submit-widget="button" + data-trans='label-cell1=communities.searchBy;submit-button=goButton' ) .segment.table-wrapper diff --git a/src/views/partials/admin/page-admin-circles-create.pug b/src/views/partials/admin/page-admin-circles-create.pug index 8e55e0a..0158dce 100644 --- a/src/views/partials/admin/page-admin-circles-create.pug +++ b/src/views/partials/admin/page-admin-circles-create.pug @@ -43,6 +43,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac next=getRoute('circles', true) submit-button='' + submit-widget="button" data-trans='label-status=circle.create.labelStatus;label-name=circle.create.labelName;label-description=circle.create.labelDescription;submit-button=circle.create.buttonSubmit;label-subtitle=circle.create.labelSubtitle;label-help=circle.create.descriptionHelp' ) diff --git a/src/views/partials/admin/page-admin-circles.pug b/src/views/partials/admin/page-admin-circles.pug index 743a467..174ece0 100644 --- a/src/views/partials/admin/page-admin-circles.pug +++ b/src/views/partials/admin/page-admin-circles.pug @@ -24,10 +24,11 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac id="admin-circle-filter" fields='name' label-name='' - data-trans='label-name=circle.list.searchBy' widget-name='solid-form-label-text' class-name="segment margin-bottom-medium third sm-full padding-right-small sm-padding-none text-small text-semibold text-uppercase text-color-heading" - submit-button="GO" + submit-button="" + submit-widget="button" + data-trans='label-name=circle.list.searchBy;submit-button=goButton' ) .segment.table-wrapper @@ -38,7 +39,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac div.segment.table-cell.third(data-trans='circle.list.tableHeader2') div.segment.table-cell.third(data-trans='circle.list.tableHeader3') - solid-widget(name=`leave-circle-reactivity-${component.uniq}`) + solid-widget(name=`leave-circle-reactivity`) template hubl-reactivity(data-src=`${getComponent('circles').endpoints.get}` target-src='${value}') hubl-reactivity(data-src=`${getComponent('circles').endpoints.get}joinable/` target-src='${value}') @@ -46,7 +47,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac hubl-reactivity(data-src=`${getComponent('circles').endpoints.post}joinable/` target-src='${value}') hubl-reactivity(bind-user nested-field="circles" target-src='${value}') - solid-widget(name=`hubl-admin-circle-leave-button-${component.uniq}`) + solid-widget(name=`hubl-admin-circle-leave-button`) template solid-delete( class='segment text-xsmall children-link-button children-link-text-bold children-link-text-uppercase children-link-color-secondary bordered' @@ -62,7 +63,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac solid-display( data-src="${src}" fields="circle" - widget-circle=`leave-circle-reactivity-${component.uniq}` + widget-circle=`leave-circle-reactivity` hidden ) @@ -83,7 +84,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac class-circle.subtitle='segment full text-ellipsis' action-leaveButton="joinButton" - widget-leaveButton=`hubl-admin-circle-leave-button-${component.uniq}` + widget-leaveButton=`hubl-admin-circle-leave-button` widget-circle.owner='hubl-circle-owner' action-counter="counter" widget-counter="hubl-admin-circle-counter-alternate" @@ -91,7 +92,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac order-by="circle.name" ) - solid-widget(name=`hubl-admin-circle-join-button-${component.uniq}`) + solid-widget(name=`hubl-admin-circle-join-button`) template solid-form( class='join-button text-xsmall' @@ -102,6 +103,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac widget-user.username='solid-form-hidden' submit-button='' + submit-widget="button" data-trans='submit-button=circle.list.buttonJoin' ) hubl-reactivity(data-src=`${getComponent('circles').endpoints.get}` target-src='${value}') @@ -128,7 +130,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-normal.whitespac class-subtitle='segment full text-ellipsis' widget-owner='hubl-circle-owner' - widget-members=`hubl-admin-circle-join-button-${component.uniq}` + widget-members=`hubl-admin-circle-join-button` action-counter="counter" widget-counter="hubl-admin-circle-counter" diff --git a/src/views/partials/admin/page-admin-projects-create.pug b/src/views/partials/admin/page-admin-projects-create.pug index fd7de86..662b5e0 100644 --- a/src/views/partials/admin/page-admin-projects-create.pug +++ b/src/views/partials/admin/page-admin-projects-create.pug @@ -45,6 +45,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac widget-linebreak='solid-form-hidden' submit-button='' + submit-widget="button" next=getRoute('projects', true) data-trans='label-status=project.create.labelStatus;label-customer.name=project.create.labelCustomer;label-name=project.create.labelName;label-description=project.create.labelDescription;label-captain=project.create.labelCaptain;label-help=project.create.descriptionHelp;submit-button=project.create.buttonSubmit' diff --git a/src/views/partials/admin/page-admin-projects.pug b/src/views/partials/admin/page-admin-projects.pug index c900c3e..e7bdc26 100644 --- a/src/views/partials/admin/page-admin-projects.pug +++ b/src/views/partials/admin/page-admin-projects.pug @@ -18,10 +18,11 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac id="admin-project-filter" fields='cell1' label-cell1='' - data-trans='label-cell1=project.list.searchBy' widget-cell1='solid-form-label-text' class-cell1="segment margin-bottom-medium third sm-full padding-right-small sm-padding-none text-small text-semibold text-uppercase text-color-heading" - submit-button="GO" + submit-button="" + submit-widget="button" + data-trans='label-cell1=project.list.searchBy;submit-button=goButton' ) .segment.table-wrapper @@ -33,7 +34,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac div.segment.table-cell.quarter(data-trans='project.list.tableHeader3') div.segment.table-cell.quarter(data-trans='project.list.tableHeader4') - solid-widget(name=`leave-project-reactivity-${component.uniq}`) + solid-widget(name=`leave-project-reactivity`) template hubl-reactivity(data-src=`${getComponent('projects').endpoints.get}` target-src='${value}') hubl-reactivity(data-src=`${getComponent('projects').endpoints.get}joinable/` target-src='${value}') @@ -41,7 +42,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac hubl-reactivity(data-src=`${getComponent('projects').endpoints.post}joinable/` target-src='${value}') hubl-reactivity(bind-user nested-field="projects" target-src='${value}') - solid-widget(name=`hubl-admin-project-leave-button-${component.uniq}`) + solid-widget(name=`hubl-admin-project-leave-button`) template solid-delete( class='segment text-xsmall children-link-button children-link-text-bold children-link-text-uppercase children-link-color-secondary bordered' @@ -57,7 +58,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac solid-display( data-src="${src}" fields="project" - widget-project=`leave-project-reactivity-${component.uniq}` + widget-project=`leave-project-reactivity` hidden ) @@ -88,7 +89,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac class-project.name='segment full' action-leaveButton="joinButton" - widget-leaveButton=`hubl-admin-project-leave-button-${component.uniq}` + widget-leaveButton=`hubl-admin-project-leave-button` widget-project.captain='hubl-project-captain' widget-project.members='hubl-project-admins' @@ -98,7 +99,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac order-by="project.name" ) - solid-widget(name=`hubl-admin-project-join-button-${component.uniq}`) + solid-widget(name=`hubl-admin-project-join-button`) template solid-form( class='button text-xsmall text-bold text-uppercase reversed color-secondary bordered icon icon-arrow-right-circle' @@ -110,6 +111,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac widget-user.username='solid-form-hidden' submit-button='' + submit-widget="button" data-trans='submit-button=project.list.buttonJoin' ) hubl-reactivity(data-src=`${getComponent('projects').endpoints.get}` target-src='${value}') @@ -136,7 +138,7 @@ div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-medium.whitespac class-name='segment full' action-joinButton="joinButton" - widget-joinButton=`hubl-admin-project-join-button-${component.uniq}` + widget-joinButton=`hubl-admin-project-join-button` widget-captain='hubl-project-captain' widget-members='hubl-project-admins' diff --git a/src/views/partials/circle/page-circle-chat.pug b/src/views/partials/circle/page-circle-chat.pug index 776db51..16a1ebf 100644 --- a/src/views/partials/circle/page-circle-chat.pug +++ b/src/views/partials/circle/page-circle-chat.pug @@ -14,7 +14,6 @@ div.segment.full.padding-large.sm-padding-top-small.sm-padding-right-xsmall.sm-p data-authentication='login' data-auto-login='true' data-websocket-url=component.endpoints.xmpp - data-i18n='en' bind-resources uniq=component.uniq ) diff --git a/src/views/partials/circle/page-circle-edit.pug b/src/views/partials/circle/page-circle-edit.pug index 01bc9b3..43ceeba 100644 --- a/src/views/partials/circle/page-circle-edit.pug +++ b/src/views/partials/circle/page-circle-edit.pug @@ -53,7 +53,8 @@ div.segment.full.padding-large.whitespace-normal partial='' - submit-button='Enregistrer' + submit-button='' + submit-widget="button" next=`${component.route}-information` data-trans='label-status=circle.edit.labelStatus;label-name=circle.edit.labelName;label-owner=circle.edit.labelOwner;label-description=circle.edit.labelDescription;submit-button=circle.edit.buttonSubmit;label-subtitle=circle.edit.labelSubtitle;label-help=circle.edit.descriptionHelp' @@ -72,6 +73,7 @@ div.segment.full.padding-large.whitespace-normal widget-user='solid-form-dropdown-autocompletion' submit-button='' + submit-widget="button" data-trans='submit-button=circle.edit.buttonAddMember' ) diff --git a/src/views/partials/circle/page-circle-profile.pug b/src/views/partials/circle/page-circle-profile.pug index 3a2f09b..30a5b43 100644 --- a/src/views/partials/circle/page-circle-profile.pug +++ b/src/views/partials/circle/page-circle-profile.pug @@ -18,7 +18,7 @@ div( class-name='text-color-heading text-bold' class-dash='text-color-heading text-bold' ) - + div.segment.full.padding-large.sm-padding-xsmall.sm-padding-top-xlarge div.loader(id=`loader-${component.route}-profile-1`) div @@ -45,16 +45,17 @@ div( solid-ac-checker(no-permission='acl:Delete', bind-resources) solid-link(class='segment sm-full button text-xsmall text-bold text-uppercase text-center reversed color-secondary bordered icon icon-pencil' next=`${component.route}-edit` bind-resources data-trans='circle.profile.buttonAdd') - solid-widget(name=`hubl-circle-leave-button-${uniq}`) + solid-widget(name=`hubl-circle-leave-button-${component.uniq}`) template - solid-ac-checker(no-permission='acl:Delete', data-src="${src}", nested-field="circle") - solid-delete( - class='button text-xsmall text-bold text-uppercase color-secondary bordered' - data-src="${src}" - data-label='' - data-trans='data-label=circle.profile.buttonQuit' - next=`${component.route}-left` - ) + solid-ac-checker(permission='acl:Delete', data-src="${src}") + solid-ac-checker(no-permission='acl:Delete', data-src="${src}", nested-field='circle') + solid-delete( + class='button text-xsmall text-bold text-uppercase color-secondary bordered' + data-src="${src}" + data-label='' + data-trans='data-label=circle.profile.buttonQuit' + next=`${component.route}-left` + ) .segment.full.text-right.margin-bottom-large.sm-margin-bottom-medium solid-display.segment( @@ -62,10 +63,10 @@ div( nested-field='members' fields='relation' action-relation='relation' - widget-relation=`hubl-circle-leave-button-${uniq}` + widget-relation=`hubl-circle-leave-button-${component.uniq}` search-fields='user' search-widget-user='solid-form-hidden' - search-value-user="store://user.@id" + search-value-user='store://user.@id' empty-widget='hubl-circle-join-button' ) solid-ac-checker.segment.margin-left-small(permission='acl:Delete', bind-resources) @@ -76,9 +77,9 @@ div( data-trans='data-label=circle.profile.buttonDelete' next=`admin-${component.route}` ) - + h3.text-color-heading.text-bold.text-letter-spacing-large(data-trans='circle.profile.description') - + solid-display.segment.block.sm-hidden.whitespace-normal( bind-resources fields='description' @@ -112,7 +113,7 @@ div( class-is_admin='segment tag color-primary' multiple-user.communities - multiple-user.communities-fields="community.name" + multiple-user.communities-fields='community.name' widget-user='hubl-circle-team-contact' widget-user.account.picture='hubl-user-avatar' diff --git a/src/views/partials/project/page-project-chat.pug b/src/views/partials/project/page-project-chat.pug index 688ea63..6130ed1 100644 --- a/src/views/partials/project/page-project-chat.pug +++ b/src/views/partials/project/page-project-chat.pug @@ -18,7 +18,6 @@ div.segment.full.padding-large.sm-padding-top-small.sm-padding-right-xsmall.sm-p data-authentication='login' data-auto-login='true' data-websocket-url=component.endpoints.xmpp - data-i18n='en' bind-resources uniq=component.uniq ) diff --git a/src/views/partials/project/page-project-edit.pug b/src/views/partials/project/page-project-edit.pug index 2df9a80..325ff6b 100644 --- a/src/views/partials/project/page-project-edit.pug +++ b/src/views/partials/project/page-project-edit.pug @@ -56,7 +56,8 @@ div.segment.full.padding-large.whitespace-normal partial="" - submit-button='Enregistrer' + submit-button='' + submit-widget="button" next=`${component.route}-information` data-trans='label-name=project.edit.labelName;label-captain=project.edit.labelCaptain;label-customer.name=project.edit.labelCustomer;label-description=project.edit.labelDescription;label-help=project.edit.descriptionHelp;submit-button=project.edit.buttonSubmit' @@ -76,6 +77,7 @@ div.segment.full.padding-large.whitespace-normal widget-user='solid-form-dropdown-autocompletion' submit-button='' + submit-widget="button" data-trans='submit-button=project.edit.buttonAddMember' ) diff --git a/src/views/partials/widgets/hubl-circle-join-button.pug b/src/views/partials/widgets/hubl-circle-join-button.pug index 553a977..232592b 100644 --- a/src/views/partials/widgets/hubl-circle-join-button.pug +++ b/src/views/partials/widgets/hubl-circle-join-button.pug @@ -11,5 +11,6 @@ if componentSet.has('circles') widget-user.username='solid-form-hidden' submit-button='' + submit-widget="button" data-trans='submit-button=circle.profile.buttonJoin' ) \ No newline at end of file