feat: add hu and lv locales to airpicker and moment

This commit is contained in:
Alex Lion
2025-08-24 09:55:09 +02:00
parent 4c678dc8df
commit c8bf32542f
6 changed files with 78 additions and 27 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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)

View File

@@ -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}

View File

@@ -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">