Add i18n module and fix missing CopyrightedCharacters

This commit is contained in:
Bad Manners 2024-03-29 22:48:36 -03:00
parent 7ca6f52cc2
commit 4f83ae8802
11 changed files with 270 additions and 196 deletions

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "gallery-badmanners-xyz",
"version": "1.1.0",
"version": "1.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "gallery-badmanners-xyz",
"version": "1.1.0",
"version": "1.2.0",
"dependencies": {
"@astrojs/check": "^0.5.9",
"@astrojs/rss": "^4.0.5",

View file

@ -1,7 +1,7 @@
{
"name": "gallery-badmanners-xyz",
"type": "module",
"version": "1.1.0",
"version": "1.2.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",

View file

@ -1,58 +1,15 @@
---
import { type CollectionEntry } from "astro:content";
import { type Lang } from "../content/config";
import UserComponent from "./UserComponent.astro";
import { t } from "../i18n";
type Props = {
authors: CollectionEntry<"users"> | CollectionEntry<"users">[];
lang: Lang;
};
const { authors, lang } = Astro.props;
const authorsArray = [authors].flat();
const { lang } = Astro.props;
const authors = Astro.slots.has("default")
? (await Astro.slots.render("default")).replaceAll(/\<\/(a|span)\>\</g, "</$1><br><")
: "";
---
{
authorsArray.length > 0 ? (
<p class="font-light">
{lang === "eng" &&
(authorsArray.length > 2 ? (
<span>
by{" "}
{authorsArray.slice(0, authorsArray.length - 1).map((author) => (
<Fragment>
<UserComponent lang="eng" user={author} />,
</Fragment>
))}
and <UserComponent lang="eng" user={authorsArray[authorsArray.length - 1]} />
</span>
) : authorsArray.length > 1 ? (
<span>
by <UserComponent lang="eng" user={authorsArray[0]} /> and{" "}
<UserComponent lang="eng" user={authorsArray[1]} />
</span>
) : (
<span>
by <UserComponent lang="eng" user={authorsArray[0]} />
</span>
))}
{lang === "tok" &&
(authorsArray.length > 1 ? (
<span>
lipu ni li tan jan ni:{" "}
{authorsArray.slice(0, authorsArray.length - 1).map((author) => (
<Fragment>
<UserComponent lang="tok" user={author} />
{" en "}
</Fragment>
))}
<UserComponent lang="tok" user={authorsArray[authorsArray.length - 1]} />
</span>
) : (
<span>
lipu ni li tan <UserComponent lang="tok" user={authorsArray[0]} />
</span>
))}
</p>
) : null
}
{authors ? <p id="authors" set:html={t(lang, "story/authors", authors.split("<br>"))} /> : null}

View file

@ -1,67 +1,34 @@
---
import { type CollectionEntry } from "astro:content";
import { type Lang } from "../content/config";
import { t } from "../i18n";
import UserComponent from "./UserComponent.astro";
import CopyrightedCharactersItem from "./CopyrightedCharactersItem.astro";
type Props = {
copyrightedCharacters?: Record<string, CollectionEntry<"users">>;
copyrightedCharacters?: Array<[CollectionEntry<"users">, string[]]>;
lang: Lang;
};
const { copyrightedCharacters, lang } = Astro.props;
if (copyrightedCharacters && "" in copyrightedCharacters && Object.keys(copyrightedCharacters).length > 1) {
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
}
const charactersPerUser =
copyrightedCharacters &&
Object.keys(copyrightedCharacters).reduce(
(acc, character) => {
const key = copyrightedCharacters[character].id;
if (!(key in acc)) {
acc[key] = [];
}
acc[key].push(character);
return acc;
},
{} as Record<
CollectionEntry<"users">["id"],
(typeof copyrightedCharacters extends Record<infer K, any> ? K : never)[]
>,
);
---
{
charactersPerUser ? (
copyrightedCharacters ? (
<section id="copyrighted-characters">
{lang === "eng" ? (
<ul>
{Object.values(charactersPerUser).map((characterList) => (
<li>
{characterList[0] === "" ? (
<span>
All characters are &copy; <UserComponent lang={lang} user={copyrightedCharacters[""]} />
</span>
) : characterList.length > 2 ? (
<span>
{characterList.slice(0, characterList.length - 1).join(", ")}, and{" "}
{characterList[characterList.length - 1]} are &copy;{" "}
<UserComponent lang={lang} user={copyrightedCharacters[characterList[0]]} />
</span>
) : characterList.length > 1 ? (
<span>
{characterList[0]} and {characterList[1]} are &copy;{" "}
<UserComponent lang={lang} user={copyrightedCharacters[characterList[0]]} />
</span>
) : (
<span>
{characterList[0]} is &copy;{" "}
<UserComponent lang={lang} user={copyrightedCharacters[characterList[0]]} />
</span>
)}
</li>
))}
</ul>
) : null}
<ul>
{copyrightedCharacters.map(([owner, characterList]) => (
<CopyrightedCharactersItem
stringFunction={
characterList[0] === ""
? (user) => t(lang, "characters/all_characters_are_copyrighted_by", user)
: (user) => t(lang, "characters/characters_are_copyrighted_by", user, characterList)
}
>
<UserComponent lang={lang} user={owner} />
</CopyrightedCharactersItem>
))}
</ul>
</section>
) : null
}

View file

@ -0,0 +1,10 @@
---
type Props = {
stringFunction: (_: string) => string;
};
const { stringFunction } = Astro.props;
const owner = Astro.slots.has("default") ? await Astro.slots.render("default") : "";
---
{owner ? <li set:html={stringFunction(owner)} /> : null}

View file

@ -1,7 +1,7 @@
---
import { type CollectionEntry } from "astro:content";
import { type CollectionEntry, getEntry } from "astro:content";
import { t } from "../i18n";
import { type Lang } from "../content/config";
import { getEntry } from "astro:content";
type Props = {
lang: Lang;
@ -12,7 +12,7 @@ let { user, lang } = Astro.props;
if (user.data.isAnonymous) {
user = await getEntry("users", "anonymous");
}
const username = user.data.nameLang[lang] || user.data.name;
const username = t(lang, user.data.nameLang as any) || user.data.name;
let link: string | null = null;
if (user.data.preferredLink) {
if (user.data.preferredLink in user.data.links) {

120
src/i18n/index.ts Normal file
View file

@ -0,0 +1,120 @@
import { type Lang } from "../content/config";
export const DEFAULT_LANG = "eng" satisfies Lang;
export type TranslationRecord = { [DEFAULT_LANG]: string | ((...args: any[]) => string) } & {
[L in Exclude<Lang, typeof DEFAULT_LANG>]?: string | ((...args: any[]) => string);
};
export const UI_STRINGS: Record<string, TranslationRecord> = {
"story/return_to_stories": {
eng: "Return to stories",
tok: "o tawa e lipu ale",
},
"story/return_to_series": {
eng: (seriesName: string) => `Return to ${seriesName}`,
},
"story/go_to_description": {
eng: "Go to description",
tok: "o tawa e toki lipu",
},
"story/toggle_dark_mode": {
eng: "Toggle dark mode",
tok: "o ante e kule lipu",
},
"story/word_count": {
eng: (wordCount: string | number) => `Word count: ${wordCount}.`,
tok: "",
},
"story/publish_date": {
eng: (date: string) => date,
tok: (date: string) => `tenpo suno ${date}`,
},
"story/description": {
eng: "Description",
tok: "toki lipu",
},
"story/summary": {
eng: "Summary",
tok: "lipu tawa tenpo lili",
},
"story/reveal_summary": {
eng: "Click to reveal",
tok: "Click to reveal summary in English",
},
"story/to_top": {
eng: "To top",
tok: "tawa sewi",
},
"story/tags": {
eng: "Tags",
tok: "nimi kulupu",
},
"story/copyright_year": {
eng: (year: string | number) => `© ${year}`,
tok: (year: string | number) => `© tenpo pi sike suno ${year}`,
},
"story/licenses": {
eng: "Licenses",
tok: "lipu lawa",
},
"story/authors": {
eng: (authorsList: string[]) => {
let authorsString = `by ${authorsList[0]}`;
if (authorsList.length > 2) {
authorsString += `, ${authorsList.slice(1, authorsList.length - 1).join(", ")}, and ${authorsList[authorsList.length - 1]}`;
} else if (authorsList.length == 2) {
authorsString += ` and ${authorsList[1]}`;
}
return authorsString;
},
tok: (authorsList: string[]) => {
let authorsString = "lipu ni li tan ";
if (authorsList.length > 1) {
authorsString += `jan ni: ${authorsList.join(" en ")}`;
} else {
authorsString += authorsList[0];
}
return authorsString;
},
},
"story/commissioned_by": {
eng: (arg: string) => `Commissioned by ${arg}`,
},
"story/requested_by": {
eng: (arg: string) => `Requested by ${arg}`,
},
"characters/characters_are_copyrighted_by": {
eng: (owner: string, charactersList: string[]) => {
if (charactersList.length == 1) {
return `${charactersList[0]} is © ${owner}`;
}
if (charactersList.length == 2) {
return `${charactersList[0]} and ${charactersList[1]} are © ${owner}`;
}
return `${charactersList.slice(0, -1).join(", ")}, and ${charactersList[charactersList.length - 1]} are © ${owner}`;
},
},
"characters/all_characters_are_copyrighted_by": {
eng: (owner: string) => `All characters are © ${owner}`,
},
};
export function t(lang: Lang, stringOrSource: string | TranslationRecord, ...args: any[]): string {
if (typeof stringOrSource === "object") {
const translation = stringOrSource[lang] || stringOrSource[DEFAULT_LANG];
if (typeof translation === "function") {
return translation(...args);
}
return translation;
}
if (UI_STRINGS[stringOrSource]) {
const translation = UI_STRINGS[stringOrSource][lang] || UI_STRINGS[stringOrSource][DEFAULT_LANG];
if (typeof translation === "function") {
return translation(...args);
}
return translation;
}
console.warn(`No translation map found for "${stringOrSource}"`);
return stringOrSource;
}

View file

@ -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
>&copy; {
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>

View file

@ -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
>&copy; {
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>

View file

@ -3,6 +3,7 @@ import { getCollection, getEntry, type CollectionEntry, getEntries } from "astro
import { marked, type RendererApi } from "marked";
import { decode as tinyDecode } from "tiny-decode";
import { type Lang, type Website } from "../../../content/config";
import { t } from "../../../i18n";
type DescriptionFormat = "bbcode" | "markdown";
@ -196,9 +197,9 @@ function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite):
function getNameForUser(user: CollectionEntry<"users">, anonymousUser: CollectionEntry<"users">, lang: Lang): string {
if (user.data.isAnonymous) {
return anonymousUser.data.nameLang[lang] || anonymousUser.data.name;
return t(lang, anonymousUser.data.nameLang as any) || anonymousUser.data.name;
}
return user.data.nameLang[lang] || user.data.name;
return t(lang, user.data.nameLang as any) || user.data.name;
}
type Props = {
@ -253,7 +254,7 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
[
story.data.description,
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" , "),
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" "),
story.data.requester && "Request for: " + u(await getEntry(story.data.requester)),
story.data.commissioner && "Commissioned by: " + u(await getEntry(story.data.commissioner)),
...(await Promise.all(
@ -301,35 +302,11 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
const requester = story.data.requester && (await getEntry(story.data.requester));
let storyHeader = `${story.data.title}\n`;
if (lang === "eng") {
let authorsString = `by ${authorsNames[0]}`;
if (authorsNames.length > 2) {
authorsString += `, ${authorsNames.slice(1, authorsNames.length - 1).join(", ")}, and ${authorsNames[authorsNames.length - 1]}`;
} else if (authorsNames.length == 2) {
authorsString += ` and ${authorsNames[1]}`;
}
storyHeader +=
`${authorsString}\n` +
(commissioner ? `Commissioned by ${getNameForUser(commissioner, anonymousUser, lang)}\n` : "") +
(requester ? `Requested by ${getNameForUser(requester, anonymousUser, lang)}\n` : "");
} else if (lang === "tok") {
let authorsString = "lipu ni li tan ";
if (authorsNames.length > 1) {
authorsString += `jan ni: ${authorsNames.join(" en ")}`;
} else {
authorsString += authorsNames[0];
}
if (commissioner) {
throw new Error(`No "commissioner" handler for language "tok"`);
}
if (requester) {
throw new Error(`No "requester" handler for language "tok"`);
}
storyHeader += `${authorsString}\n`;
} else {
throw new Error(`Unknown language "${lang}"`);
}
const storyHeader =
`${story.data.title}\n` +
`${t(lang, "story/authors", authorsNames)}\n` +
(commissioner ? `${t(lang, "story/commissioned_by", getNameForUser(commissioner, anonymousUser, lang))}\n` : "") +
(requester ? `${t(lang, "story/requested_by", getNameForUser(requester, anonymousUser, lang))}\n` : "");
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll("\\*", "*").replaceAll("\\=", "=")}`
.replaceAll(/\n\n\n+/g, "\n\n")

View file

@ -53,7 +53,7 @@ const categorizedTags: Array<[string, string, string[]]> = tagCategories
return tag;
}
if (!("eng" in tag)) {
throw new Error(`"{[lang]: text}" tag must have an "eng" key: ${tag}`);
throw new Error(`Object-formatted tag must have an "eng" key: ${tag}`);
}
return tag["eng"]!;
});