Add lazy widget language localization

This commit is contained in:
rajchales-monito
2026-07-30 08:13:57 +02:00
parent 05636ab77e
commit 80540cf188
5 changed files with 439 additions and 38 deletions
+128 -8
View File
@@ -24,6 +24,15 @@ const state = {
};
const $ = (selector) => document.querySelector(selector);
const WIDGET_LANGUAGES = [
["cs", "Cestina"], ["sk", "Slovenstina"], ["en", "Anglictina"], ["de", "Nemcina"], ["pl", "Polstina"],
["hu", "Madarstina"], ["ro", "Rumunstina"], ["bg", "Bulharstina"], ["hr", "Chorvatstina"], ["sl", "Slovinstina"],
["it", "Italstina"], ["fr", "Francouzstina"], ["es", "Spanelstina"], ["pt", "Portugalstina"], ["nl", "Nizozemstina"],
["da", "Danstina"], ["sv", "Svedstina"], ["fi", "Finstina"], ["no", "Norstina"], ["et", "Estonstina"],
["lv", "Lotystina"], ["lt", "Litevstina"], ["el", "Rectina"], ["uk", "Ukrajinstina"], ["ru", "Rustina"],
["tr", "Turectina"], ["sr", "Srb-stina"], ["bs", "Bosenstina"], ["sq", "Albanstina"], ["mk", "Makedonstina"],
["mt", "Maltstina"], ["ga", "Irstina"], ["is", "Islandstina"], ["be", "Belorustina"], ["ca", "Katalanstina"]
];
boot();
renderAdminSoundControls();
@@ -104,6 +113,9 @@ $("#settingsForm").brandHex.addEventListener("input", (event) => {
const color = normalizeHexColor(event.target.value);
if (color) $("#settingsForm").brand.value = color;
});
$("#settingsForm").defaultLanguage.addEventListener("change", () => {
renderWidgetCopyFields($("#settingsForm").defaultLanguage.value);
});
window.addEventListener("focus", syncAttentionWithUnread);
document.addEventListener("visibilitychange", () => {
@@ -118,10 +130,33 @@ $("#settingsForm").addEventListener("submit", async (event) => {
event.preventDefault();
const form = new FormData(event.currentTarget);
const settings = structuredClone(state.site.settings);
settings.title.cs = form.get("titleCs");
settings.intro.cs = form.get("introCs");
settings.title.en = form.get("titleEn");
settings.intro.en = form.get("introEn");
const defaultLanguage = form.get("defaultLanguage") || "cs";
settings.widgetLanguage = {
...(settings.widgetLanguage || {}),
mode: form.get("widgetLanguageMode") || "auto",
defaultLanguage,
fixedLanguage: form.get("fixedLanguage") || defaultLanguage,
fallbackLanguage: form.get("fallbackLanguage") || defaultLanguage
};
settings.widgetCopy = settings.widgetCopy || {};
settings.widgetCopy[defaultLanguage] = {
...(settings.widgetCopy[defaultLanguage] || {}),
title: form.get("copyTitle") || "",
intro: form.get("copyIntro") || "",
offlineIntro: form.get("copyOfflineIntro") || "",
placeholder: form.get("copyPlaceholder") || "",
sendLabel: form.get("copySendLabel") || "",
dropzoneLabel: form.get("copyDropzoneLabel") || "",
onlineLabel: form.get("copyOnlineLabel") || "",
offlineLabel: settings.widgetCopy[defaultLanguage]?.offlineLabel || "Offline",
newReplyLabel: form.get("copyNewReplyLabel") || "",
typingLabel: settings.widgetCopy[defaultLanguage]?.typingLabel || "Operator pise...",
infoTitle: settings.widgetCopy[defaultLanguage]?.infoTitle || "GDPR informace",
infoText: form.get("copyInfoText") || "",
closeLabel: settings.widgetCopy[defaultLanguage]?.closeLabel || "Zmensit chat",
operatorReplyLabel: form.get("copyOperatorReplyLabel") || ""
};
syncLegacyWidgetCopy(settings);
settings.colors.brand = normalizeHexColor(form.get("brandHex")) || form.get("brand");
settings.desktop.side = form.get("desktopSide");
settings.desktop.bottomPx = Number(form.get("desktopBottom"));
@@ -378,10 +413,8 @@ function renderSite() {
$("#snippet").textContent = state.snippet;
const s = state.site.settings;
const form = $("#settingsForm");
form.titleCs.value = s.title.cs || "";
form.introCs.value = s.intro.cs || "";
form.titleEn.value = s.title.en || "";
form.introEn.value = s.intro.en || "";
renderWidgetLanguageOptions(form, s);
renderWidgetCopyFields(s.widgetLanguage?.defaultLanguage || "cs");
form.brand.value = normalizeHexColor(s.colors.brand) || "#0f8f6f";
form.brandHex.value = form.brand.value;
form.desktopSide.value = s.desktop.side || "right";
@@ -407,6 +440,93 @@ function renderSite() {
renderModelOptions(s.translations?.model || "gpt-5-mini");
}
function renderWidgetLanguageOptions(form, settings) {
const language = settings.widgetLanguage || {};
const options = WIDGET_LANGUAGES
.map(([code, label]) => `<option value="${code}">${code.toUpperCase()} - ${escapeHtml(label)}</option>`)
.join("");
for (const name of ["defaultLanguage", "fixedLanguage", "fallbackLanguage"]) {
form[name].innerHTML = options;
}
form.widgetLanguageMode.value = language.mode || "auto";
form.defaultLanguage.value = language.defaultLanguage || "cs";
form.fixedLanguage.value = language.fixedLanguage || "cs";
form.fallbackLanguage.value = language.fallbackLanguage || language.defaultLanguage || "cs";
}
function renderWidgetCopyFields(language) {
const form = $("#settingsForm");
const settings = state.site?.settings || {};
const copy = widgetCopyForAdmin(settings, language);
form.copyTitle.value = copy.title || "";
form.copyIntro.value = copy.intro || "";
form.copyOfflineIntro.value = copy.offlineIntro || "";
form.copyPlaceholder.value = copy.placeholder || "";
form.copySendLabel.value = copy.sendLabel || "";
form.copyDropzoneLabel.value = copy.dropzoneLabel || "";
form.copyOnlineLabel.value = copy.onlineLabel || "";
form.copyNewReplyLabel.value = copy.newReplyLabel || "";
form.copyOperatorReplyLabel.value = copy.operatorReplyLabel || "";
form.copyInfoText.value = copy.infoText || "";
}
function widgetCopyForAdmin(settings, language) {
const lang = language || "cs";
const copy = settings.widgetCopy?.[lang] || {};
const legacy = {
title: settings.title?.[lang],
intro: settings.intro?.[lang],
offlineIntro: settings.offlineIntro?.[lang],
placeholder: settings.placeholder?.[lang],
sendLabel: settings.sendLabel?.[lang]
};
return { ...defaultWidgetCopy(lang), ...legacy, ...copy };
}
function defaultWidgetCopy(language) {
if (language === "en") {
return {
title: "Need help?",
intro: "Message us and we will reply as soon as possible.",
offlineIntro: "We are offline, but you can leave us a message.",
placeholder: "Write a message...",
sendLabel: "Send",
dropzoneLabel: "Add a photo or drag it here",
onlineLabel: "We are online",
newReplyLabel: "New reply",
infoText: "We process your message and technical session data only to answer your request.",
operatorReplyLabel: "is replying"
};
}
return {
title: "Potrebujete poradit?",
intro: "Napiste nam. Odpovime co nejdrive.",
offlineIntro: "Ted nejsme online, ale zpravu nam muzete nechat.",
placeholder: "Napiste zpravu...",
sendLabel: "Odeslat",
dropzoneLabel: "Pridat fotku nebo pretahnout",
onlineLabel: "Jsme online",
newReplyLabel: "Nova odpoved",
infoText: "Zpravu a technicke udaje relace zpracujeme jen pro odpoved na vas dotaz.",
operatorReplyLabel: "odpovida"
};
}
function syncLegacyWidgetCopy(settings) {
settings.title = settings.title || {};
settings.intro = settings.intro || {};
settings.offlineIntro = settings.offlineIntro || {};
settings.placeholder = settings.placeholder || {};
settings.sendLabel = settings.sendLabel || {};
for (const [language, copy] of Object.entries(settings.widgetCopy || {})) {
settings.title[language] = copy.title;
settings.intro[language] = copy.intro;
settings.offlineIntro[language] = copy.offlineIntro;
settings.placeholder[language] = copy.placeholder;
settings.sendLabel[language] = copy.sendLabel;
}
}
async function saveTranslationSettings() {
if (!state.site) return;
setOpenAiStatus("Ukladam AI nastaveni...");