Add i18n module and fix missing CopyrightedCharacters
This commit is contained in:
parent
7ca6f52cc2
commit
4f83ae8802
11 changed files with 270 additions and 196 deletions
|
|
@ -1,25 +1,59 @@
|
|||
---
|
||||
import { getImage } from "astro:assets";
|
||||
import { type CollectionEntry, getEntry, getEntries } from "astro:content";
|
||||
import { type CollectionEntry, getEntry, getEntries, getCollection } from "astro:content";
|
||||
import { Markdown } from "@astropub/md";
|
||||
import { slug } from "github-slugger";
|
||||
import { DEFAULT_LANG, t } from "../i18n";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import Authors from "../components/Authors.astro";
|
||||
import CopyrightedCharacters from "../components/CopyrightedCharacters.astro";
|
||||
import Prose from "../components/Prose.astro";
|
||||
import MastodonComments from "../components/MastodonComments.astro";
|
||||
import UserComponent from "../components/UserComponent.astro";
|
||||
|
||||
type Props = CollectionEntry<"games">["data"];
|
||||
|
||||
const { props } = Astro;
|
||||
const series = props.series && (await getEntry(props.series));
|
||||
const authors = await getEntries([props.authors].flat());
|
||||
const copyrightedCharacters: Record<string, CollectionEntry<"users">> = {};
|
||||
Object.keys(props.copyrightedCharacters).forEach(async (character) => {
|
||||
copyrightedCharacters[character] = await getEntry(props.copyrightedCharacters[character]);
|
||||
});
|
||||
if ("" in props.copyrightedCharacters && Object.keys(props.copyrightedCharacters).length > 1) {
|
||||
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
|
||||
}
|
||||
const copyrightedCharacters = await Promise.all(
|
||||
Object.values(
|
||||
Object.keys(props.copyrightedCharacters).reduce(
|
||||
(acc, character) => {
|
||||
const user = props.copyrightedCharacters[character];
|
||||
if (!(user.id in acc)) {
|
||||
acc[user.id] = [getEntry(user), []];
|
||||
}
|
||||
acc[user.id][1].push(character);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, [Promise<CollectionEntry<"users">>, string[]]>,
|
||||
),
|
||||
).map(async ([userPromise, characters]) => [await userPromise, characters] as [CollectionEntry<"users">, string[]]),
|
||||
);
|
||||
// const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
||||
// const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
||||
const categorizedTags = Object.fromEntries(
|
||||
(await getCollection("tag-categories")).flatMap((category) =>
|
||||
category.data.tags.map((tag) => {
|
||||
if (typeof tag === "string") {
|
||||
return [tag, tag];
|
||||
}
|
||||
return [t(DEFAULT_LANG, tag as any), t(props.lang, tag as any)];
|
||||
}),
|
||||
),
|
||||
);
|
||||
const tags = props.tags.map<[string, string]>((tag) => {
|
||||
const tagSlug = slug(tag);
|
||||
if (!(tag in categorizedTags)) {
|
||||
console.log(`Tag "${tag}" doesn't have a category in tag-categories!`);
|
||||
return [tagSlug, tag];
|
||||
}
|
||||
return [tagSlug, categorizedTags[tag]!];
|
||||
});
|
||||
const thumbnail =
|
||||
props.thumbnail &&
|
||||
(await getImage({ src: props.thumbnail, width: props.thumbnailWidth, height: props.thumbnailHeight }));
|
||||
|
|
@ -37,8 +71,14 @@ const thumbnail =
|
|||
id="top"
|
||||
class="min-w-screen relative min-h-screen bg-radial from-bm-300 to-bm-600 px-1 pb-16 pt-20 dark:from-green-700 dark:to-green-950 print:bg-none"
|
||||
>
|
||||
<div id="toolbox-buttons" aria-label="Toolbox" class="absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full">
|
||||
<div class="sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden">
|
||||
<div
|
||||
id="toolbox-buttons"
|
||||
aria-label="Toolbox"
|
||||
class="pointer-events-none absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-auto sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden"
|
||||
>
|
||||
<a
|
||||
href={series ? series.data.url : "/games"}
|
||||
class="text-link my-1 h-9 w-9 p-2"
|
||||
|
|
@ -89,7 +129,9 @@ const thumbnail =
|
|||
id="game-information"
|
||||
class="mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
|
||||
>
|
||||
<Authors authors={authors} lang={props.lang} />
|
||||
<Authors lang={props.lang}>
|
||||
{authors.map((author) => <UserComponent lang={props.lang} user={author} />)}
|
||||
</Authors>
|
||||
{
|
||||
props.isDraft ? (
|
||||
<p id="draft-warning" class="py-2 text-center text-2xl font-semibold not-italic text-red-600">
|
||||
|
|
@ -142,9 +184,7 @@ const thumbnail =
|
|||
year: "numeric",
|
||||
})}
|
||||
>
|
||||
{props.lang === "tok"
|
||||
? `tenpo suno ${props.pubDate.toISOString().slice(undefined, 10)}`
|
||||
: props.pubDate.toISOString().slice(undefined, 10)}
|
||||
{t(props.lang, "story/publish_date", props.pubDate.toISOString().slice(undefined, 10))}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
|
@ -170,10 +210,10 @@ const thumbnail =
|
|||
<h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">Tags</h2>
|
||||
<ul class="flex flex-wrap gap-x-2 gap-y-2 px-2">
|
||||
{
|
||||
props.tags.map((tag) => (
|
||||
tags.map(([tagSlug, tagText]) => (
|
||||
<li class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm dark:bg-bm-600 dark:text-white print:bg-none">
|
||||
<a class="hover:underline focus:underline" href={`/tags/${slug(tag)}`}>
|
||||
{tag}
|
||||
<a class="hover:underline focus:underline" href={`/tags/${tagSlug}`}>
|
||||
{tagText}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
|
|
@ -187,13 +227,9 @@ const thumbnail =
|
|||
/>
|
||||
</main>
|
||||
<div class="pt-6 text-center text-xs text-black dark:text-white">
|
||||
<span
|
||||
>© {
|
||||
props.lang === "tok" ? `tenpo pi sike suno ${props.pubDate.getFullYear()}` : props.pubDate.getFullYear()
|
||||
} |
|
||||
</span>
|
||||
<span>{t(props.lang, "story/copyright_year", props.pubDate.getFullYear())} | </span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank"
|
||||
>{props.lang === "eng" ? "Licenses" : props.lang === "tok" ? "lipu lawa" : null}</a
|
||||
>{t(props.lang, "story/licenses")}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { getImage } from "astro:assets";
|
|||
import { type CollectionEntry, getEntry, getEntries, getCollection } from "astro:content";
|
||||
import { Markdown } from "@astropub/md";
|
||||
import { slug } from "github-slugger";
|
||||
import { DEFAULT_LANG, t } from "../i18n";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import Authors from "../components/Authors.astro";
|
||||
import UserComponent from "../components/UserComponent.astro";
|
||||
|
|
@ -25,10 +26,24 @@ const series = props.series && (await getEntry(props.series));
|
|||
const authors = await getEntries([props.authors].flat());
|
||||
const commissioner = props.commissioner && (await getEntry(props.commissioner));
|
||||
const requester = props.requester && (await getEntry(props.requester));
|
||||
const copyrightedCharacters: Record<string, CollectionEntry<"users">> = {};
|
||||
Object.keys(props.copyrightedCharacters).forEach(async (character) => {
|
||||
copyrightedCharacters[character] = await getEntry(props.copyrightedCharacters[character]);
|
||||
});
|
||||
if ("" in props.copyrightedCharacters && Object.keys(props.copyrightedCharacters).length > 1) {
|
||||
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
|
||||
}
|
||||
const copyrightedCharacters = await Promise.all(
|
||||
Object.values(
|
||||
Object.keys(props.copyrightedCharacters).reduce(
|
||||
(acc, character) => {
|
||||
const user = props.copyrightedCharacters[character];
|
||||
if (!(user.id in acc)) {
|
||||
acc[user.id] = [getEntry(user), []];
|
||||
}
|
||||
acc[user.id][1].push(character);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, [Promise<CollectionEntry<"users">>, string[]]>,
|
||||
),
|
||||
).map(async ([userPromise, characters]) => [await userPromise, characters] as [CollectionEntry<"users">, string[]]),
|
||||
);
|
||||
const relatedStories = (await getEntries(props.relatedStories)).filter((story) => !story.data.isDraft);
|
||||
// const relatedGames = (await getEntries(props.relatedGames)).filter((game) => !game.data.isDraft);
|
||||
const categorizedTags = Object.fromEntries(
|
||||
|
|
@ -37,11 +52,7 @@ const categorizedTags = Object.fromEntries(
|
|||
if (typeof tag === "string") {
|
||||
return [tag, tag];
|
||||
}
|
||||
const key = tag["eng"]!;
|
||||
if (props.lang in tag) {
|
||||
return [key, tag[props.lang]!];
|
||||
}
|
||||
return [key, key];
|
||||
return [t(DEFAULT_LANG, tag as any), t(props.lang, tag as any)];
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
|
@ -70,16 +81,20 @@ const thumbnail =
|
|||
id="top"
|
||||
class="min-w-screen relative min-h-screen bg-radial from-bm-300 to-bm-600 px-1 pb-16 pt-20 dark:from-green-700 dark:to-green-950 print:bg-none"
|
||||
>
|
||||
<div id="toolbox-buttons" aria-label="Toolbox" class="absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full">
|
||||
<div class="sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden">
|
||||
<div
|
||||
id="toolbox-buttons"
|
||||
aria-label="Toolbox"
|
||||
class="pointer-events-none absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-auto sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden"
|
||||
>
|
||||
<a
|
||||
href={series ? series.data.url : "/stories/1"}
|
||||
class="text-link my-1 h-9 w-9 p-2"
|
||||
aria-label={props.lang === "eng"
|
||||
? `Return to ${series ? series.data.name : "stories"}`
|
||||
: props.lang === "tok"
|
||||
? "o tawa e lipu ale"
|
||||
: null}
|
||||
aria-label={series
|
||||
? t(props.lang, "story/return_to_series", series.data.name)
|
||||
: t(props.lang, "story/return_to_stories")}
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="fill-current" aria-hidden="true">
|
||||
<path
|
||||
|
|
@ -90,7 +105,7 @@ const thumbnail =
|
|||
<a
|
||||
href="#description"
|
||||
class="text-link my-1 h-9 w-9 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||
aria-label={props.lang === "eng" ? "Go to description" : props.lang === "tok" ? "o tawa e toki lipu" : null}
|
||||
aria-label={t(props.lang, "story/go_to_description")}
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="fill-current" aria-hidden="true">
|
||||
<path
|
||||
|
|
@ -101,7 +116,7 @@ const thumbnail =
|
|||
<button
|
||||
data-dark-mode
|
||||
class="text-link my-1 h-9 w-9 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||
aria-label={props.lang === "eng" ? "Toggle dark mode" : props.lang === "tok" ? "o ante e kule" : null}
|
||||
aria-label={t(props.lang, "story/toggle_dark_mode")}
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="hidden fill-current dark:block" aria-hidden="true">
|
||||
<path
|
||||
|
|
@ -161,7 +176,9 @@ const thumbnail =
|
|||
id="story-information"
|
||||
class="mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
|
||||
>
|
||||
<Authors authors={authors} lang={props.lang} />
|
||||
<Authors lang={props.lang}>
|
||||
{authors.map((author) => <UserComponent lang={props.lang} user={author} />)}
|
||||
</Authors>
|
||||
{
|
||||
props.isDraft ? (
|
||||
<p id="draft-warning" class="py-2 text-center text-2xl font-semibold not-italic text-red-600">
|
||||
|
|
@ -185,7 +202,7 @@ const thumbnail =
|
|||
}
|
||||
<div id="content-warning">
|
||||
<p>
|
||||
{props.lang === "eng" ? `Word count: ${props.wordCount}.` : props.lang === "tok" ? `` : null}
|
||||
{t(props.lang, "story/word_count", props.wordCount)}
|
||||
{props.contentWarning}
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -231,15 +248,13 @@ const thumbnail =
|
|||
year: "numeric",
|
||||
})}
|
||||
>
|
||||
{props.lang === "tok"
|
||||
? `tenpo suno ${props.pubDate.toISOString().slice(undefined, 10)}`
|
||||
: props.pubDate.toISOString().slice(undefined, 10)}
|
||||
{t(props.lang, "story/publish_date", props.pubDate.toISOString().slice(undefined, 10))}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<section id="description" class="px-2 font-serif" aria-describedby="title-description">
|
||||
<h2 id="title-description" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.lang === "eng" ? "Description" : props.lang === "tok" ? "toki lipu" : null}
|
||||
{t(props.lang, "story/description")}
|
||||
</h2>
|
||||
<Prose>
|
||||
<Markdown of={props.description} />
|
||||
|
|
@ -250,15 +265,11 @@ const thumbnail =
|
|||
props.summary ? (
|
||||
<section id="summary" class="px-2 font-serif" aria-describedby="title-summary">
|
||||
<h2 id="title-summary" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.lang === "eng" ? "Summary" : props.lang === "tok" ? "lipu tawa tenpo lili" : null}
|
||||
{t(props.lang, "story/summary")}
|
||||
</h2>
|
||||
<details class="mb-6 mt-1 rounded-lg border border-stone-400 bg-stone-50 text-stone-800 dark:border-stone-500 dark:bg-stone-900 dark:text-stone-100">
|
||||
<summary class="rounded-lg bg-stone-200 px-2 py-1 dark:bg-stone-800">
|
||||
{props.lang === "eng"
|
||||
? "Click to reveal"
|
||||
: props.lang === "tok"
|
||||
? "Click to reveal summary in English"
|
||||
: null}
|
||||
{t(props.lang, "story/reveal_summary")}
|
||||
</summary>
|
||||
<div class="px-2 py-1">
|
||||
<Prose>
|
||||
|
|
@ -275,7 +286,7 @@ const thumbnail =
|
|||
><path
|
||||
d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"
|
||||
></path></svg
|
||||
><span>{props.lang === "eng" ? "To top" : props.lang === "tok" ? "tawa sewi" : null}</span></a
|
||||
><span>{t(props.lang, "story/to_top")}</span></a
|
||||
>
|
||||
</div>
|
||||
{
|
||||
|
|
@ -334,7 +345,7 @@ const thumbnail =
|
|||
}
|
||||
<section id="tags" aria-describedby="title-tags" class="my-5">
|
||||
<h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.lang === "eng" ? "Tags" : props.lang === "tok" ? "nimi kulupu" : null}
|
||||
{t(props.lang, "story/tags")}
|
||||
</h2>
|
||||
<ul class="flex flex-wrap gap-x-2 gap-y-2 px-2">
|
||||
{
|
||||
|
|
@ -355,13 +366,9 @@ const thumbnail =
|
|||
/>
|
||||
</main>
|
||||
<div class="pt-6 text-center text-xs text-black dark:text-white">
|
||||
<span
|
||||
>© {
|
||||
props.lang === "tok" ? `tenpo pi sike suno ${props.pubDate.getFullYear()}` : props.pubDate.getFullYear()
|
||||
} |
|
||||
</span>
|
||||
<span>{t(props.lang, "story/copyright_year", props.pubDate.getFullYear())} | </span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank"
|
||||
>{props.lang === "eng" ? "Licenses" : props.lang === "tok" ? "lipu lawa" : null}</a
|
||||
>{t(props.lang, "story/licenses")}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue