Tons more

This commit is contained in:
DrMint 2024-02-17 23:11:17 +01:00
parent 47fa0d93a1
commit 07f6375b46
41 changed files with 2230 additions and 569 deletions

View File

@ -1,31 +0,0 @@
Bun.serve({
port: 12498,
fetch: async (req) => {
const reqUrl = new URL(req.url);
const rewriteUrl = new URL(reqUrl);
rewriteUrl.hostname = "localhost";
rewriteUrl.port = "12499";
rewriteUrl.protocol = "http";
const rewrite = new Request(rewriteUrl, req);
const response = await fetch(rewrite, { redirect: "manual" });
console.log(`[${response.status}] ${rewriteUrl.pathname}`);
if (response.status === 404 && response.headers.has("Location")) {
// Prevent redirection from a non locale-specific page to the en locale-specific page
if (response.headers.get("location") === "/en" + rewriteUrl.pathname) {
rewriteUrl.pathname = "/en/" + rewriteUrl.pathname;
const rewrite = new Request(rewriteUrl, req);
return await fetch(rewrite, { redirect: "manual" });
}
return new Response(await response.blob(), {
headers: response.headers,
status: 302,
statusText: "Found",
});
}
return response;
},
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
import { writeFileSync } from "fs";
const PAYLOAD_FOLDER = `${process.cwd()}/src/shared/payload`;
const sdk = await fetch(`${import.meta.env.PAYLOAD_API_URL}/sdk`);
if (!sdk.ok) {
console.error("Failed to get the sdk", sdk.status, sdk.statusText);
} else {
const sdkFile = await sdk.text();
writeFileSync(`${PAYLOAD_FOLDER}/payload-sdk.ts`, sdkFile, {
encoding: "utf-8",
});
}

View File

@ -5,8 +5,8 @@ import Footer from "./components/Footer.astro";
interface Props {
breadcrumb?: { name: string; slug: string }[];
title?: string;
description?: string;
title?: string | undefined;
description?: string | undefined;
illustration?: string;
illustrationSize?: string;
illustrationPosition?: string;
@ -34,14 +34,17 @@ const {
<slot name="header-title">
<h1>{title}</h1>
</slot>
<slot name="header-description">
<p>{description}</p>
</slot>
<div id="description">
<slot name="header-description">
<p>{description}</p>
</slot>
</div>
</div>
<div id="image-container"></div>
{illustration && <div id="image-container" />}
</div>
</header>
<main><slot name="main" /></main>
<main><slot /></main>
<Footer withLinks={breadcrumb.length > 0} />
</Html>
@ -78,8 +81,12 @@ const {
overflow-wrap: anywhere;
}
& > p {
& > #description {
max-width: 35em;
& > p {
line-height: 1.4;
}
}
}

View File

@ -1,6 +1,7 @@
---
import Button from "components/Button.astro";
import Tooltip from "components/Tooltip.astro";
import { cache } from "src/utils/cachedPayload";
import { getI18n } from "translations/translations";
interface Props {
@ -20,14 +21,17 @@ const { currentCurrency } = Astro.locals;
<Tooltip trigger="click" class={className}>
<div id="content" slot="tooltip-content">
<a
class:list={{ current: currentCurrency === "usd" }}
href="?action-currency=usd">USD</a
>
<a
class:list={{ current: currentCurrency === "eur" }}
href="?action-currency=eur">EUR</a
>
{
cache.currencies.map((id) => (
<a
class:list={{ current: currentCurrency === id }}
href={`?action-currency=${id}`}
data-astro-prefetch="tap"
>
{id}
</a>
))
}
</div>
<Button
icon="material-symbols:currency-exchange"

View File

@ -26,6 +26,10 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
)}`;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<footer>
{
withLinks && (
@ -36,6 +40,18 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
<Icon name="accords" />
<p>{t("footer.links.home.title")}</p>
</a>
<a href="/archives">
<Icon name="material-symbols:browse-outline" />
<p>{"Contents"}</p>
</a>
<a href="/chronicles">
<Icon name="material-symbols:book-2-outline" />
<p>{"Chronicles"}</p>
</a>
<a href="/changelog">
<Icon name="material-symbols:history" />
<p>{"Changelog"}</p>
</a>
<a href="/timeline">
<Icon name="material-symbols:calendar-month-outline" />
<p>{t("footer.links.timeline.title")}</p>
@ -126,10 +142,17 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
& > div {
max-width: 20em;
flex: 1;
@media (max-width: 35rem) {
max-width: unset;
}
}
& > #nav {
display: grid;
place-content: start;
flex: 1.5;
@media (max-width: 35rem) {
place-items: center;
@ -155,8 +178,6 @@ const contactLabel = `${t("footer.socials.contact.title")} - ${t(
margin-left: -0.6em;
@media (max-width: 65rem) {
grid-template-columns: 1fr;
margin-top: 0.5em;
gap: unset;
}

View File

@ -333,35 +333,27 @@ const { currentTheme } = Astro.locals;
}
.keycap {
transition-duration: 150ms;
transition-property: translate, box-shadow, background-color;
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
--foreground-color: var(--color-base-650);
color: var(--foreground-color);
border: 0.1rem solid var(--foreground-color);
background-color: var(--color-elevation-0);
box-shadow:
0 7px 15px -5px var(--color-shadow),
inset 0 0 50px var(--color-elevation-2),
inset 0 -7px 1px 5px var(--color-shadow-2);
transition-duration: 250ms;
transition-property: padding-top, box-shadow, background-color, color,
border-color;
transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
&:hover {
box-shadow:
0 7px 15px -2px var(--color-shadow),
inset 0 0 50px var(--color-elevation-2),
inset 0 -7px 1px 5px var(--color-shadow-2);
--foreground-color: var(--color-base-1000);
box-shadow: 0 2px 2px var(--color-shadow-2);
background-color: var(--color-elevation-1);
}
&:active {
transition-duration: 75ms;
translate: 0 5px;
--foreground-color: var(--color-base-1000);
background-color: var(--color-elevation-2);
box-shadow:
0 3px 5px -2px var(--color-shadow),
inset 0 0 50px var(--color-elevation-2),
inset 0 -6px 1px 6px var(--color-shadow-1);
box-shadow: 0 6px 12px 2px var(--color-shadow-2);
}
}
@ -377,11 +369,3 @@ const { currentTheme } = Astro.locals;
display: none;
}
</style>
<script>
import htmx from "htmx.org";
// On any page navigation, reprocess HTMX
document.addEventListener("astro:page-load", () => {
htmx.process(document.querySelector("main")!);
});
</script>

View File

@ -1,7 +1,8 @@
---
import Button from "components/Button.astro";
import Tooltip from "components/Tooltip.astro";
import { getI18n, locales } from "translations/translations";
import { cache } from "src/utils/cachedPayload";
import { getI18n } from "translations/translations";
interface Props {
withTitle?: boolean | undefined;
@ -21,13 +22,13 @@ const { t } = await getI18n(currentLocale);
<Tooltip trigger="click" class={className}>
<div id="content" slot="tooltip-content">
{
locales.map((locale) => (
cache.locales.map(id => (
<a
class:list={{ current: locale === currentLocale }}
href={`?action-lang=${locale}`}
class:list={{ current: currentLocale === id }}
href={`?action-lang=${id}`}
data-astro-prefetch="tap"
>
{locale.toString().toUpperCase()}
{id}
</a>
))
}

View File

@ -11,7 +11,8 @@ interface Props {
}
const { breadcrumb } = Astro.props;
const { t } = await getI18n(Astro.locals.currentLocale);
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
---
{
@ -44,10 +45,12 @@ const { t } = await getI18n(Astro.locals.currentLocale);
)
}
<div id="toolbar">
<Button
icon="material-symbols:search"
ariaLabel={t("header.topbar.search.tooltip")}
/>
<a href={getLocalizedUrl("/search")}>
<Button
icon="material-symbols:search"
ariaLabel={t("header.topbar.search.tooltip")}
/>
</a>
<div class="separator"></div>
<div class="when-no-js">

View File

@ -0,0 +1,18 @@
---
interface Props {
wrapper: (props?: any) => any;
condition: boolean;
}
const { wrapper: Wrapper, condition } = Astro.props;
---
{
condition ? (
<Wrapper>
<slot />
</Wrapper>
) : (
<slot />
)
}

View File

@ -0,0 +1,52 @@
---
interface Props {
id?: string;
class?: string;
href: string;
method?: "get" | "post" | "delete" | "put";
}
const { href, method = "get", class: className, id } = Astro.props;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<maso-actor href={href} method={method} class={className} id={id}>
<slot />
</maso-actor>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
maso-actor {
cursor: pointer;
}
</style>
{
/* ------------------------------------------- JS --------------------------------------------- */
}
<script>
import { customElement } from "src/utils/customElements";
customElement("maso-actor", (elem) => {
const href = elem.getAttribute("href");
const method = elem.getAttribute("method");
if (!href || !method) return;
elem.addEventListener("click", async () => {
const elementToReplace = elem.closest("maso-target");
if (!elementToReplace) return;
const result = await fetch(href);
if (!result.ok) return;
const newContent = await result.text();
elementToReplace.outerHTML = newContent;
});
});
</script>

View File

@ -0,0 +1,26 @@
---
interface Props {
id?: string;
class?: string;
}
const { class:className, id } = Astro.props;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<maso-target class={className} id={id}>
<slot />
</maso-target>
{
/* ------------------------------------------- JS --------------------------------------------- */
}
<script>
class MasoTarget extends HTMLElement {}
customElements.define("maso-target", MasoTarget);
</script>

View File

@ -4,33 +4,32 @@ interface Props {
trigger?: string | undefined;
}
const { class: className, trigger = "mouseenter focus" } = Astro.props;
const { class: className, trigger } = Astro.props;
---
<tippy-tooltip class={className} data-tippy-trigger={trigger}>
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<tippy-tooltip class={className} trigger={trigger}>
<template><slot name="tooltip-content" /></template>
<slot />
</tippy-tooltip>
{
/* ------------------------------------------- JS --------------------------------------------- */
}
<script>
import tippy from "tippy.js";
import htmx from "htmx.org";
import { customElement } from "src/utils/customElements";
class TippyTooltip extends HTMLElement {
constructor() {
super();
tippy(this, {
allowHTML: true,
content: (ref) =>
ref.querySelector(":scope > template")?.innerHTML ?? "",
interactive: true,
onMount: (instance) => {
htmx.process(instance.popper);
},
});
}
}
customElements.define("tippy-tooltip", TippyTooltip);
customElement("tippy-tooltip", (elem) => {
tippy(elem, {
allowHTML: true,
content: (ref) => ref.querySelector(":scope > template")?.innerHTML ?? "",
interactive: true,
trigger: elem.getAttribute("trigger") ?? "mouseenter focus",
});
});
</script>

4
src/env.d.ts vendored
View File

@ -4,8 +4,8 @@
declare namespace App {
interface Locals {
currentLocale: import("translations/translations").Locale
currentLocale: string
currentTheme: "dark" | "auto" | "light"
currentCurrency: "usd" | "eur"
currentCurrency: string
}
}

View File

@ -1,13 +1,18 @@
import { defineMiddleware, sequence } from "astro:middleware";
import { z } from "zod";
import {
defaultLocale,
getCookiePreferredLocale,
getCurrentLocale,
getPreferredLocale,
getBestAcceptedLanguage,
} from "translations/translations";
const cookieThemeSchema = z.enum(["dark", "light", "auto"]);
import {
CookieKeys,
getCookieCurrency,
getCookieLocale,
getCookieTheme,
isValidCurrency,
isValidLocale,
themeSchema,
} from "src/utils/cookies";
const getAbsoluteLocaleUrl = (locale: string, url: string) =>
`/${locale}${url}`;
@ -23,91 +28,74 @@ const redirect = (
});
};
const localeNegotiator = defineMiddleware(
({ cookies, url, request }, next) => {
const currentLocale = getCurrentLocale(url.pathname);
const preferredLocale = getPreferredLocale(request);
if (url.pathname.startsWith("/api/")) {
return next();
}
const cookiePreferredLocale = getCookiePreferredLocale(cookies);
if (!currentLocale) {
const redirectURL = getAbsoluteLocaleUrl(
cookiePreferredLocale ?? preferredLocale ?? defaultLocale,
url.pathname
);
return redirect(redirectURL);
}
if (cookiePreferredLocale) {
if (cookiePreferredLocale !== currentLocale) {
const pathnameWithoutLocale = url.pathname.substring(
currentLocale.length + 1
);
const redirectURL = getAbsoluteLocaleUrl(
cookiePreferredLocale,
pathnameWithoutLocale
);
return redirect(redirectURL);
}
} else if (preferredLocale) {
if (preferredLocale !== currentLocale) {
const pathnameWithoutLocale = url.pathname.substring(
currentLocale.length + 1
);
const redirectURL = getAbsoluteLocaleUrl(
preferredLocale,
pathnameWithoutLocale
);
return redirect(redirectURL);
}
}
const localeAgnosticPaths = ["/api/"];
const localeNegotiator = defineMiddleware(({ cookies, url, request }, next) => {
if (localeAgnosticPaths.some((prefix) => url.pathname.startsWith(prefix))) {
return next();
}
);
const currentLocale = getCurrentLocale(url.pathname);
const acceptedLocale = getBestAcceptedLanguage(request);
const cookieLocale = getCookieLocale(cookies);
const bestMatchingLocale = cookieLocale ?? acceptedLocale ?? defaultLocale;
if (!currentLocale) {
const redirectURL = getAbsoluteLocaleUrl(bestMatchingLocale, url.pathname);
return redirect(redirectURL);
}
if (currentLocale !== bestMatchingLocale) {
const pathnameWithoutLocale = url.pathname.substring(
currentLocale.length + 1
);
const redirectURL = getAbsoluteLocaleUrl(
bestMatchingLocale,
pathnameWithoutLocale
);
return redirect(redirectURL);
}
return next();
});
const handleActionsSearchParams = defineMiddleware(async ({ url }, next) => {
// TODO: Verify locale typing
const actionLang = url.searchParams.get("action-lang");
if (actionLang) {
if (isValidLocale(actionLang)) {
const currentLocale = getCurrentLocale(url.pathname);
const pathnameWithoutLocale = currentLocale
? url.pathname.substring(currentLocale.length + 1)
: url.pathname;
const redirectURL = getAbsoluteLocaleUrl(actionLang, pathnameWithoutLocale);
return redirect(redirectURL, {
"Set-Cookie": `al_pref_languages=${JSON.stringify([actionLang])}; Path=/`,
"Set-Cookie": `${CookieKeys.Languages}=${JSON.stringify([
actionLang,
])}; Path=/`,
});
}
// TODO: Verify currency typing
const actionCurrency = url.searchParams.get("action-currency");
if (actionCurrency) {
if (isValidCurrency(actionCurrency)) {
return redirect(url.pathname, {
"Set-Cookie": `al_pref_currency=${JSON.stringify(
"Set-Cookie": `${CookieKeys.Currency}=${JSON.stringify(
actionCurrency
)}; Path=/`,
});
}
const actionTheme = url.searchParams.get("action-theme");
const verifiedActionTheme = cookieThemeSchema.safeParse(actionTheme);
const verifiedActionTheme = themeSchema.safeParse(actionTheme);
if (verifiedActionTheme.success) {
url.searchParams.delete("action-theme");
if (verifiedActionTheme.data === "auto") {
return redirect(url.pathname, {
"Set-Cookie": `al_pref_theme=; Path=/; Expires=${new Date(
"Set-Cookie": `${CookieKeys.Theme}=; Path=/; Expires=${new Date(
0
).toUTCString()}`,
});
}
return redirect(url.pathname, {
"Set-Cookie": `al_pref_theme=${verifiedActionTheme.data}; Path=/`,
"Set-Cookie": `${CookieKeys.Theme}=${verifiedActionTheme.data}; Path=/`,
});
}
@ -126,12 +114,14 @@ const addContentLanguageResponseHeader = defineMiddleware(
}
);
const provideLocalsToRequest = defineMiddleware(async ({ url, locals, cookies }, next) => {
locals.currentLocale = getCurrentLocale(url.pathname) ?? "en";
locals.currentCurrency = cookies.get("al_pref_currency")?.value ?? "usd"
locals.currentTheme = cookies.get("al_pref_theme")?.value ?? "auto"
return next();
});
const provideLocalsToRequest = defineMiddleware(
async ({ url, locals, cookies }, next) => {
locals.currentLocale = getCurrentLocale(url.pathname) ?? "en";
locals.currentCurrency = getCookieCurrency(cookies) ?? "USD";
locals.currentTheme = getCookieTheme(cookies) ?? "auto";
return next();
}
);
export const onRequest = sequence(
addContentLanguageResponseHeader,

View File

@ -1 +1,9 @@
<h1>Oh nyo...</h1>
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="Oh nyo..." />

View File

@ -0,0 +1,9 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="WIP" />

View File

@ -0,0 +1,9 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="WIP" />

View File

@ -0,0 +1,9 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="WIP" />

View File

@ -0,0 +1,18 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
import Content from "pages/api/content.astro";
const { currentLocale } = Astro.locals;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout
breadcrumb={[{ name: "About us", slug: "about" }]}
title="About us"
description="This is a page to test the Temporary Language Override™ feature"
>
<Content lang={currentLocale} />
</AppLayout>

View File

@ -0,0 +1,9 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="WIP" />

View File

@ -1,95 +0,0 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
import FolderCard from "./_components/FolderCard.astro";
import { getI18n } from "translations/translations";
const {getLocalizedUrl} = await getI18n(Astro.locals.currentLocale)
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout
breadcrumb={[
{ name: "Drakengard", slug: "drakengard" },
]}
title="Drakengard"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
illustration="/img/categories/light/dod.png"
>
<div id="main" slot="main">
<FolderCard
title="Games"
icon="material-symbols:stadia-controller-outline"
href={getLocalizedUrl("/drakengard/games")}
/>
<FolderCard
title="Guidebooks"
icon="material-symbols:menu-book-outline"
href={getLocalizedUrl("/drakengard/guidebooks")}
/>
<FolderCard
title="Manga"
icon="material-symbols:menu-book-outline"
href={getLocalizedUrl("/drakengard/manga")}
/>
<FolderCard
title="Novels"
icon="material-symbols:menu-book-outline"
href={getLocalizedUrl("/drakengard/novels")}
/>
<FolderCard
title="Novellas"
icon="material-symbols:menu-book-outline"
href={getLocalizedUrl("/drakengard/novellas")}
/>
<FolderCard
title="Characters"
icon="material-symbols:person-outline"
href={getLocalizedUrl("/drakengard/characters")}
/>
<FolderCard
title="Places"
icon="material-symbols:location-on-outline"
href={getLocalizedUrl("/drakengard/places")}
/>
<FolderCard
title="Enemies"
icon="material-symbols:sentiment-extremely-dissatisfied-outline"
href={getLocalizedUrl("/drakengard/enemies")}
/>
<FolderCard
title="Terms"
icon="material-symbols:indeterminate-question-box"
href={getLocalizedUrl("/drakengard/terms")}
/>
<FolderCard
title="Weapons"
icon="material-symbols:swords-outline"
href={getLocalizedUrl("/drakengard/weapons")}
/>
<FolderCard
title="Timeline"
icon="material-symbols:calendar-month-outline"
href={getLocalizedUrl("/drakengard/timeline")}
/>
<FolderCard
title="News"
icon="material-symbols:newspaper"
href={getLocalizedUrl("/drakengard/news")}
/>
</div>
</AppLayout>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#main {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 0.7rem 1rem;
}
</style>

View File

@ -0,0 +1,67 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
import { payload } from "src/shared/payload/payload-sdk";
import { getI18n } from "translations/translations";
import RichText from "components/RichText/RichText.astro";
import FoldersSection from "./_components/FoldersSection.astro";
const { slug } = Astro.params;
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
if (!slug) {
return Astro.redirect("/en/404");
}
const folder = await payload.getFolder(slug);
const meta = getLocalizedMatch(folder.translations, { name: slug });
// TODO: handle folder not found
// TODO: handle rich text description
// TODO: handle light and dark illustration for applayout
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title={meta.name}>
{
meta.description && (
<div slot="header-description">
<RichText content={JSON.parse(meta.description)} />
</div>
)
}
{
folder.sections.type === "single" ? (
<FoldersSection folders={folder.sections.subfolders} />
) : (
<div id="sections">
{folder.sections.sections.map(({ subfolders, translations }) => (
<FoldersSection
folders={subfolders}
title={
getLocalizedMatch<{
language: string;
name: string | undefined;
}>(translations, { name: undefined }).name
}
/>
))}
</div>
)
}
</AppLayout>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#sections {
display: grid;
gap: 2.5em;
}
</style>

View File

@ -2,11 +2,11 @@
import { Icon } from "astro-icon/components";
interface Props {
title: string;
icon: string;
icon?: string | undefined;
href: string;
}
const { icon, title, href } = Astro.props;
const { icon = "material-symbols:folder-outline", title, href } = Astro.props;
---
<a href={href} class="keycap">
@ -22,8 +22,7 @@ const { icon, title, href } = Astro.props;
place-items: center;
gap: 1em;
color: var(--color-base-1000);
padding: 24px;
padding-top: 12px;
padding: 12px 24px;
border-radius: 12px;
text-decoration: none;

View File

@ -0,0 +1,46 @@
---
import type { EndpointFolderPreview } from "src/shared/payload/payload-sdk";
import FolderCard from "./FolderCard.astro";
import { getI18n } from "translations/translations";
interface Props {
title?: string | undefined;
folders: EndpointFolderPreview[];
}
const { title, folders } = Astro.props;
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(
Astro.locals.currentLocale
);
---
<div>
{title && <h3>{title}</h3>}
<section>
{
folders.map(({ slug, translations, icon }) => (
<FolderCard
title={getLocalizedMatch(translations, { name: slug }).name}
icon={icon ? `material-symbols:${icon}` : undefined}
href={getLocalizedUrl(`/folders/${slug}`)}
/>
))
}
</section>
</div>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
h3 {
margin-bottom: 1em;
}
section {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 0.7rem 1rem;
}
</style>

View File

@ -3,9 +3,11 @@ import { Icon } from "astro-icon/components";
import AppLayout from "components/AppLayout/AppLayout.astro";
import Button from "components/Button.astro";
import LinkCard from "../_components/LinkCard.astro";
import CategoryCard from "../_components/CategoryCard.astro";
import { getI18n } from "../../../translations/translations";
import ChronicleCard from "pages/_components/ChronicleCard.astro";
import LibraryGrid from "pages/_components/LibraryGrid.astro";
const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
---
@ -36,186 +38,142 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
</a>
</div>
<Fragment slot="main">
<div id="main">
<section id="library">
<h2>{t("home.librarySection.title")}</h2>
<p set:html={t("home.librarySection.description")} />
<div class="grid">
<CategoryCard
img={{
light: "/img/categories/light/dod.png",
dark: "/img/categories/dark/dod.png",
}}
name="Drakengard"
href={getLocalizedUrl("/drakengard")}
/>
<CategoryCard
img={{
light: "/img/categories/light/dod2.png",
dark: "/img/categories/dark/dod2.png",
}}
name="Drakengard 2"
href={getLocalizedUrl("/drakengard-2")}
/>
<CategoryCard
img={{
light: "/img/categories/light/dod3.png",
dark: "/img/categories/dark/dod3.png",
}}
name="Drakengard 3"
href={getLocalizedUrl("/drakengard-3")}
/>
<CategoryCard
img={{
light: "/img/categories/light/dod1.3.png",
dark: "/img/categories/dark/dod1.3.png",
}}
name="Drakengard 1.3"
href={getLocalizedUrl("/drakengard-1-3")}
/>
<CategoryCard
img={{
light: "/img/categories/light/nier.png",
dark: "/img/categories/dark/nier.png",
}}
name="NieR"
href={getLocalizedUrl("/nier")}
/>
<CategoryCard
img={{
light: "/img/categories/light/na.png",
dark: "/img/categories/dark/na.png",
}}
name="NieR:Automata"
href={getLocalizedUrl("/nier-automata")}
/>
<CategoryCard
img={{
light: "/img/categories/light/nier-rein.png",
dark: "/img/categories/dark/nier-rein.png",
}}
name="NieR Re[in]carnation"
href={getLocalizedUrl("/nier-rein")}
/>
<CategoryCard
img={{
light: "/img/categories/light/yorha.png",
dark: "/img/categories/dark/yorha.png",
}}
name="YoRHa"
href={getLocalizedUrl("/yorha")}
/>
<CategoryCard
img={{
light: "/img/categories/light/yorha-boys.png",
dark: "/img/categories/dark/yorha-boys.png",
}}
name="YoRHa Boys"
href={getLocalizedUrl("/yorha-boys")}
/>
<CategoryCard
img={{
light: "/img/categories/light/sino.png",
dark: "/img/categories/dark/sino.png",
}}
name="SINoALICE"
href={getLocalizedUrl("/sinoalice")}
/>
<CategoryCard
img={{
light: "/img/categories/light/404gamereset.png",
dark: "/img/categories/dark/404gamereset.png",
}}
name="404 Game Re:Set"
href={getLocalizedUrl("/404-game-reset")}
/>
<CategoryCard
img={{
light: "/img/categories/light/god-app.png",
dark: "/img/categories/dark/god-app.png",
}}
name="God App"
href={getLocalizedUrl("/god-app")}
/>
<CategoryCard
img={{
light: "/img/categories/light/voc.png",
dark: "/img/categories/dark/voc.png",
}}
name="Voice of Cards"
href={getLocalizedUrl("/voice-of-cards")}
/>
<CategoryCard name="Others..." href={getLocalizedUrl("/others")} />
</div>
</section>
<div id="main">
<section id="library">
<h2>{t("home.librarySection.title")}</h2>
<p set:html={t("home.librarySection.description")} />
<a href={getLocalizedUrl("/search")}>
<Button
class="section-button"
title={t("home.librarySection.button")}
icon="material-symbols:browse-outline"
/>
</a>
<div class="grid">
<LibraryGrid />
</div>
</section>
<section>
<h2>{t("home.moreSection.title")}</h2>
<p set:html={t("home.moreSection.description")} />
<div class="grid">
<LinkCard
icon="material-symbols:calendar-month-outline"
title={t("footer.links.timeline.title")}
subtitle={t("footer.links.timeline.subtitle", {
eraCount: 8,
eventCount: 358,
})}
href={getLocalizedUrl("/timeline")}
/>
<LinkCard
icon="material-symbols:perm-media-outline"
title={t("footer.links.gallery.title")}
subtitle={t("footer.links.gallery.subtitle", { count: 5750 })}
href="https://gallery.accords-library.com/posts"
/>
<LinkCard
icon="material-symbols:movie-outline"
title={t("footer.links.videos.title")}
subtitle={t("footer.links.videos.subtitle", { count: 2115 })}
href={getLocalizedUrl("/videos")}
/>
<LinkCard
icon="material-symbols:folder-zip-outline"
title={t("footer.links.webArchives.title")}
subtitle={t("footer.links.webArchives.subtitle", { count: 20 })}
href={getLocalizedUrl("/archives")}
/>
</div>
</section>
<section>
<h2>{t("home.chroniclesSection.title")}</h2>
<p set:html={t("home.chroniclesSection.description")} />
<div class="flex">
<ChronicleCard
pretitle={"Preface"}
title={"The Yokoverse"}
subtitle={"Start reading the Chronicles here"}
href={getLocalizedUrl("/chronicles/preface")}
/>
<ChronicleCard
pretitle={"Chapter 1"}
title={"Drakengard 3"}
subtitle={"A cursed world, and the girl who hated it"}
href={getLocalizedUrl("/chronicles/drakengard-3")}
/>
<ChronicleCard
pretitle={"Chapter 2"}
title={"Drakengard"}
subtitle={"Humanity at its lowest"}
href={getLocalizedUrl("/chronicles/drakengard")}
/>
<ChronicleCard
pretitle={"Chapter 3"}
title={"NieR"}
subtitle={"The Glory of Mankind comes to an abrupt End"}
href={getLocalizedUrl("/chronicles/nier")}
/>
<ChronicleCard
pretitle={"Chapter 4"}
title={"NieR:Automata"}
subtitle={"Protagonists of Meaningless Stories"}
href={getLocalizedUrl("/chronicles/nier-automata")}
/>
<ChronicleCard
pretitle={"Epilogue"}
title={"Appendices"}
subtitle={"Protagonists of Meaningless Stories"}
href={getLocalizedUrl("/chronicles/nier-automata")}
/>
</div>
</section>
<section>
<h2>{t("home.linksSection.title")}</h2>
<p set:html={t("home.linksSection.description")} />
<div class="grid">
<LinkCard
icon="discord-brands"
title={t("footer.socials.discord.title")}
subtitle={t("footer.socials.discord.subtitle")}
href="/discord"
/>
<LinkCard
icon="x-brands"
title={t("footer.socials.twitter.title")}
subtitle={t("footer.socials.twitter.subtitle")}
href="https://twitter.com/AccordsLibrary"
/>
<LinkCard
icon="github-brands"
title={t("footer.socials.github.title")}
subtitle={t("footer.socials.github.subtitle")}
href="https://github.com/Accords-Library"
/>
<LinkCard
icon="material-symbols:mail-outline"
title={t("footer.socials.contact.title")}
subtitle={t("footer.socials.contact.subtitle")}
href={getLocalizedUrl("/contact")}
/>
</div>
</section>
</div>
</Fragment>
<section>
<h2>{t("home.changesSection.title")}</h2>
<p set:html={t("home.changesSection.description")} />
<a href={getLocalizedUrl("/changelog")}>
<Button
class="section-button"
title={t("home.changesSection.button")}
icon="material-symbols:history"
/>
</a>
</section>
<section>
<h2>{t("home.moreSection.title")}</h2>
<p set:html={t("home.moreSection.description")} />
<div class="grid">
<LinkCard
icon="material-symbols:calendar-month-outline"
title={t("footer.links.timeline.title")}
subtitle={t("footer.links.timeline.subtitle", {
eraCount: 8,
eventCount: 358,
})}
href={getLocalizedUrl("/timeline")}
/>
<LinkCard
icon="material-symbols:perm-media-outline"
title={t("footer.links.gallery.title")}
subtitle={t("footer.links.gallery.subtitle", { count: 5750 })}
href="https://gallery.accords-library.com/posts"
/>
<LinkCard
icon="material-symbols:movie-outline"
title={t("footer.links.videos.title")}
subtitle={t("footer.links.videos.subtitle", { count: 2115 })}
href={getLocalizedUrl("/videos")}
/>
<LinkCard
icon="material-symbols:folder-zip-outline"
title={t("footer.links.webArchives.title")}
subtitle={t("footer.links.webArchives.subtitle", { count: 20 })}
href={getLocalizedUrl("/archives")}
/>
</div>
</section>
<section>
<h2>{t("home.linksSection.title")}</h2>
<p set:html={t("home.linksSection.description")} />
<div class="grid">
<LinkCard
icon="discord-brands"
title={t("footer.socials.discord.title")}
subtitle={t("footer.socials.discord.subtitle")}
href="/discord"
/>
<LinkCard
icon="x-brands"
title={t("footer.socials.twitter.title")}
subtitle={t("footer.socials.twitter.subtitle")}
href="https://twitter.com/AccordsLibrary"
/>
<LinkCard
icon="github-brands"
title={t("footer.socials.github.title")}
subtitle={t("footer.socials.github.subtitle")}
href="https://github.com/Accords-Library"
/>
<LinkCard
icon="material-symbols:mail-outline"
title={t("footer.socials.contact.title")}
subtitle={t("footer.socials.contact.subtitle")}
href={getLocalizedUrl("/contact")}
/>
</div>
</section>
</div>
</AppLayout>
{
@ -230,11 +188,6 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
align-items: flex-start;
margin-bottom: 128px;
& > p {
line-height: 1.4;
max-width: 35em;
}
@media (max-width: 35rem) {
align-items: center;
@ -315,8 +268,12 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
& > p {
max-width: 35em;
line-height: 1.4;
padding-top: 8px;
padding-bottom: 24px;
margin-top: 8px;
margin-bottom: 24px;
}
& > a > :global(.section-button) {
margin-bottom: 24px;
}
&#library {
@ -337,6 +294,18 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
column-gap: clamp(6px, 2vmin, 16px);
row-gap: clamp(6px + 6px, 2vmin + 6px, 16px);
}
& > .flex {
display: flex;
column-gap: clamp(6px, 2vmin, 16px);
row-gap: clamp(6px + 6px, 2vmin + 6px, 16px);
flex-wrap: wrap;
@media (max-width: 35rem) {
display: grid;
grid-template-columns: 1fr;
}
}
}
}
</style>

View File

@ -1,32 +0,0 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
import Content from "pages/api/content.astro";
const { currentLocale } = Astro.locals;
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout
breadcrumb={[{ name: "Drakengard", slug: "drakengard" }]}
title="Others"
description="This is a page to test the Temporary Language Override™ feature"
>
<div id="main" slot="main">
<Content lang={currentLocale} />
</div>
</AppLayout>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#main {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 0.7rem 1rem;
}
</style>

View File

@ -1,35 +1,36 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
import { getI18n, locales } from "translations/translations";
import { cache } from "src/utils/cachedPayload";
import { getI18n } from "translations/translations";
const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
const { t } = await getI18n(currentLocale);
---
<AppLayout
title="Settings"
breadcrumb={[{ name: "Settings", slug: "settings" }]}
title={t("settings.title")}
breadcrumb={[{ name: t("settings.title"), slug: "settings" }]}
>
<div id="main" slot="main">
<div id="main">
<div class="section">
<h2>Language</h2>
<p>{t("header.topbar.language.tooltip")}</p><br />
<h2>{t("settings.language.title")}</h2>
<p>{t("settings.language.description")}</p><br />
{
locales.map((locale) => (
cache.locales.map((id) => (
<a
class:list={{ current: locale === currentLocale }}
href={`?action-lang=${locale}`}
class:list={{ current: currentLocale === id }}
href={`?action-lang=${id}`}
data-astro-prefetch="tap"
>
{locale.toString().toUpperCase()}
{id}
</a>
))
}
</div>
<div class="section">
<h2>Theme</h2>
<p>{t("header.topbar.theme.tooltip")}</p><br />
<h2>{t("settings.theme.title")}</h2>
<p>{t("settings.theme.description")}</p><br />
<a
class:list={{ current: currentTheme === "dark" }}
href="?action-theme=dark"
@ -54,18 +55,19 @@ const { t } = await getI18n(currentLocale);
</div>
<div class="section">
<h2>Currency</h2>
<p>{t("header.topbar.currency.tooltip")}</p><br />
<a
class:list={{ current: currentCurrency === "usd" }}
href="?action-currency=usd"
data-astro-prefetch="tap">USD</a
>
<a
class:list={{ current: currentCurrency === "eur" }}
href="?action-currency=eur"
data-astro-prefetch="tap">EUR</a
>
<h2>{t("settings.theme.title")}</h2>
<p>{t("settings.theme.description")}</p><br />
{
cache.currencies.map((id) => (
<a
class:list={{ current: currentCurrency === id }}
href={`?action-currency=${id}`}
data-astro-prefetch="tap"
>
{id}
</a>
))
}
</div>
</div>
</AppLayout>

View File

@ -0,0 +1,9 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="WIP" />

View File

@ -0,0 +1,9 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
---
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<AppLayout title="WIP" />

View File

@ -1,6 +1,6 @@
---
interface Props {
img?: { light: string; dark: string };
img?: { light: string; dark: string } | undefined;
name: string;
href: string;
}
@ -30,7 +30,6 @@ const { img, name, href } = Astro.props;
place-items: center;
padding: 24px;
padding-bottom: 32px;
border-radius: 12px;
user-select: none;

View File

@ -0,0 +1,52 @@
---
interface Props {
pretitle: string;
title: string;
subtitle: string;
href: string;
}
const { pretitle, subtitle, title, href } = Astro.props;
---
<a href={href} class="keycap">
<p class="pretitle">{pretitle}</p>
<h3>{title}</h3>
<p>{subtitle}</p>
</a>
<style>
a {
display: flex;
place-items: center;
gap: 1em;
color: var(--color-base-1000);
padding: 1.5em;
border-radius: 0.75em;
text-decoration: none;
& > svg {
width: clamp(1.5em, 6vw + 0.8em, 3em);
height: clamp(1.5em, 6vw + 0.8em, 3em);
flex-shrink: 0;
}
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.15em;
& > h3 {
font-size: 1.5em;
margin-bottom: 0.5em;
}
& > .pretitle {
font-size: 1em;
}
& > p {
font-size: 0.8em;
}
}
</style>

View File

@ -0,0 +1,24 @@
---
import { payload } from "src/shared/payload/payload-sdk";
import { getI18n } from "translations/translations";
import CategoryCard from "./CategoryCard.astro";
const folders = await payload.getRootFolders()
const { getLocalizedUrl, getLocalizedMatch } = await getI18n(
Astro.locals.currentLocale
);
---
{
folders.map(({ slug, translations, darkThumbnail, lightThumbnail }) => (
<CategoryCard
img={
darkThumbnail && lightThumbnail
? { dark: darkThumbnail.url, light: lightThumbnail.url }
: undefined
}
name={getLocalizedMatch(translations, { name: slug }).name}
href={getLocalizedUrl(`/folders/${slug}`)}
/>
))
}

View File

@ -25,7 +25,6 @@ const { icon, subtitle, title, href } = Astro.props;
gap: 1em;
color: var(--color-base-1000);
padding: 1.5em;
padding-top: 0.75em;
border-radius: 0.75em;
text-decoration: none;

View File

@ -1,7 +1,9 @@
---
import Button from "components/Button.astro";
import Tooltip from "components/Tooltip.astro";
import { getI18n, locales } from "translations/translations";
import MasoActor from "components/Maso/MasoActor.astro";
import MasoTarget from "components/Maso/MasoTarget.astro";
import { getI18n } from "translations/translations";
export const partial = true;
@ -15,7 +17,11 @@ const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
const { t } = await getI18n(lang);
---
<div class="hx-swap-content">
{
/* ------------------------------------------- HTML ------------------------------------------- */
}
<MasoTarget>
<Tooltip trigger="click" class="when-js">
<Button
icon="material-symbols:translate"
@ -25,33 +31,30 @@ const { t } = await getI18n(lang);
<div id="content" slot="tooltip-content">
{
locales.map((locale) => (
<a
["en", "fr"].map((locale) => (
<MasoActor
class:list={{ current: locale === lang }}
hx-get={`/api/content?lang=${locale}`}
hx-trigger="click"
hx-target="closest .hx-swap-content"
hx-swap="outerHTML"
href={`/api/content?lang=${locale}`}
>
{locale.toString().toUpperCase()}
</a>
</MasoActor>
))
}
</div>
</Tooltip>
<div set:html={t("home.description")} />
</div>
</MasoTarget>
{
/* ------------------------------------------- CSS -------------------------------------------- */
}
<style>
#content {
display: grid;
gap: 0.5em;
& > a {
cursor: pointer;
}
& > .current {
& > :global(.current) {
color: var(--color-base-750);
text-decoration: underline 0.08em var(--color-base-650);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
import { payload } from "src/shared/payload/payload-sdk";
type Cache = {
locales: string[];
currencies: string[];
};
const fetchNewData = async (): Promise<Cache> => ({
locales: (await payload.getLanguages()).map(({ id }) => id),
currencies: (await payload.getCurrencies()).map(({ id }) => id),
});
export let cache = await fetchNewData();
setInterval(async () => {
console.log("Refreshing cached Payload data")
cache = await fetchNewData();
}, 1000_000);

56
src/utils/cookies.ts Normal file
View File

@ -0,0 +1,56 @@
import type { AstroCookies } from "astro";
import { cache } from "src/utils/cachedPayload";
import { z } from "zod";
export enum CookieKeys {
Currency = "al_pref_currency",
Theme = "al_pref_theme",
Languages = "al_pref_languages",
}
export const themeSchema = z.enum(["dark", "light", "auto"]);
export const getCookieLocale = (cookies: AstroCookies): string | undefined => {
const cookie = cookies.get(CookieKeys.Languages);
try {
const json = cookie?.json();
const result = z.array(z.string()).nonempty().safeParse(json);
if (result.success && isValidLocale(result.data[0])) {
return result.data[0];
}
} catch (e) {
console.error(e);
}
return undefined;
};
export const getCookieCurrency = (
cookies: AstroCookies
): string | undefined => {
const cookieValue = cookies.get(CookieKeys.Currency)?.value;
return isValidCurrency(cookieValue)
? cookieValue
: undefined;
};
export const getCookieTheme = (
cookies: AstroCookies
): z.infer<typeof themeSchema> | undefined => {
const cookieValue = cookies.get(CookieKeys.Theme)?.value;
const result = themeSchema.safeParse(cookieValue);
return result.success ? result.data : undefined;
};
export const isValidCurrency = (
currency: string | null | undefined
): currency is string =>
currency !== null &&
currency != undefined &&
cache.currencies.includes(currency);
export const isValidLocale = (
locale: string | null | undefined
): locale is string =>
locale !== null && locale != undefined && cache.locales.includes(locale);

View File

@ -0,0 +1,12 @@
export const customElement = (
name: string,
constructor?: (elem: HTMLElement) => void
) => {
class CustomElementClass extends HTMLElement {
constructor() {
super();
constructor?.(this);
}
}
customElements.define(name, CustomElementClass);
};

View File

@ -6,11 +6,25 @@
"home.aboutUsButton": "Read more about us",
"home.librarySection.title": "The Library",
"home.librarySection.description": "Here you will find a list of IPs Yoko Taro worked on. Select one to discover all the media/content/articles that relates to this IP. <strong>Beware there can be spoilers.</strong>",
"home.librarySection.button": "Browse all content",
"home.chroniclesSection.title": "The Chronicles",
"home.chroniclesSection.description": "Interested in exploring the Yokoverse lore? Experience all events and content in chronological order. <strong>Beware there can be spoilers.</strong>",
"home.changesSection.title": "Whats new?",
"home.changesSection.description": "Here are the 10 most recently added/updated content. You can open the changelog to see all past changes.",
"home.changesSection.button": "Open the changelog",
"home.moreSection.title": "More content",
"home.moreSection.description": "The NieR and Drakengard series share a common timeline which you can explore it at the link bellow. Also we have gathered thousands of official artworks, videos, and notable web resources. <strong>Beware there can be spoilers.</strong>",
"home.linksSection.title": "Links",
"home.linksSection.description": "Do you have a <strong>question</strong>? Would like to share something with our <strong>community</strong>? Are you interested in <strong>contributing</strong> to this project? Whatever it is, you should find what you are looking for at the following links.",
"settings.title": "Settings",
"settings.theme.title": "Theme",
"settings.theme.description": "Switch between dark/light mode",
"settings.language.title": "Language",
"settings.language.description": "Select preferred language",
"settings.currency.title": "Currency",
"settings.currency.description": "Select preferred currency",
"header.topbar.search.tooltip": "Search on this website",
"header.topbar.theme.tooltip": "Switch between dark/light mode",
"header.topbar.theme.dark": "Dark",

View File

@ -1,16 +1,15 @@
import type { AstroCookies } from "astro";
import { cache } from "src/utils/cachedPayload";
import en from "./en.json";
import fr from "./fr.json";
import ja from "./ja.json"
import ja from "./ja.json";
import acceptLanguage from 'accept-language';
import { z } from "zod";
import acceptLanguage from "accept-language";
type WordingKeys = keyof typeof en;
const translationFiles: Record<string, Record<WordingKeys, string>> = {
en,
fr,
ja
ja,
};
export const getI18n = async (locale: string) => {
@ -118,6 +117,13 @@ export const getI18n = async (locale: string) => {
return `«${key}»`;
},
getLocalizedUrl: (url: string): string => `/${locale}${url}`,
getLocalizedMatch: <T extends { language: string }>(
options: T[],
fallback: Omit<T, "language">
): Omit<T, "language"> =>
options.find(({ language }) => language === locale) ??
options.find(({ language }) => language === defaultLocale) ??
fallback,
};
};
@ -140,15 +146,11 @@ const limitMatchToBalanceCurlyBraces = (
return match.substring(0, index);
};
export const locales = ["en", "es", "fr", "ja", "pt", "zh"] as const;
acceptLanguage.languages([...locales]);
export type Locale = (typeof locales)[number];
export type Locale = string;
export const defaultLocale: Locale = "en";
export const getCurrentLocale = (pathname: string): Locale | undefined => {
for (const locale of locales) {
for (const locale of cache.locales) {
if (pathname.startsWith(`/${locale}`)) {
return locale;
}
@ -156,29 +158,14 @@ export const getCurrentLocale = (pathname: string): Locale | undefined => {
return undefined;
};
export const getPreferredLocale = (request: Request): Locale | undefined => {
return acceptLanguage.get(request.headers.get("Accept-Language")) as Locale | null ?? undefined;
};
export const getCookiePreferredLocale = (
cookies: AstroCookies
): string | undefined => {
const alPrefLanguages = cookies.get("al_pref_languages");
try {
const json = alPrefLanguages?.json();
const result = z.array(z.string()).nonempty().safeParse(json);
if (result.success) {
for (const value of result.data) {
if (locales.includes(value as Locale)) {
return value;
}
}
}
} catch (e) {
console.error(e);
return undefined;
}
return undefined;
export const getBestAcceptedLanguage = (
request: Request
): Locale | undefined => {
acceptLanguage.languages(cache.locales);
return (
(acceptLanguage.get(
request.headers.get("Accept-Language")
) as Locale | null) ?? undefined
);
};