gallery.badmanners.xyz/src/i18n/index.ts
Bad Manners 7bb8a952ef Several minor improvements to typing and misc.
- Improved schema validation
- Move username parsing and other validators to schema types
- Fix astro check command
- Add JSON/YAML schema validation for data collections
- Update licenses
- Remove deployment script in favor of rsync
- Prevent unsanitized input in export-story script
- Change "eng" language to "en", per BCP47
- Clean up i18n keys and add aria attributes
- Improve MastodonComments behavior on no-JS browsers
2024-08-07 19:31:38 -03:00

249 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { GamePlatform, Lang } from "../content/config";
import { DEFAULT_LANG } from "../content/config";
export { DEFAULT_LANG } from "../content/config";
const UI_STRINGS = {
// Utility functions
"util/join_names": {
en: (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 "),
},
"util/capitalize": {
en: (text: string) => (text.length > 0 ? `${text[0].toUpperCase()}${text.slice(1)}` : ""),
},
"util/enumerate": {
en: (count: number, nounSingular: string, nounPlural?: string) => {
if (count == 0) {
return `no ${nounPlural ?? nounSingular}`;
}
if (count == 1) {
return `one ${nounSingular}`;
}
return `${count} ${nounPlural ?? nounSingular}`;
},
tok: (count: number, nounSingular: string, nounPlural?: string) =>
`${(count > 1 && nounPlural) || nounSingular} ${["ala", "wan", "tu"][count] || "mute"}`,
},
// export-story API functions
"export_story/authors": {
en: (authorsList: string[]) => `Writing: ${authorsList.join(" ")}`,
tok: (authorsList: string[]) => `lipu ni li tan jan ni: ${authorsList.join(" en ")}`,
},
"export_story/request_for": {
en: (requesterList: string[]) => `Request for: ${requesterList.join(" ")}`,
},
"export_story/commissioned_by": {
en: (commissionerList: string[]) => `Commissioned by: ${commissionerList.join(" ")}`,
},
// Shared strings for published content (stories + games)
"published_content/return_to_series": {
en: (seriesName: string) => `Return to ${seriesName}`,
},
"published_content/go_to_description": {
en: "Go to description",
tok: "o tawa e toki lipu",
},
"published_content/toggle_dark_mode": {
en: "Toggle dark mode",
tok: "o ante e kule lipu",
},
"published_content/cover_art_alt": {
en: (title: string) => `Cover art for ${title}`,
tok: (_title: string) => `sitelen tawa lipu ni`,
},
"published_content/publish_date": {
en: (date: Date) => date.toISOString().slice(0, 10),
tok: (date: Date) => `tenpo suno ${date.toISOString().slice(0, 10)}`,
},
"published_content/publish_date_aria_label": {
en: "Publish date",
tok: "tenpo pi pana lipu",
},
"published_content/publish_date_aria_description": {
en: (date: Date) =>
date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
}),
tok: (_date: Date) => "",
},
"published_content/description": {
en: "Description",
tok: "toki lipu",
},
"published_content/to_top": {
en: "To top",
tok: "tawa sewi",
},
"published_content/tags": {
en: "Tags",
tok: "nimi kulupu",
},
"published_content/copyright_year": {
en: (year: string | number) => `© ${year}`,
tok: (year: string | number) => `© tenpo pi sike suno ${year}`,
},
"published_content/licenses": {
en: "Licenses",
tok: "lipu lawa",
},
"published_content/draft_warning": {
en: "DRAFT VERSION DO NOT REDISTRIBUTE",
},
"published_content/related_stories": {
en: "Related stories",
},
"published_content/related_games": {
en: "Related games",
},
// Story page-specific strings
"story/return_to_stories": {
en: "Return to stories",
tok: "o tawa e lipu ale",
},
"story/title_aria_label": {
en: "Story title",
tok: "nimi lipu",
},
"story/authors_aria_label": {
en: (authors: any[]) => (authors.length == 1 ? "Author" : "Authors"),
tok: (_authors: any[]) => "jan pi pali lipu",
},
"story/requesters_aria_label": {
en: (requesters: any[]) => (requesters.length == 1 ? "Requester" : "Requesters"),
},
"story/commissioners_aria_label": {
en: (commissioners: any[]) => (commissioners.length == 1 ? "Commissioner" : "Commissioners"),
},
"story/warnings": {
en: (wordCount: number | string | undefined, contentWarning: string) =>
wordCount ? `Word count: ${wordCount}. ${contentWarning}` : contentWarning,
tok: (_wordCount: number | string | undefined, contentWarning: string) => contentWarning,
},
"story/article_aria_label": {
en: "Story",
tok: "lipu",
},
"story/summary": {
en: "Summary",
tok: "lipu tawa tenpo lili",
},
"story/reveal_summary": {
en: "Click to reveal",
tok: "Click to reveal summary in English",
},
"story/authors": {
en: (authorsList: string[]) => `by ${UI_STRINGS["util/join_names"].en(authorsList)}`,
tok: (authorsList: string[]) =>
authorsList.length > 1
? `lipu ni li tan jan ni: ${UI_STRINGS["util/join_names"].tok(authorsList)}`
: `lipu ni li tan ${authorsList[0]}`,
},
"story/commissioned_by": {
en: (commissionersList: string[]) => `Commissioned by ${UI_STRINGS["util/join_names"].en(commissionersList)}`,
},
"story/requested_by": {
en: (requestersList: string[]) => `Requested by ${UI_STRINGS["util/join_names"].en(requestersList)}`,
},
// Game page-specific strings
"game/return_to_games": {
en: "Return to games",
},
"game/title_aria_label": {
en: "Game title",
},
"game/platforms": {
en: (platforms: GamePlatform[]) => {
if (platforms.length == 0) {
return "";
}
const translatedPlatforms = platforms.map((platform) => {
const platformLang = UI_STRINGS[`game/platform_${platform}`].en;
if (!platformLang) {
throw new Error(`Invalid platform "${platform}"`);
}
return platformLang;
});
return `A game for ${UI_STRINGS["util/join_names"].en(translatedPlatforms)}.`;
},
},
"game/platform_web": {
en: "web browsers",
},
"game/platform_windows": {
en: "Windows",
},
"game/platform_linux": {
en: "Linux",
},
"game/platform_macos": {
en: "macOS",
},
"game/platform_android": {
en: "Android",
},
"game/platform_ios": {
en: "iOS",
},
"game/warnings": {
en: (platforms: GamePlatform[], contentWarning: string) =>
platforms.length > 0 ? `${UI_STRINGS["game/platforms"].en(platforms)} ${contentWarning}` : contentWarning,
},
"game/article_aria_label": {
en: "Game",
},
// Copyrighted character-related strings
"characters/copyrighted_characters_aria_label": {
en: "Copyrighted characters",
},
"characters/characters_are_copyrighted_by": {
en: (owner: string, charactersList: string[]) =>
charactersList.length == 1
? `${charactersList[0]} is © ${owner}`
: `${UI_STRINGS["util/join_names"].en(charactersList)} are © ${owner}`,
},
"characters/all_characters_are_copyrighted_by": {
en: (owner: string) => `All characters are © ${owner}`,
},
// Tag-related strings
"tag/total_works_with_tag": {
en: (tag: string, storiesCount: number, gamesCount: number) => {
const content = [];
if (storiesCount > 0) {
content.push(UI_STRINGS["util/enumerate"].en(storiesCount, "story", "stories"));
}
if (gamesCount > 0) {
content.push(UI_STRINGS["util/enumerate"].en(gamesCount, "game", "games"));
}
if (content.length == 0) {
return `No works tagged with "${tag}".`;
}
return UI_STRINGS["util/capitalize"].en(`${UI_STRINGS["util/join_names"].en(content)} tagged with "${tag}".`);
},
},
} as const;
type TranslationKey = keyof typeof UI_STRINGS;
type TranslationEntry<T> = { [DEFAULT_LANG]: T } & {
[L in Exclude<Lang, typeof DEFAULT_LANG>]?: T;
};
type TranslationArgs<K extends TranslationKey> =
(typeof UI_STRINGS)[K] extends TranslationEntry<infer T> ? (T extends (...args: infer A) => string ? A : []) : never;
/** Translates some text according to the provided language, a translation key,
* and optionally any required arguments.
*/
export function t<K extends TranslationKey>(lang: Lang, key: K, ...args: TranslationArgs<K>): string {
if (key in UI_STRINGS) {
const translation: string | ((...args: TranslationArgs<K>) => string) =
(UI_STRINGS[key] as any)[lang] || UI_STRINGS[key][DEFAULT_LANG];
return typeof translation === "function" ? translation(...args) : translation;
}
throw new Error(`No translation entry found for key "${key}"`);
}