Improve i18n support and config validation
This commit is contained in:
parent
4f83ae8802
commit
37db38b613
9 changed files with 152 additions and 152 deletions
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "gallery-badmanners-xyz",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gallery-badmanners-xyz",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.5.9",
|
||||
"@astrojs/rss": "^4.0.5",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "gallery-badmanners-xyz",
|
||||
"type": "module",
|
||||
"version": "1.2.0",
|
||||
"version": "1.2.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
|
|
|
@ -15,15 +15,11 @@ if (user.data.isAnonymous) {
|
|||
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) {
|
||||
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
||||
if (typeof preferredLink === "string") {
|
||||
link = preferredLink;
|
||||
} else {
|
||||
link = preferredLink[0];
|
||||
}
|
||||
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
||||
if (typeof preferredLink === "string") {
|
||||
link = preferredLink;
|
||||
} else {
|
||||
throw new Error(`No preferredLink "${user.data.preferredLink}" for user ${user.id}`);
|
||||
link = preferredLink[0];
|
||||
}
|
||||
}
|
||||
---
|
||||
|
|
|
@ -17,6 +17,8 @@ export const WEBSITE_LIST = [
|
|||
] as const;
|
||||
|
||||
const localUrlRegex = /^((\/?\.\.(?=\/))+|(\.(?=\/)))?\/?[a-z0-9_-]+(\/[a-z0-9_-]+)*\/?$/;
|
||||
const refineAuthors = (value: { id: any } | any[]) => "id" in value || value.length > 0;
|
||||
const refineCopyrightedCharacters = (value: Record<string, any>) => !("" in value) || Object.keys(value).length == 1;
|
||||
|
||||
const lang = z.enum(["eng", "tok"]).default("eng");
|
||||
const website = z.enum(WEBSITE_LIST);
|
||||
|
@ -43,7 +45,10 @@ const storiesCollection = defineCollection({
|
|||
// Optional
|
||||
isDraft: z.boolean().default(false),
|
||||
shortTitle: z.string().optional(),
|
||||
authors: z.union([reference("users"), z.array(reference("users"))]).default("bad-manners"),
|
||||
authors: z
|
||||
.union([reference("users"), z.array(reference("users"))])
|
||||
.default("bad-manners")
|
||||
.refine(refineAuthors, "authors cannot be empty"),
|
||||
descriptionPlaintext: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
thumbnail: image().optional(),
|
||||
|
@ -52,7 +57,13 @@ const storiesCollection = defineCollection({
|
|||
series: reference("series").optional(),
|
||||
commissioner: reference("users").optional(),
|
||||
requester: reference("users").optional(),
|
||||
copyrightedCharacters: z.record(z.string(), reference("users")).default({}),
|
||||
copyrightedCharacters: z
|
||||
.record(z.string(), reference("users"))
|
||||
.default({})
|
||||
.refine(
|
||||
refineCopyrightedCharacters,
|
||||
"copyrightedCharacters cannot have an empty catch-all key with other keys",
|
||||
),
|
||||
lang,
|
||||
prev: reference("stories").nullish(),
|
||||
next: reference("stories").nullish(),
|
||||
|
@ -74,13 +85,22 @@ const gamesCollection = defineCollection({
|
|||
tags: z.array(z.string()),
|
||||
// Optional
|
||||
isDraft: z.boolean().default(false),
|
||||
authors: z.union([reference("users"), z.array(reference("users"))]).default("bad-manners"),
|
||||
authors: z
|
||||
.union([reference("users"), z.array(reference("users"))])
|
||||
.default("bad-manners")
|
||||
.refine(refineAuthors, "authors cannot be empty"),
|
||||
descriptionPlaintext: z.string().optional(),
|
||||
thumbnail: image().optional(),
|
||||
thumbnailWidth: z.number().int().optional(),
|
||||
thumbnailHeight: z.number().int().optional(),
|
||||
series: reference("series").optional(),
|
||||
copyrightedCharacters: z.record(z.string(), reference("users")).default({}),
|
||||
copyrightedCharacters: z
|
||||
.record(z.string(), reference("users"))
|
||||
.default({})
|
||||
.refine(
|
||||
refineCopyrightedCharacters,
|
||||
"copyrightedCharacters cannot have an empty catch-all key with other keys",
|
||||
),
|
||||
lang,
|
||||
relatedStories: z.array(reference("stories")).default([]),
|
||||
relatedGames: z.array(reference("games")).default([]),
|
||||
|
@ -91,16 +111,24 @@ const gamesCollection = defineCollection({
|
|||
const usersCollection = defineCollection({
|
||||
type: "data",
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
// Required
|
||||
name: z.string(),
|
||||
links: z.record(website, z.union([z.string().url(), z.tuple([z.string().url(), z.string()])])),
|
||||
// Optional
|
||||
preferredLink: website.nullish(),
|
||||
nameLang: z.record(lang, z.string()).default({}),
|
||||
avatar: image().optional(),
|
||||
isAnonymous: z.boolean().default(false),
|
||||
}),
|
||||
z
|
||||
.object({
|
||||
// Required
|
||||
name: z.string(),
|
||||
links: z.record(website, z.union([z.string().url(), z.tuple([z.string().url(), z.string()])])),
|
||||
// Optional
|
||||
preferredLink: website.nullish(),
|
||||
nameLang: z.record(lang, z.string()).default({}),
|
||||
avatar: image().optional(),
|
||||
isAnonymous: z.boolean().default(false),
|
||||
})
|
||||
.refine(
|
||||
({ links, preferredLink }) => !preferredLink || preferredLink in links,
|
||||
({ preferredLink }) => ({
|
||||
message: `"${preferredLink}" not defined in links`,
|
||||
path: ["preferredLink"],
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const seriesCollection = defineCollection({
|
||||
|
@ -118,7 +146,12 @@ const tagCategoriesCollection = defineCollection({
|
|||
// Required
|
||||
name: z.string(),
|
||||
index: z.number().int(),
|
||||
tags: z.array(z.union([z.string(), z.record(lang, z.string())])),
|
||||
tags: z.array(
|
||||
z.union([
|
||||
z.string(),
|
||||
z.record(lang, z.string()).refine((tag) => "eng" in tag, 'Object-formatted tag must have an "eng" key'),
|
||||
]),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,29 @@ export type TranslationRecord = { [DEFAULT_LANG]: string | ((...args: any[]) =>
|
|||
};
|
||||
|
||||
export const UI_STRINGS: Record<string, TranslationRecord> = {
|
||||
"util/join_names": {
|
||||
eng: (names: string[]) =>
|
||||
names.length <= 1
|
||||
? names.join("")
|
||||
: names.length == 2
|
||||
? `${names[0]} and ${names[1]}`
|
||||
: `${names.slice(0, -1).join(", ")}, and ${names[names.length - 1]}`,
|
||||
tok: (names: string[]) => names.join(" en "),
|
||||
},
|
||||
"export_story/warnings": {
|
||||
eng: (wordCount: number | string, contentWarning: string) => `*Word count: ${wordCount}. ${contentWarning}*`,
|
||||
tok: (_wordCount: number | string, contentWarning: string) => `*${contentWarning}*`,
|
||||
},
|
||||
"export_story/writing": {
|
||||
eng: (authorsList: string[]) => `Writing: ${authorsList.join(" ")}`,
|
||||
tok: (authorsList: string[]) => `lipu ni li tan jan ni: ${authorsList.join(" en ")}`,
|
||||
},
|
||||
"export_story/request_for": {
|
||||
eng: (requester: string) => `Request for: ${requester}`,
|
||||
},
|
||||
"export_story/commissioned_by": {
|
||||
eng: (commissioner: string) => `Commissioned by: ${commissioner}`,
|
||||
},
|
||||
"story/return_to_stories": {
|
||||
eng: "Return to stories",
|
||||
tok: "o tawa e lipu ale",
|
||||
|
@ -59,24 +82,12 @@ export const UI_STRINGS: Record<string, TranslationRecord> = {
|
|||
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;
|
||||
},
|
||||
eng: (authorsList: string[]) =>
|
||||
`by ${(UI_STRINGS["util/join_names"]!.eng as (arg: string[]) => string)(authorsList)}`,
|
||||
tok: (authorsList: string[]) =>
|
||||
authorsList.length > 1
|
||||
? `lipu ni li tan jan ni: ${(UI_STRINGS["util/join_names"]!.tok as (arg: string[]) => string)(authorsList)}`
|
||||
: `lipu ni li tan ${authorsList[0]}`,
|
||||
},
|
||||
"story/commissioned_by": {
|
||||
eng: (arg: string) => `Commissioned by ${arg}`,
|
||||
|
@ -85,15 +96,10 @@ export const UI_STRINGS: Record<string, TranslationRecord> = {
|
|||
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}`;
|
||||
},
|
||||
eng: (owner: string, charactersList: string[]) =>
|
||||
charactersList.length == 1
|
||||
? `${charactersList[0]} is © ${owner}`
|
||||
: `${(UI_STRINGS["util/join_names"]!["eng"] as (arg: string[]) => string)(charactersList)} are © ${owner}`,
|
||||
},
|
||||
"characters/all_characters_are_copyrighted_by": {
|
||||
eng: (owner: string) => `All characters are © ${owner}`,
|
||||
|
|
|
@ -16,9 +16,6 @@ type Props = CollectionEntry<"games">["data"];
|
|||
const { props } = Astro;
|
||||
const series = props.series && (await getEntry(props.series));
|
||||
const authors = await getEntries([props.authors].flat());
|
||||
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(
|
||||
|
|
|
@ -26,9 +26,6 @@ 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));
|
||||
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(
|
||||
|
|
|
@ -5,7 +5,7 @@ import { decode as tinyDecode } from "tiny-decode";
|
|||
import { type Lang, type Website } from "../../../content/config";
|
||||
import { t } from "../../../i18n";
|
||||
|
||||
type DescriptionFormat = "bbcode" | "markdown";
|
||||
type ExportFormat = "bbcode" | "markdown";
|
||||
|
||||
const WEBSITE_LIST = [
|
||||
["eka", "bbcode"],
|
||||
|
@ -13,9 +13,9 @@ const WEBSITE_LIST = [
|
|||
["inkbunny", "bbcode"],
|
||||
["sofurry", "bbcode"],
|
||||
["weasyl", "markdown"],
|
||||
] as const satisfies [Website, DescriptionFormat][];
|
||||
] as const satisfies [Website, ExportFormat][];
|
||||
|
||||
type ExportWebsite = typeof WEBSITE_LIST extends ReadonlyArray<[infer K, DescriptionFormat]> ? K : never;
|
||||
type ExportWebsite = typeof WEBSITE_LIST extends ReadonlyArray<[infer K, ExportFormat]> ? K : never;
|
||||
|
||||
const bbcodeRenderer: RendererApi = {
|
||||
strong: (text) => `[b]${text}[/b]`,
|
||||
|
@ -108,7 +108,7 @@ function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website)
|
|||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled website "${website}" in getUsernameForWebsite`);
|
||||
throw new Error(`Unhandled Website "${website}"`);
|
||||
}
|
||||
} else {
|
||||
return link[1].replace(/^@/, "");
|
||||
|
@ -117,24 +117,14 @@ function getUsernameForWebsite(user: CollectionEntry<"users">, website: Website)
|
|||
throw new Error(`Cannot get "${website}" username for user "${user.id}"`);
|
||||
}
|
||||
|
||||
function isPreferredWebsite(
|
||||
user: CollectionEntry<"users">,
|
||||
website: Website,
|
||||
preferredChoices: readonly Website[],
|
||||
): boolean {
|
||||
const { preferredLink, links } = user.data;
|
||||
if (!(website in links)) {
|
||||
return false;
|
||||
}
|
||||
if (!preferredLink || preferredLink == website) {
|
||||
return true;
|
||||
}
|
||||
return !preferredChoices.includes(preferredLink);
|
||||
function isPreferredWebsite(user: CollectionEntry<"users">, website: Website): boolean {
|
||||
const { preferredLink } = user.data;
|
||||
return !preferredLink || preferredLink == website;
|
||||
}
|
||||
|
||||
function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite): string {
|
||||
function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite, anonymousFallback: string): string {
|
||||
if (user.data.isAnonymous) {
|
||||
return "anonymous";
|
||||
return anonymousFallback;
|
||||
}
|
||||
switch (website) {
|
||||
case "eka":
|
||||
|
@ -148,51 +138,46 @@ function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite):
|
|||
}
|
||||
break;
|
||||
case "weasyl":
|
||||
const weasylPreferredWebsites = ["furaffinity", "inkbunny", "sofurry"] as const;
|
||||
if ("weasyl" in user.data.links) {
|
||||
return `<!~${getUsernameForWebsite(user, "weasyl").replaceAll(" ", "")}>`;
|
||||
} else if (isPreferredWebsite(user, "furaffinity", weasylPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||
return `<fa:${getUsernameForWebsite(user, "furaffinity")}>`;
|
||||
} else if (isPreferredWebsite(user, "inkbunny", weasylPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "inkbunny")) {
|
||||
return `<ib:${getUsernameForWebsite(user, "inkbunny")}>`;
|
||||
} else if (isPreferredWebsite(user, "sofurry", weasylPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "sofurry")) {
|
||||
return `<sf:${getUsernameForWebsite(user, "sofurry")}>`;
|
||||
}
|
||||
break;
|
||||
case "inkbunny":
|
||||
const inkbunnyPreferredWebsites = ["furaffinity", "sofurry", "weasyl"] as const;
|
||||
if ("inkbunny" in user.data.links) {
|
||||
return `[iconname]${getUsernameForWebsite(user, "inkbunny")}[/iconname]`;
|
||||
} else if (isPreferredWebsite(user, "furaffinity", inkbunnyPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||
return `[fa]${getUsernameForWebsite(user, "furaffinity")}[/fa]`;
|
||||
} else if (isPreferredWebsite(user, "sofurry", inkbunnyPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "sofurry")) {
|
||||
return `[sf]${getUsernameForWebsite(user, "sofurry")}[/sf]`;
|
||||
} else if (isPreferredWebsite(user, "weasyl", inkbunnyPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "weasyl")) {
|
||||
return `[weasyl]${getUsernameForWebsite(user, "weasyl")}[/weasyl]`;
|
||||
}
|
||||
break;
|
||||
case "sofurry":
|
||||
const sofurryPreferredWebsites = ["furaffinity", "inkbunny"] as const;
|
||||
if ("sofurry" in user.data.links) {
|
||||
return `:icon${getUsernameForWebsite(user, "sofurry")}:`;
|
||||
} else if (isPreferredWebsite(user, "furaffinity", sofurryPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "furaffinity")) {
|
||||
return `fa!${getUsernameForWebsite(user, "furaffinity")}`;
|
||||
} else if (isPreferredWebsite(user, "inkbunny", sofurryPreferredWebsites)) {
|
||||
} else if (isPreferredWebsite(user, "inkbunny")) {
|
||||
return `ib!${getUsernameForWebsite(user, "inkbunny")}`;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled website "${website}" in getLinkForUser`);
|
||||
throw new Error(`Unhandled ExportWebsite "${website}"`);
|
||||
}
|
||||
if (user.data.preferredLink) {
|
||||
if (user.data.preferredLink in user.data.links) {
|
||||
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
||||
return `[${user.data.name}](${typeof preferredLink === "string" ? preferredLink : preferredLink[0]})`;
|
||||
} else {
|
||||
throw new Error(`No preferredLink "${user.data.preferredLink}" for user ${user.id}`);
|
||||
}
|
||||
const preferredLink = user.data.links[user.data.preferredLink] as string | [string, string];
|
||||
return `[${user.data.name}](${typeof preferredLink === "string" ? preferredLink : preferredLink[0]})`;
|
||||
}
|
||||
throw new Error(`No "${website}" link for user "${user.id}" (consider setting preferredLink)`);
|
||||
throw new Error(
|
||||
`No matching "${website}" link for user "${user.id}" (consider setting their "preferredLink" property)`,
|
||||
);
|
||||
}
|
||||
|
||||
function getNameForUser(user: CollectionEntry<"users">, anonymousUser: CollectionEntry<"users">, lang: Lang): string {
|
||||
|
@ -222,55 +207,47 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
|||
|
||||
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
|
||||
const { lang } = story.data;
|
||||
if (
|
||||
story.data.copyrightedCharacters &&
|
||||
"" in story.data.copyrightedCharacters &&
|
||||
Object.keys(story.data.copyrightedCharacters).length > 1
|
||||
) {
|
||||
throw new Error("copyrightedCharacter cannot use empty key (catch-all) with other keys");
|
||||
}
|
||||
const charactersPerUser =
|
||||
story.data.copyrightedCharacters &&
|
||||
Object.keys(story.data.copyrightedCharacters).reduce(
|
||||
(acc, character) => {
|
||||
const key = story.data.copyrightedCharacters[character].id;
|
||||
if (!(key in acc)) {
|
||||
acc[key] = [];
|
||||
}
|
||||
acc[key].push(character);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
CollectionEntry<"users">["id"],
|
||||
(typeof story.data.copyrightedCharacters extends Record<infer K, any> ? K : never)[]
|
||||
>,
|
||||
);
|
||||
const copyrightedCharacters = await Promise.all(
|
||||
Object.values(
|
||||
Object.keys(story.data.copyrightedCharacters).reduce(
|
||||
(acc, character) => {
|
||||
const user = story.data.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 authorsList = await getEntries([story.data.authors].flat());
|
||||
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
|
||||
const requester = story.data.requester && (await getEntry(story.data.requester));
|
||||
const anonymousUser = await getEntry("users", "anonymous");
|
||||
const anonymousFallback = getNameForUser(anonymousUser, anonymousUser, lang);
|
||||
|
||||
const description: Record<ExportWebsite, string> = Object.fromEntries(
|
||||
await Promise.all(
|
||||
WEBSITE_LIST.map(async ([website, exportFormat]) => {
|
||||
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website);
|
||||
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website, anonymousFallback);
|
||||
const storyDescription = (
|
||||
[
|
||||
story.data.description,
|
||||
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
|
||||
"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(
|
||||
(Object.keys(charactersPerUser) as CollectionEntry<"users">["id"][]).map(async (id) => {
|
||||
const user = u(await getEntry("users", id));
|
||||
const characterList = charactersPerUser[id];
|
||||
if (characterList[0] == "") {
|
||||
return `All characters are © ${user}`;
|
||||
} else if (characterList.length > 2) {
|
||||
return `${characterList.slice(0, characterList.length - 1).join(", ")}, and ${characterList[characterList.length - 1]} are © ${user}`;
|
||||
} else if (characterList.length > 1) {
|
||||
return `${characterList[0]} and ${characterList[1]} are © ${user}`;
|
||||
}
|
||||
return `${characterList[0]} is © ${user}`;
|
||||
}),
|
||||
)),
|
||||
t(lang, "export_story/warnings", story.data.wordCount, story.data.contentWarning.trim()),
|
||||
t(
|
||||
lang,
|
||||
"export_story/writing",
|
||||
authorsList.map((author) => u(author)),
|
||||
),
|
||||
requester && t(lang, "export_story/request_for", u(requester)),
|
||||
commissioner && t(lang, "export_story/commissioned_by", u(commissioner)),
|
||||
...copyrightedCharacters.map(([user, characterList]) =>
|
||||
characterList[0] == ""
|
||||
? t(lang, "characters/all_characters_are_copyrighted_by", u(user))
|
||||
: t(lang, "characters/characters_are_copyrighted_by", u(user), characterList),
|
||||
),
|
||||
].filter((data) => data) as string[]
|
||||
)
|
||||
.join("\n\n")
|
||||
|
@ -290,25 +267,22 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) =
|
|||
if (exportFormat === "markdown") {
|
||||
return [website, storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()];
|
||||
}
|
||||
throw new Error(`Unknown exportFormat "${exportFormat}"`);
|
||||
throw new Error(`Unhandled ExportFormat "${exportFormat}"`);
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const anonymousUser = await getEntry("users", "anonymous");
|
||||
const authorsNames = (await getEntries([story.data.authors].flat())).map((author) =>
|
||||
getNameForUser(author, anonymousUser, lang),
|
||||
);
|
||||
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
|
||||
const requester = story.data.requester && (await getEntry(story.data.requester));
|
||||
|
||||
const storyHeader =
|
||||
`${story.data.title}\n` +
|
||||
`${t(lang, "story/authors", authorsNames)}\n` +
|
||||
`${t(
|
||||
lang,
|
||||
"story/authors",
|
||||
authorsList.map((author) => getNameForUser(author, anonymousUser, lang)),
|
||||
)}\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("\\=", "=")}`
|
||||
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll(/\\([=*])/g, "$1")}`
|
||||
.replaceAll(/\n\n\n+/g, "\n\n")
|
||||
.trim();
|
||||
|
||||
|
|
|
@ -52,9 +52,6 @@ const categorizedTags: Array<[string, string, string[]]> = tagCategories
|
|||
if (typeof tag === "string") {
|
||||
return tag;
|
||||
}
|
||||
if (!("eng" in tag)) {
|
||||
throw new Error(`Object-formatted tag must have an "eng" key: ${tag}`);
|
||||
}
|
||||
return tag["eng"]!;
|
||||
});
|
||||
tagList.forEach((tag, index) => {
|
||||
|
|
Loading…
Add table
Reference in a new issue