HEX
Server: nginx/1.18.0
System: Linux test-ipsremont 5.4.0-214-generic #234-Ubuntu SMP Fri Mar 14 23:50:27 UTC 2025 x86_64
User: ips (1000)
PHP: 8.0.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/bebroker/vite.config.ts
import { defineConfig, type Plugin } from "vite";
import browserslist from "browserslist";
import fs from "fs";
import path from "path";
import { browserslistToTargets } from "lightningcss";

function i18nPages(): Plugin {
  const defaultLang = "en";
  let rootDir = process.cwd();
  let outDir = path.resolve(rootDir, "dist");
  let localesDir = path.resolve(rootDir, "locales");

  const loadLocales = () => {
    if (!fs.existsSync(localesDir)) return {};
    const files = fs
      .readdirSync(localesDir)
      .filter((file) => file.endsWith(".json"));

    const map: Record<string, Record<string, string>> = {};

    for (const file of files) {
      const lang = file.replace(".json", "");
      const translations = JSON.parse(
        fs.readFileSync(path.join(localesDir, file), "utf-8"),
      );
      map[lang] = translations;
    }

    return map;
  };

  const applyTranslations = (
    html: string,
    translations: Record<string, string> | undefined,
    fallbackLang: string,
  ) => {
    if (!translations) return html.replaceAll("{{lang}}", fallbackLang);
    let result = html;
    const mergedTranslations = { lang: fallbackLang, ...translations };
    for (const key in mergedTranslations) {
      if (Object.prototype.hasOwnProperty.call(mergedTranslations, key)) {
        result = result.replaceAll(`{{${key}}}`, String(mergedTranslations[key]));
      }
    }
    return result;
  };

  const resolveLangFromUrl = (url: string | undefined): string => {
    if (!url) return defaultLang;

    const pathMatch = url.match(/^\/([a-z]{2})(\/|$)/i);
    if (pathMatch) {
      return pathMatch[1].toLowerCase();
    }

    const queryMatch = url.match(/[?&]__lang=([a-z]{2})/i);
    if (queryMatch) {
      return queryMatch[1].toLowerCase();
    }

    return defaultLang;
  };

  return {
    name: "i18n-pages",

    configResolved(config) {
			rootDir = config.root;
      outDir = path.resolve(config.root, config.build.outDir || "dist");
      localesDir = path.resolve(config.root, "locales");
    },

    configureServer(server) {
      server.middlewares.use((req, _res, next) => {
        const url = req.url || "/";
				const match = url.match(/^\/([a-z]{2})(\/|$)/i);

        if (match) {
          const lang = match[1].toLowerCase();

					const [pathname, search = ""] = url.split("?");
					const rest = pathname.slice(match[0].length).replace(/^\/+/, "");

					// Only rewrite "known" pages; otherwise leave static assets alone.
					let htmlPath: string | null = null;
					if (!rest || rest === "index.html") {
						htmlPath = "/index.html";
					} else if (rest.startsWith("giveaway")) {
						htmlPath = "/giveaway/index.html";
					} else if (rest.startsWith("privacy-policy")) {
						htmlPath = "/privacy-policy/index.html";
					} else if (rest.startsWith("terms-and-conditions")) {
						htmlPath = "/terms-and-conditions/index.html";
					}

					if (htmlPath) {
						req.url = `${htmlPath}?__lang=${lang}${search ? `&${search}` : ""}`;
					}
        }

        next();
      });
    },

    transformIndexHtml(html, ctx) {
      // Only translate on the dev server.
      // During build we keep placeholders and render each locale in closeBundle().
      if (!(ctx as { server?: unknown }).server) {
        return html;
      }

      const url = (ctx as any).originalUrl || (ctx as any).path || "/";
      const lang = resolveLangFromUrl(url);
      const locales = loadLocales();
      const translations = locales[lang] || locales[defaultLang];
      let result = applyTranslations(html, translations, lang);

      // Dev mode serves the same HTML file, but the browser URL includes the locale prefix
      // (e.g. `/es/giveaway/`). Relative `./assets/...` / `../assets/...` therefore resolve
      // incorrectly for nested URLs. Make asset URLs absolute for giveaway pages.
      const pathname = String(url).split("?")[0];
      const isGiveawayRoute =
        /^\/[a-z]{2}\/giveaway(?:\/|$)/i.test(pathname) ||
        /^\/giveaway(?:\/|$)/i.test(pathname);

      if (isGiveawayRoute) {
        result = result
          .replace(/(?:\.\.\/)+assets\//g, "/assets/")
          .replace(/\.\/assets\//g, "/assets/");
      }

      return result;
    },

		closeBundle() {
			if (!fs.existsSync(localesDir)) return;

			// Pages that should get language folders (home + giveaway).
			const translatablePages = ["index.html", path.join("giveaway", "index.html")];

			const locales = loadLocales();

			for (const pageRelPath of translatablePages) {
				const basePath = path.join(outDir, pageRelPath);
				if (!fs.existsSync(basePath)) continue;

				const baseHtml = fs.readFileSync(basePath, "utf-8");
				const baseDir = path.dirname(pageRelPath);
				const baseDirDepth = baseDir === "."
					? 0
					: baseDir.split(path.sep).filter(Boolean).length;

				for (const [lang, translations] of Object.entries(locales)) {
					const result = applyTranslations(baseHtml, translations, lang);
					const isDefaultLang = lang === defaultLang;

					const localeTargetPath = path.join(outDir, lang, pageRelPath);
					const targets = isDefaultLang
						? [path.join(outDir, pageRelPath), localeTargetPath]
						: [localeTargetPath];

					for (const target of targets) {
						// The base HTML was built at `dist/<pageRelPath>`.
						// When we write to `dist/<lang>/<pageRelPath>`, the HTML moves one directory deeper,
						// so relative asset URLs like `../assets/...` need one extra `../`.
						const targetRelPath = path.relative(outDir, target);
						const targetDir = path.dirname(targetRelPath);
						const targetDirDepth = targetDir === "."
							? 0
							: targetDir.split(path.sep).filter(Boolean).length;
						const assetShift = targetDirDepth - baseDirDepth;

						let finalHtml = result;
						if (assetShift > 0) {
							const prefix = "../".repeat(assetShift);
							finalHtml = finalHtml.replace(
								/((?:\.\.\/)+assets\/)/g,
								prefix + "$1",
							);
						}

						// Make asset URLs independent of the current nesting level.
						// Example: `./assets/...` works for `/giveaway/` but breaks for `/es/giveaway/`.
						// By rewriting to absolute `/assets/...`, all locales resolve correctly.
						finalHtml = finalHtml
							.replace(/(?:\.\.\/)+assets\//g, "/assets/")
							.replace(/\.\/assets\//g, "/assets/");

						const dir = path.dirname(target);
						if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
						fs.writeFileSync(target, finalHtml, "utf-8");
					}
				}
			}
    },
  };
}

export default defineConfig({
	root: path.resolve(process.cwd(), "src"),
	base: "",
	publicDir: path.resolve(process.cwd(), "public"),
	server: {
		port: 3000,
		open: false,
	},
	css: {
		transformer: "lightningcss",
		lightningcss: {
			targets: browserslistToTargets(browserslist(">= 0.25%")),
		},
	},
    plugins: [i18nPages()],
	build: {
		emptyOutDir: true,
		outDir: path.resolve(process.cwd(), "dist"),
		cssMinify: "lightningcss",
		rollupOptions: {
			input: {
				index: path.resolve(process.cwd(), "src/index.html"),
				giveaway: path.resolve(process.cwd(), "src/giveaway/index.html"),
				privacy: path.resolve(process.cwd(), "src/privacy-policy/index.html"),
				terms: path.resolve(process.cwd(), "src/terms-and-conditions/index.html"),
			},
		},
	},
});