import i18next from "i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import HttpApi from "i18next-http-backend"; ///////////////// // TRANSLATION // ///////////////// async function initI18next() { await i18next .use(HttpApi) .use(LanguageDetector) .init({ supportedLngs: ["en", "de"], nonExplicitSupportedLngs: true, fallbackLng: "en", debug: true, backend: { loadPath: "/lang/{{lng}}.json", }, }); } function translatePageElements() { // Translate content inside a tag const translatableElements = document.querySelectorAll( "[data-i18n-key]", ); translatableElements.forEach((el) => { const key = el.getAttribute("data-i18n-key"); el.innerHTML = i18next.t(key); }); // Translate alt texts const translatableAltTexts = document.querySelectorAll( "[alt-i18n-key]", ); translatableAltTexts.forEach((el) => { const translation_key = el.getAttribute("alt-i18n-key"); el.setAttribute("alt", i18next.t(translation_key)); }); } function bindLocaleSwitcher(initialValue) { const switcher = document.querySelector( "[data-i18n-switcher]", ); switcher.value = initialValue; switcher.onchange = (e) => { i18next .changeLanguage(e.target.value) .then(translatePageElements) .then(update); }; } (async function () { await initI18next(); translatePageElements(); bindLocaleSwitcher(i18next.resolvedLanguage); update(); })(); //////////////// // CALCULATOR // //////////////// const MINIMUM_BASE_AREA = 0.5; const MINIMUM_AREA_THREE_RATS = 1.5; const AREA_PER_ADDITIONAL_RAT = 0.25; const MAXIMUM_FALL_HEIGHT = 0.5; const MINIMUM_LENGTH_LONG_SIDE = 0.8; const MINIMUM_LENGTH_SHORT_SIDE = 0.5; const MINIMUM_FLOOR_HEIGHT = 0.25; const FAILED_BASE_AREA = "base_area"; const FAILED_OVERALL_AREA = "overall_area"; const FAILED_FALL_HEIGHT = "fall_height"; const FAILED_NUM_RATS = "num_rats"; const FAILED_MINIMUM_LENGTH_LONG_SIDE = "length_long_side"; const FAILED_MINIMUM_LENGTH_SHORT_SIDE= "length_short_side"; const FAILED_FLOOR_HEIGHT = "floor_height" class Validator { constructor() { this.FAIL_CRITERIA = { [FAILED_BASE_AREA]: i18next.t('failed-base-area', {"MINIMUM_BASE_AREA": MINIMUM_BASE_AREA}), [FAILED_OVERALL_AREA]: i18next.t("failed-overall-area"), [FAILED_FALL_HEIGHT]: i18next.t("failed-fall-height", {"maximum_fall_height": (MAXIMUM_FALL_HEIGHT * 100).toFixed(0)}), [FAILED_FLOOR_HEIGHT]: i18next.t("failed-floor-height", {"minimum_floor_height": (MINIMUM_FLOOR_HEIGHT * 100).toFixed(0)}), [FAILED_NUM_RATS]: i18next.t("failed-num-rats"), [FAILED_MINIMUM_LENGTH_LONG_SIDE]: i18next.t("failed-minimum-length-long-side", {"minimum_length_long_side": (MINIMUM_LENGTH_LONG_SIDE * 100).toFixed(0)}), [FAILED_MINIMUM_LENGTH_SHORT_SIDE]: i18next.t("failed-minimum-length-short-side", {"minimum_length_short_side": (MINIMUM_LENGTH_SHORT_SIDE * 100).toFixed(0)}), }; } overallAreaNeeded(numOfRats) { if (numOfRats < 3 || numOfRats > 15) { throw new Error("This formula works only from 3 to 15 rats"); } return MINIMUM_AREA_THREE_RATS + (numOfRats - 3) * AREA_PER_ADDITIONAL_RAT; } cageCheck(dimensions, numRats, numFullFloors) { let failedCriteria = {}; if (numRats < 2 || numRats > 15) { failedCriteria[FAILED_NUM_RATS] = this.FAIL_CRITERIA[FAILED_NUM_RATS]; } const baseArea = dimensions.depth * dimensions.width; if (baseArea < MINIMUM_BASE_AREA) { failedCriteria[FAILED_BASE_AREA] = this.FAIL_CRITERIA[FAILED_BASE_AREA]; } const areaNeeded = this.overallAreaNeeded(numRats); if (baseArea * numFullFloors < areaNeeded) { failedCriteria[FAILED_OVERALL_AREA] = this.FAIL_CRITERIA[FAILED_OVERALL_AREA]; } if (dimensions.height / numFullFloors > MAXIMUM_FALL_HEIGHT) { failedCriteria[FAILED_FALL_HEIGHT] = this.FAIL_CRITERIA[FAILED_FALL_HEIGHT]; } if (dimensions.width < MINIMUM_LENGTH_LONG_SIDE && dimensions.depth < MINIMUM_LENGTH_LONG_SIDE) { failedCriteria[FAILED_MINIMUM_LENGTH_LONG_SIDE] = this.FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_LONG_SIDE]; } if (dimensions.width < MINIMUM_LENGTH_SHORT_SIDE || dimensions.depth < MINIMUM_LENGTH_SHORT_SIDE) { failedCriteria[FAILED_MINIMUM_LENGTH_SHORT_SIDE] = this.FAIL_CRITERIA[FAILED_MINIMUM_LENGTH_SHORT_SIDE]; } if (dimensions.height / numFullFloors < MINIMUM_FLOOR_HEIGHT) { failedCriteria[FAILED_FLOOR_HEIGHT] = this.FAIL_CRITERIA[FAILED_FLOOR_HEIGHT]; } return failedCriteria; } } class Dimensions { constructor(width, depth, height) { this.width = width; this.depth = depth; this.height = height; } toString() { return `${this.width}x${this.depth}x${this.height}`; } static fromDict(data) { const { width, depth, height } = data; return new Dimensions(width, depth, height); } } ////////////////////////// // DOCUMENT INTERACTION // ////////////////////////// const inputDecreaseFloorNum = document.getElementById("decreaseFloorNum"); inputDecreaseFloorNum.onclick = decreaseFloorNum; const inputIncreaseFloorNum = document.getElementById("increaseFloorNum"); inputIncreaseFloorNum.onclick = increaseFloorNum; const inputWidth = document.getElementById("width"); inputWidth.onchange = updateViaManualMeasurements; const inputDepth = document.getElementById("depth"); inputDepth.onchange = updateViaManualMeasurements; const inputHeight = document.getElementById("height"); inputHeight.onchange = updateViaManualMeasurements; const selectSavicSuiteRoyaleXL = document.getElementById("SavicSuiteRoyaleXL"); const selectSavicSuiteRoyale95Double = document.getElementById("SavicSuiteRoyale95Double"); const selectTiakiKleintierkäfigEtagere = document.getElementById("TiakiKleintierkäfigEtagere"); const cardSavicSuiteRoyaleXL = document.getElementById("card-SavicSuiteRoyaleXL"); const cardSavicSuiteRoyale95Double = document.getElementById("card-SavicSuiteRoyale95Double"); const cardTiakiKleintierkäfigEtagere = document.getElementById("card-TiakiKleintierkäfigEtagere"); function markActiveCage(cageName) { cardSavicSuiteRoyaleXL.classList.remove("card-active"); cardSavicSuiteRoyale95Double.classList.remove("card-active"); cardTiakiKleintierkäfigEtagere.classList.remove("card-active"); if (cageName != "") { const activeCage = document.getElementById("card-" + cageName); activeCage.classList.add("card-active") } } function updateCage(event) { selectSavicSuiteRoyaleXL.checked = false; selectSavicSuiteRoyale95Double.checked = false; selectTiakiKleintierkäfigEtagere.checked = false; const selectedCage = event.currentTarget selectedCage.checked = true; const cageName = selectedCage.id; var dim = getCageDimensions(cageName); inputWidth.value = dim.width; inputDepth.value = dim.depth; inputHeight.value = dim.height; markActiveCage(cageName); update(); } selectSavicSuiteRoyaleXL.onchange = updateCage; selectSavicSuiteRoyale95Double.onchange = updateCage; selectTiakiKleintierkäfigEtagere.onchange = updateCage; var labelNumRats = document.getElementById("labelNumRats"); var ratSlider = document.getElementById("numRats"); ratSlider.oninput = function () { update(); } // Full floor functions var fullFloorNum = document.getElementById("numFullFloors"); function getCageDimensions(cageName) { if (cageName == "SavicSuiteRoyaleXL") { return new Dimensions(115, 67.5, 153); } if (cageName == "SavicSuiteRoyale95Double") { return new Dimensions(95, 63, 120); } if (cageName == "TiakiKleintierkäfigEtagere") { return new Dimensions(93.5, 63, 141.2); } } function getResultFromChecks(checks) { if (Object.keys(checks).length > 0) { const ul = document.createElement('ul'); for (const key in checks) { const li = document.createElement('li'); li.textContent = `❌ ` + checks[key]; ul.appendChild(li); } return ul; } else { const p = document.createElement('p'); p.innerHTML = "✅ " + i18next.t("cage-complies-with-all-criteria") return p; } } function updateViaManualMeasurements() { markActiveCage("ManualMeasurements"); update(); } function update() { labelNumRats.innerHTML = i18next.t("cage-for-x-rats", {"num_rats": ratSlider.value}); const width = inputWidth.value const depth = inputDepth.value const height = inputHeight.value const dimensions = new Dimensions(width / 100, depth / 100, height / 100); const validator = new Validator(); const failed_checks = validator.cageCheck(dimensions, ratSlider.value, fullFloorNum.value); let resultsDiv = document.getElementById("resultsDiv"); const result = getResultFromChecks(failed_checks); resultsDiv.innerHTML = ""; resultsDiv.appendChild(result); } function decreaseFloorNum() { var input = document.getElementById('numFullFloors'); var value = parseInt(input.value); if (value > 0) { input.value = value - 1; } update(); } function increaseFloorNum() { var input = document.getElementById('numFullFloors'); var value = parseInt(input.value); input.value = value + 1; update(); }