Layout

Layout files are created using TypeScript functions. You can define as many properties as you like as long as you return a string. Then have each page in your project pass in the relevant data to this function.

Just using TypeScript files keeps the number of languages down in this project. You don't have to learn yet another templating language. To make interacting with the HTML in TypeScript easier, I've used the `/* HTML */` comment to allow for multi-line strings. That is recognized by 2 extensions I find useful.

  • esbenp.prettier-vscode
  • tobermory.es6-string-html
TypeScript - src/Layout.ts
import { LanguageOption } from "./GlobalSitesCore/LanguageOption";
import i18next from "./GlobalSitesCore/i18n";
import { languageSettings } from "./GlobalSitesCore/languages";
import { urlBuilder } from "./GlobalSitesCore/urlBuilder";

interface LayoutProps {
  content: string;
  lang: string;
  description: string;
  title: string;
  languageOptions: LanguageOption[];
}
export function Layout(props: LayoutProps): string {
  const baseUrl = "https://www.globalsites.ai";
  var currentLanguageOption = props.languageOptions.find(
    (option) => option.code == props.lang
  );
  var defaultLanguageOption = props.languageOptions.find(
    (option) => option.code == languageSettings.defaultLanguage
  );

  return /* HTML */ `
    <!DOCTYPE html>
    <html lang="${props.lang}">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta name="description" content="${props.description}" />
        <meta property="og:title" content="${props.title}" />
        <meta property="og:description" content="${props.description}" />
        <meta property="og:url" content="${currentLanguageOption?.url}" />
        <meta property="og:type" content="website" />
        <meta property="og:site_name" content="Global Sites" />
        <meta property="og:locale" content="${props.lang}" />
        <meta property="og:image" content="${baseUrl}/ogImage.jpg" />
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:site" content="@benwmaddox" />
        <meta name="twitter:creator" content="@benwmaddox" />
        <meta name="twitter:title" content="${props.title}" />
        <meta name="twitter:description" content="${props.description}" />
        <meta name="twitter:image" content="${baseUrl}/ogImage.jpg" />

        <link
          rel="sitemap"
          type="application/xml"
          title="Sitemap"
          href="/sitemap.xml"
        />
        <link
          rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/dark.min.css"
        />
        <link rel="stylesheet" href="/styles.css" />
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <title>
          ${props.title ? props.title + " - " : ""}${i18next.t(`Global Sites`)}
        </title>
        <style>
          body {
            height: 100vh;
          }
        </style>

        ${props.languageOptions
          .map((option) => {
            return option.code == languageSettings.defaultLanguage
              ? `<link rel="alternate" href="${baseUrl}${option.url}" hreflang="${option.code}"/>
<link rel="alternate" href="${baseUrl}${option.url}" hreflang="x-default"/>`
              : `<link rel="alternate" href="${baseUrl}${option.url}" hreflang="${option.code}"/>`;
          })
          .join("
")}
        <script>
          function switchLanguage() {
            var selectedLanguageUrl =
              document.getElementById("language-select").value;
            window.location.href = selectedLanguageUrl;
          }
        </script>
      </head>
      <body>
        <header>
          <div class="flex">
            <div class="logo">
              <a
                href="${props.lang === "en" ? "/" : `/${props.lang}/`}"
                style="display:inline-block;"
              >
                <img src="/logo.svg" alt="Logo"
              /></a>
            </div>

            <div class="language-switcher">
              <select id="language-select" onchange="switchLanguage()">
                ${[...props.languageOptions]
                  .sort((a, b) =>
                    a.code == props.lang ? 1 : a.code.localeCompare(b.code)
                  )
                  .map((option) => {
                    return `<option value="${option.url}" ${
                      option.code === props.lang ? "selected" : ""
                    }>${option.name}</option>`;
                  })
                  .join("")}
              </select>
            </div>
            <nav>
              <a href="/${urlBuilder(undefined, undefined)}"
                >${i18next.t("Home")}</a
              >
              |
              <a href="/${urlBuilder(undefined, "documentation")}"
                >${i18next.t("Documentation")}</a
              >
              |
              <a href="/${urlBuilder(undefined, "contact-us")}"
                >${i18next.t("Contact Us")}</a
              >
              |
              <a href="/${urlBuilder(undefined, "privacy-policy")}"
                >${i18next.t("Privacy Policy")}</a
              >
              |
              <a href="/${urlBuilder(undefined, "faq")}">${i18next.t("FAQ")}</a>
            </nav>
          </div>
        </header>
        <main>
          <div class="container">${props.content}</div>
        </main>
        <footer>
          <div class="footer-content">
            <p>
              <a href="/${urlBuilder(undefined, "contact-us")}"
                >${i18next.t("Contact Us")}</a
              >
              |
              <a href="/${urlBuilder(undefined, "privacy-policy")}"
                >${i18next.t("Privacy Policy")}</a
              >
              |
              <a href="/${urlBuilder(undefined, "faq")}">${i18next.t("FAQ")}</a>
              &copy; ${new Date().getFullYear()} ${baseUrl}
            </p>
            <div>
              ${[...props.languageOptions]
                .map((option) => {
                  return `<a href="${option.url}" >${option.name}</a>`;
                })
                .join(" | ")}
            </div>
          </div>
        </footer>
      </body>
    </html>
  `;
}