mirror of
https://github.com/ClaperCo/Claper.git
synced 2025-12-14 19:07:52 +01:00
feat: add hu and lv locales to airpicker and moment
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
### Features
|
||||
|
||||
- Add `LANGUAGES` setting to configure available languages in the app
|
||||
- Add Latvian language support (@possible-im)
|
||||
- Add Hungarian language support (@bpisch)
|
||||
|
||||
### Fixes and improvements
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Claper has a two-sided mission:
|
||||
- The first one is to help these people presenting an idea or a message by giving them the opportunity to make their presentation unique and to have real-time feedback from their audience.
|
||||
- The second one is to help each participant to take their place, to be an actor in the presentation, in the meeting and to feel important and useful.
|
||||
|
||||
Supported languages: 🇬🇧 English, 🇫🇷 French, 🇩🇪 German, 🇪🇸 Spanish, 🇳🇱 Dutch, 🇮🇹 Italian
|
||||
Supported languages: 🇬🇧 English, 🇫🇷 French, 🇩🇪 German, 🇪🇸 Spanish, 🇳🇱 Dutch, 🇮🇹 Italian, 🇭🇺 Hungarian, 🇱🇻 Latvian
|
||||
|
||||
### Built With
|
||||
|
||||
@@ -101,6 +101,3 @@ Distributed under the GPLv3 License. See `LICENSE.txt` for more information.
|
||||
[Tailwind-url]: https://tailwindcss.com/
|
||||
[Phoenix]: https://img.shields.io/badge/phoenix-f35424?style=for-the-badge&logo=&logoColor=white
|
||||
[Phoenix-url]: https://www.phoenixframework.org/
|
||||
[lmddc-logo]: /priv/static/images/partners/lmddc.png
|
||||
[pixilearn-logo]: /priv/static/images/partners/pixilearn.png
|
||||
[uccs-logo]: /priv/static/images/partners/uccs.png
|
||||
|
||||
@@ -13,11 +13,14 @@ import airdatepickerLocaleDe from "air-datepicker/locale/de";
|
||||
import airdatepickerLocaleEs from "air-datepicker/locale/es";
|
||||
import airdatepickerLocaleNl from "air-datepicker/locale/nl";
|
||||
import airdatepickerLocaleIt from "air-datepicker/locale/it";
|
||||
import airdatepickerLocaleHu from "air-datepicker/locale/hu";
|
||||
import "moment/locale/de";
|
||||
import "moment/locale/fr";
|
||||
import "moment/locale/es";
|
||||
import "moment/locale/nl";
|
||||
import "moment/locale/it";
|
||||
import "moment/locale/hu";
|
||||
import "moment/locale/lv";
|
||||
import QRCodeStyling from "qr-code-styling";
|
||||
import { Presenter } from "./presenter";
|
||||
import { Manager } from "./manager";
|
||||
@@ -26,28 +29,53 @@ import { TourGuideClient } from "@sjmc11/tourguidejs/src/Tour";
|
||||
window.moment = moment;
|
||||
|
||||
// Get supported locales from backend configuration or fallback to default list
|
||||
const supportedLocales = window.claperConfig?.supportedLocales || ["en", "fr", "de", "es", "nl", "it"];
|
||||
const supportedLocales = window.claperConfig?.supportedLocales || [
|
||||
"en",
|
||||
"fr",
|
||||
"de",
|
||||
"es",
|
||||
"nl",
|
||||
"it",
|
||||
"hu",
|
||||
"lv",
|
||||
];
|
||||
|
||||
const airdatePickrSupportedLocales = window.claperConfig?.supportedLocales || [
|
||||
"en",
|
||||
"fr",
|
||||
"de",
|
||||
"es",
|
||||
"nl",
|
||||
"it",
|
||||
"hu",
|
||||
];
|
||||
|
||||
var locale =
|
||||
document.querySelector("html").getAttribute("lang") ||
|
||||
navigator.language.split("-")[0];
|
||||
|
||||
var airdatepickrLocale = locale;
|
||||
|
||||
if (!supportedLocales.includes(locale)) {
|
||||
locale = "en";
|
||||
}
|
||||
if (!airdatePickrSupportedLocales.includes(locale)) {
|
||||
airdatepickrLocale = "en";
|
||||
}
|
||||
|
||||
window.moment.locale("en");
|
||||
window.moment.locale(locale);
|
||||
window.Alpine = Alpine;
|
||||
Alpine.start();
|
||||
|
||||
let airdatepickerLocale = {
|
||||
let airdatePickrLocales = {
|
||||
en: airdatepickerLocaleEn,
|
||||
fr: airdatepickerLocaleFr,
|
||||
de: airdatepickerLocaleDe,
|
||||
es: airdatepickerLocaleEs,
|
||||
nl: airdatepickerLocaleNl,
|
||||
it: airdatepickerLocaleIt,
|
||||
hu: airdatepickerLocaleHu,
|
||||
};
|
||||
let csrfToken = document
|
||||
.querySelector("meta[name='csrf-token']")
|
||||
@@ -70,8 +98,8 @@ Hooks.EmbeddedBanner = {
|
||||
Hooks.TourGuide = {
|
||||
mounted() {
|
||||
this.triggerDiv = document.querySelector(this.el.dataset.btnTrigger);
|
||||
this.btnTrigger = this.triggerDiv.querySelector('.open');
|
||||
this.closeBtnTrigger = this.triggerDiv.querySelector('.close');
|
||||
this.btnTrigger = this.triggerDiv.querySelector(".open");
|
||||
this.closeBtnTrigger = this.triggerDiv.querySelector(".close");
|
||||
|
||||
this.tour = new TourGuideClient({
|
||||
nextLabel: this.el.dataset.nextLabel,
|
||||
@@ -106,7 +134,7 @@ Hooks.TourGuide = {
|
||||
destroyed() {
|
||||
this.btnTrigger.removeEventListener("click", () => {
|
||||
this.startTour();
|
||||
});
|
||||
});
|
||||
this.closeBtnTrigger.removeEventListener("click", () => {
|
||||
this.triggerDiv.classList.add("hidden");
|
||||
this.tour.finishTour(true, this.el.dataset.group);
|
||||
@@ -201,26 +229,36 @@ Hooks.Scroll = {
|
||||
Hooks.ScrollIntoDiv = {
|
||||
mounted() {
|
||||
let useParent = this.el.dataset.useParent === "true";
|
||||
this.scrollElement = this.el.dataset.useParent === "true" ? this.el.parentElement : this.el;
|
||||
this.scrollElement =
|
||||
this.el.dataset.useParent === "true" ? this.el.parentElement : this.el;
|
||||
this.checkIfAtBottom();
|
||||
this.scrollToBottom(true);
|
||||
this.handleEvent("scroll", () => this.scrollToBottom());
|
||||
this.scrollElement.addEventListener("scroll", () => this.checkIfAtBottom());
|
||||
},
|
||||
checkIfAtBottom() {
|
||||
this.isAtBottom = this.scrollElement.scrollHeight - this.scrollElement.scrollTop - this.scrollElement.clientHeight <= 30;
|
||||
this.isAtBottom =
|
||||
this.scrollElement.scrollHeight -
|
||||
this.scrollElement.scrollTop -
|
||||
this.scrollElement.clientHeight <=
|
||||
30;
|
||||
},
|
||||
scrollToBottom(force = false) {
|
||||
if (force || this.isAtBottom) {
|
||||
this.scrollElement.scrollTo({ top: this.scrollElement.scrollHeight, behavior: "smooth" });
|
||||
this.scrollElement.scrollTo({
|
||||
top: this.scrollElement.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
this.scrollToBottom();
|
||||
},
|
||||
destroyed() {
|
||||
this.scrollElement.removeEventListener("scroll", () => this.checkIfAtBottom());
|
||||
}
|
||||
this.scrollElement.removeEventListener("scroll", () =>
|
||||
this.checkIfAtBottom(),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
Hooks.NicknamePicker = {
|
||||
@@ -244,7 +282,7 @@ Hooks.NicknamePicker = {
|
||||
clicked(e) {
|
||||
let nickname = prompt(
|
||||
this.el.dataset.prompt,
|
||||
localStorage.getItem("nickname") || ""
|
||||
localStorage.getItem("nickname") || "",
|
||||
);
|
||||
|
||||
if (nickname) {
|
||||
@@ -354,7 +392,7 @@ Hooks.Pickr = {
|
||||
const utc = moment(date).utc().format("YYYY-MM-DDTHH:mm:ss");
|
||||
utcTime.value = utc;
|
||||
},
|
||||
locale: airdatepickerLocale[locale],
|
||||
locale: airdatePickrLocales[airdatepickrLocale],
|
||||
});
|
||||
},
|
||||
updated() {},
|
||||
@@ -393,7 +431,7 @@ Hooks.OpenPresenter = {
|
||||
window.open(
|
||||
this.el.dataset.url,
|
||||
"newwindow",
|
||||
"width=" + window.screen.width + ",height=" + window.screen.height
|
||||
"width=" + window.screen.width + ",height=" + window.screen.height,
|
||||
);
|
||||
},
|
||||
mounted() {
|
||||
@@ -418,7 +456,12 @@ Hooks.GlobalReacts = {
|
||||
const container = document.createElement("div");
|
||||
container.innerHTML = svgContent;
|
||||
const svgElement = container.firstChild;
|
||||
svgElement.classList.add("react-animation", "absolute", "transform", "opacity-0");
|
||||
svgElement.classList.add(
|
||||
"react-animation",
|
||||
"absolute",
|
||||
"transform",
|
||||
"opacity-0",
|
||||
);
|
||||
svgElement.classList.add(...this.el.className.split(" "));
|
||||
this.el.appendChild(svgElement);
|
||||
}
|
||||
@@ -430,15 +473,17 @@ Hooks.GlobalReacts = {
|
||||
|
||||
preloadSVGs() {
|
||||
const svgTypes = ["heart", "hundred", "clap", "raisehand"];
|
||||
svgTypes.forEach(type => {
|
||||
svgTypes.forEach((type) => {
|
||||
fetch(`/images/icons/${type}.svg`)
|
||||
.then(response => response.text())
|
||||
.then(svgContent => {
|
||||
.then((response) => response.text())
|
||||
.then((svgContent) => {
|
||||
this.svgCache[type] = svgContent;
|
||||
})
|
||||
.catch(error => console.error(`Error loading SVG for ${type}:`, error));
|
||||
.catch((error) =>
|
||||
console.error(`Error loading SVG for ${type}:`, error),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
Hooks.JoinEvent = {
|
||||
mounted() {
|
||||
@@ -630,7 +675,6 @@ window.addEventListener("phx:page-loading-stop", (info) => {
|
||||
topbar.hide();
|
||||
});
|
||||
|
||||
|
||||
const onlineUserTemplate = function (user) {
|
||||
return `
|
||||
<div id="online-user">
|
||||
|
||||
@@ -129,7 +129,7 @@ allow_unlink_external_provider =
|
||||
logout_redirect_url = get_var_from_path_or_env(config_dir, "LOGOUT_REDIRECT_URL", nil)
|
||||
|
||||
languages =
|
||||
get_var_from_path_or_env(config_dir, "LANGUAGES", "en,fr,es")
|
||||
get_var_from_path_or_env(config_dir, "LANGUAGES", "en,fr,es,it,de")
|
||||
|> String.split(",")
|
||||
|> Enum.map(&String.trim/1)
|
||||
|
||||
|
||||
@@ -259,11 +259,19 @@
|
||||
{"English", "en"},
|
||||
{"Español", "es"},
|
||||
{"Français", "fr"},
|
||||
{"Hungarian", "hu"},
|
||||
{"Italiano", "it"},
|
||||
{"Latvian", "lv"},
|
||||
{"Nederlands", "nl"}
|
||||
]
|
||||
|> Enum.filter(fn {_name, code} ->
|
||||
code in Application.get_env(:claper, :languages, ["en", "fr", "es"])
|
||||
code in Application.get_env(:claper, :languages, [
|
||||
"en",
|
||||
"fr",
|
||||
"es",
|
||||
"it",
|
||||
"de"
|
||||
])
|
||||
end)
|
||||
}
|
||||
key={:locale}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<link phx-track-static rel="stylesheet" href="/assets/custom.css" />
|
||||
<script>
|
||||
window.claperConfig = {
|
||||
supportedLocales: <%= Jason.encode!(Application.get_env(:claper, :languages, ["en", "fr", "es"])) %>
|
||||
supportedLocales: <%= Jason.encode!(Application.get_env(:claper, :languages, ["en", "fr", "es", "it", "de"])) %>
|
||||
};
|
||||
</script>
|
||||
<script defer phx-track-static type="text/javascript" src="/assets/app.js">
|
||||
|
||||
Reference in New Issue
Block a user