convert this js code to ts. improve it, use described class names, and implement best practices:
/* eslint-disable no-fallthrough */

"use strict";

// window.addEventListener("error", (e) => {
// fetch(
// `/components/head.php?error=${encodeURIComponent(
// `JS: ${e.message} | AT ${e.lineno}:${e.colno} IN ${e.target.location.href}`
// )}`
// );
// });

function log(s: string): void {
// eslint-disable-next-line no-console
console.log(s);
}

const doc: Document = document;

const ar: HTMLElement | null = $('html[lang="ar"]');

function add(el: string, ...classes: string[]): void {
const ele: NodeListOf<Element> = $$(el);
for (let i = 0; i < ele.length; i++) {
ele[i].classList.add(...classes);
}
}

function remove(el: string, ...classes: string[]): void {
const ele: NodeListOf<Element> = $$(el);
for (let i = 0; i < ele.length; i++) {
ele[i].classList.remove(...classes);
}
}

function toggle(el: string, value: string): void {
const ele: NodeListOf<Element> = $$(el);
for (let i = 0; i < ele.length; i++) {
ele[i].classList.toggle(value);
}
}

function text(el: string, value: string): void {
const ele: NodeListOf<Element> = $$(el);
for (let i = 0; i < ele.length; i++) {
T(ele[i], value);
}
}

function $(el: string): HTMLElement | null {
return doc.querySelector(el);
}

function $$(el: string): NodeListOf<Element> {
return doc.querySelectorAll(el);
}

function abort(cond: string, func: string): void {
document.onclick = () => {
// if (eval(cond)) {
// eval(func);
// }
};
}

function I(ele: HTMLElement, value: string): void {
ele.innerHTML = value;
}

function T(ele: HTMLElement, value: string): void {
ele.textContent = value;
}

function TX(ele: HTMLElement): string {
return ele.textContent ? ele.textContent.trim() : "";
}

const tagsLoad: NodeListOf<Element> = $$("[data-src]");

// Load the element by setting the appropriate attributes
const loadElement = (element: HTMLImageElement): void => {
// For images, set the src attribute from data-src
const src = element.dataset.src;

if (src) {
element.src = src;
}
// Remove data attributes once loaded to avoid re-loading
element.removeAttribute("data-src");
};

// Function to lazily load tagsLoad with 'data-src' attribute
const lazyLoad = (target: NodeListOf<Element>): void => {
// Set up the observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Load the element and stop observing it
loadElement(entry.target as HTMLImageElement);
observer.unobserve(entry.target);
}
});
},
{
root: null, // Use the viewport as the root
rootMargin: "0px", // No margins
threshold: 0, // Trigger when 0% of the element is visible
}
);

// Observe each target element
const d = target.length;
for (let i = 0; i < d; i++) {
observer.observe(target[i] as HTMLImageElement);
}
};

// Polyfill for older browsers (optional)
if ("IntersectionObserver" in window) {
// Initialize lazy loading
lazyLoad(tagsLoad);
} else {
const d = tagsLoad.length;
for (let i = 0; i < d; i++) {
loadElement(tagsLoad[i] as HTMLImageElement);
}
}

add("#loader", "none");

for (let i of $$("a")) {
if (i.hostname !== location.hostname && i.hostname !== "") {
(i as HTMLAnchorElement).target = "_blank";
}
}

const urls: { [key: string]: any } = {
// Public
Exxeer: "أمازون",
Close: "إغلاق",
computersB: {
hp: "إتش بي",
},
};

let types: string[] = [];
let branch: string[] = [];
let brands: string[] = [];

function allSubCategories(subCategory: string): void {
switch (replaceChar(subCategory)) {
case "apartments-for-sale":
case "apartments-for-rent":
types = allUrls("apartments", 0, 1);
brands = [];
branch = [];
break;
}
}

function allBranches(branch: string): void {
switch (replaceChar(branch)) {
case "desktop-computers":
case "laptop-computers":
types = [];
break;
case "video-game-consoles":
types = allUrls("video-game", 0, 1);
break;
case "video-games-accessories":
types = allUrls("video-games", 0, 1);
break;
case "computer-accessories-spare-parts":
types = allUrls("computer-acc", 0, 1);
break;
}
}

function replaceChar(ele: string): string {
if (ele) {
return ele
.trim()
.replace(/(all in |»|❭|\n +)/gi, "")
.replace(/ *( |\(|\)|\/|,|&|%20|%2C|%26|'s +|’s +) */gi, "-")
.replace(/(--|---|----)/gi, "-")
.replace(/--/gi, "-")
.toLowerCase();
}
return "";
}

function allUrls(search: string, lang: number, node: number): string | undefined {
let url: string[] = [];
if (node) {
for (const i in urls[search]) {
url.push(i);
}
} else {
if (lang) {
for (const i in urls) {
if (typeof urls[i] === "object") {
for (const e in urls[i]) {
if (replaceChar(e) === replaceChar(search)) {
url = urls[i][e];
}
}
} else {
if (replaceChar(i) === replaceChar(search)) {
url = urls[i];
}
}
}
} else {
for (const i in urls) {
if (typeof urls[i] === "object") {
for (const e in urls[i]) {
url.push(e);
}
} else {
url.push(i);
}
}
}
url = url.flat().find((r) => replaceChar(r) === replaceChar(search));
}

return url || search;
}

function trans(ifTrue: string, ifFalse: string = ifTrue): string {
return ar ? allUrls(ifTrue, 1, 0) : allUrls(ifFalse, 0, 0);
}

const locSearch: string = location.search;
const pathname: string = location.pathname;
const currentUrl: string = location.href;

const checkLog: HTMLElement | null = $(".hamburger-menu .btn");

const search: HTMLInputElement | null = $(".search-header") as HTMLInputElement;
const selectCategory: HTMLSelectElement | null = $(".selectCategory") as HTMLSelectElement;
const results: HTMLElement | null = $(".search-result");

const categoryIn: NodeListOf<Element> = $$(
"#nav:not(#sell #nav) .parent-click div a:not(:first-of-type), .nav-links div div > a:not(:first-of-type)"
);

const parentClick: NodeListOf<Element> = $$(".parent-click:not(:first-of-type), .nav-links > div");
const clickShow: NodeListOf<Element> = $$(".click-show:not(.parent .click-show), .nav-links div div");

const submit: NodeListOf<HTMLInputElement> = $$('.sign-in-form input[type="submit"]');

const url: URLSearchParams = new URLSearchParams(locSearch);

const g: HTMLElement | null = $(".my-chat span");
const footer: HTMLElement | null = $("footer");