refactor: 🌐 Internationalize every string

This commit is contained in:
Jesse Wierzbinski 2024-12-07 22:17:22 +01:00
parent 8c3ddc2a28
commit 2ceee4827f
No known key found for this signature in database
41 changed files with 932 additions and 428 deletions

View file

@ -19,10 +19,14 @@ import "~/styles/index.css";
import { convert } from "html-to-text"; import { convert } from "html-to-text";
import ConfirmationModal from "./components/modals/confirm.vue"; import ConfirmationModal from "./components/modals/confirm.vue";
import { Toaster } from "./components/ui/sonner"; import { Toaster } from "./components/ui/sonner";
import { setLanguageTag } from "./paraglide/runtime";
import { type EnumSetting, SettingIds } from "./settings"; import { type EnumSetting, SettingIds } from "./settings";
// Sin // Sin
//import "~/styles/mcdonalds.css"; //import "~/styles/mcdonalds.css";
const lang = useLanguage();
setLanguageTag(lang.value);
const code = useRequestURL().searchParams.get("code"); const code = useRequestURL().searchParams.get("code");
const appData = useAppData(); const appData = useAppData();
const instance = useInstance(); const instance = useInstance();

View file

@ -23,7 +23,7 @@
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Mention someone</p> <p>{{ m.game_tough_seal_adore() }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
@ -34,7 +34,7 @@
</Toggle> </Toggle>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Enable Markdown</p> <p>{{ m.plane_born_koala_hope() }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Select v-model:model-value="state.visibility"> <Select v-model:model-value="state.visibility">
@ -62,7 +62,7 @@
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Insert emoji</p> <p>{{ m.blue_ornate_coyote_tickle() }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
@ -72,7 +72,7 @@
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Attach a file</p> <p>{{ m.top_patchy_earthworm_vent() }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
@ -82,12 +82,12 @@
</Toggle> </Toggle>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
<p>Mark as sensitive</p> <p>{{ m.frail_broad_mallard_dart() }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Button type="submit" size="lg" class="ml-auto" :disabled="sending" @click="submit"> <Button type="submit" size="lg" class="ml-auto" :disabled="sending" @click="submit">
<Loader v-if="sending" class="!size-5 animate-spin" /> <Loader v-if="sending" class="!size-5 animate-spin" />
{{ relation?.type === "edit" ? "Save" : "Send" }} {{ relation?.type === "edit" ? m.gaudy_strong_puma_slide() : m.free_teal_bulldog_learn() }}
</Button> </Button>
</DialogFooter> </DialogFooter>
</template> </template>
@ -110,6 +110,7 @@ import { SelectTrigger } from "radix-vue";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import Note from "~/components/notes/note.vue"; import Note from "~/components/notes/note.vue";
import { Select, SelectContent, SelectItem } from "~/components/ui/select"; import { Select, SelectContent, SelectItem } from "~/components/ui/select";
import * as m from "~/paraglide/messages.js";
import { SettingIds } from "~/settings"; import { SettingIds } from "~/settings";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
@ -145,11 +146,11 @@ const getMentions = () => {
const peopleToMention = relation.note.mentions const peopleToMention = relation.note.mentions
.concat(relation.note.account) .concat(relation.note.account)
// Deduplicate mentions // Deduplicate mentions
.filter((m, i, a) => a.indexOf(m) === i) .filter((men, i, a) => a.indexOf(men) === i)
// Remove self // Remove self
.filter((m) => m.id !== identity.value?.account.id); .filter((men) => men.id !== identity.value?.account.id);
const mentions = peopleToMention.map((m) => `@${m.acct}`).join(" "); const mentions = peopleToMention.map((me) => `@${me.acct}`).join(" ");
return `${mentions} `; return `${mentions} `;
}; };
@ -165,10 +166,10 @@ const state = reactive({
? relation.note.visibility ? relation.note.visibility
: "public") as Status["visibility"], : "public") as Status["visibility"],
files: (relation?.type === "edit" files: (relation?.type === "edit"
? relation.note.media_attachments.map((m) => ({ ? relation.note.media_attachments.map((a) => ({
apiId: m.id, apiId: a.id,
file: new File([], m.url), file: new File([], a.url),
alt: m.description, alt: a.description,
uploading: false, uploading: false,
updating: false, updating: false,
})) }))
@ -276,23 +277,23 @@ const uploadFiles = (files: File[]) => {
const visibilities = { const visibilities = {
public: { public: {
icon: Globe, icon: Globe,
name: "Public", name: m.lost_trick_dog_grace(),
text: "Can be seen by anyone.", text: m.last_mean_peacock_zip(),
}, },
unlisted: { unlisted: {
icon: LockOpen, icon: LockOpen,
name: "Unlisted", name: m.funny_slow_jannes_walk(),
text: "Can be seen by anyone with the link.", text: m.grand_strong_gibbon_race(),
}, },
private: { private: {
icon: Lock, icon: Lock,
name: "Private", name: m.grassy_empty_raven_startle(),
text: "Can only be seen by your followers.", text: m.white_teal_ostrich_yell(),
}, },
direct: { direct: {
icon: AtSign, icon: AtSign,
name: "Direct", name: m.pretty_bold_baboon_wave(),
text: "Can only be seen by mentioned users.", text: m.lucky_mean_robin_link(),
}, },
}; };
</script> </script>

View file

@ -2,6 +2,7 @@
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import type { Status, StatusSource } from "@versia/client/types"; import type { Status, StatusSource } from "@versia/client/types";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import * as m from "~/paraglide/messages.js";
import Composer from "./composer.vue"; import Composer from "./composer.vue";
useListen("composer:open", () => { useListen("composer:open", () => {
@ -11,7 +12,7 @@ useListen("composer:open", () => {
}); });
useListen("composer:edit", async (note) => { useListen("composer:edit", async (note) => {
const id = toast.loading("Loading note data...", { const id = toast.loading(m.wise_late_fireant_walk(), {
duration: 0, duration: 0,
}); });
const { data: source } = await client.value.getStatusSource(note.id); const { data: source } = await client.value.getStatusSource(note.id);
@ -56,13 +57,16 @@ const relation = ref(
</script> </script>
<template> <template>
<Dialog v-model:open="open" @update:open="o => {if (!o) { relation = null}}"> <Dialog v-model:open="open" @update:open="o => { if (!o) { relation = null } }">
<DialogContent :hide-close="true" class="sm:max-w-xl max-w-full w-full grid-rows-[minmax(0,1fr)_auto] max-h-[90dvh] p-5 pt-6 top-0 sm:top-1/2 translate-y-0 sm:-translate-y-1/2"> <DialogContent :hide-close="true"
class="sm:max-w-xl max-w-full w-full grid-rows-[minmax(0,1fr)_auto] max-h-[90dvh] p-5 pt-6 top-0 sm:top-1/2 translate-y-0 sm:-translate-y-1/2">
<DialogTitle class="sr-only"> <DialogTitle class="sr-only">
{{ relation?.type === "reply" ? "Reply" : relation?.type === "quote" ? "Quote" : "Compose" }} {{ relation?.type === "reply" ? m.loved_busy_mantis_slide() : relation?.type === "quote" ? "Quote" :
m.chunky_dull_marlin_trip() }}
</DialogTitle> </DialogTitle>
<DialogDescription class="sr-only"> <DialogDescription class="sr-only">
{{ relation?.type === "reply" ? "Reply to this status" : relation?.type === "quote" ? "Quote this status" : "Compose a new status" }} {{ relation?.type === "reply" ? m.tired_grassy_vulture_forgive() : relation?.type === "quote" ?
m.livid_livid_nils_snip() : m.brief_cool_capybara_fear() }}
</DialogDescription> </DialogDescription>
<Composer :relation="relation ?? undefined" /> <Composer :relation="relation ?? undefined" />
</DialogContent> </DialogContent>

View file

@ -12,6 +12,7 @@ import {
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import * as m from "~/paraglide/messages.js";
import type { ConfirmModalOptions, ConfirmModalResult } from "./composable.ts"; import type { ConfirmModalOptions, ConfirmModalResult } from "./composable.ts";
defineProps<{ defineProps<{
@ -41,14 +42,14 @@ const inputValue = ref<string>("");
<div v-if="modalOptions.inputType === 'text'" class="grid gap-4 py-4"> <div v-if="modalOptions.inputType === 'text'" class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="confirmInput" class="text-right">Value</Label> <Label for="confirmInput" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label>
<Input id="confirmInput" v-model="inputValue" class="col-span-3" /> <Input id="confirmInput" v-model="inputValue" class="col-span-3" />
</div> </div>
</div> </div>
<div v-else-if="modalOptions.inputType === 'textarea'" class="grid gap-4 py-4"> <div v-else-if="modalOptions.inputType === 'textarea'" class="grid gap-4 py-4">
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="confirmTextarea" class="text-right">Value</Label> <Label for="confirmTextarea" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label>
<Textarea id="confirmTextarea" v-model="inputValue" class="col-span-3" /> <Textarea id="confirmTextarea" v-model="inputValue" class="col-span-3" />
</div> </div>
</div> </div>

View file

@ -12,6 +12,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import * as m from "~/paraglide/messages.js";
import { import {
type ConfirmModalOptions, type ConfirmModalOptions,
type ConfirmModalResult, type ConfirmModalResult,
@ -20,11 +21,11 @@ import {
const isOpen = ref(false); const isOpen = ref(false);
const modalOptions = ref<ConfirmModalOptions>({ const modalOptions = ref<ConfirmModalOptions>({
title: "Confirm", title: m.antsy_whole_alligator_blink(),
message: "", message: "",
inputType: "none", inputType: "none",
confirmText: "Confirm", confirmText: m.antsy_whole_alligator_blink(),
cancelText: "Cancel", cancelText: m.soft_bold_ant_attend(),
}); });
const inputValue = ref(""); const inputValue = ref("");
const resolvePromise = ref<((result: ConfirmModalResult) => void) | null>(null); const resolvePromise = ref<((result: ConfirmModalResult) => void) | null>(null);
@ -32,11 +33,11 @@ const resolvePromise = ref<((result: ConfirmModalResult) => void) | null>(null);
function open(options: ConfirmModalOptions): Promise<ConfirmModalResult> { function open(options: ConfirmModalOptions): Promise<ConfirmModalResult> {
isOpen.value = true; isOpen.value = true;
modalOptions.value = { modalOptions.value = {
title: options.title || "Confirm", title: options.title || m.antsy_whole_alligator_blink(),
message: options.message, message: options.message,
inputType: options.inputType || "none", inputType: options.inputType || "none",
confirmText: options.confirmText || "Confirm", confirmText: options.confirmText || m.antsy_whole_alligator_blink(),
cancelText: options.cancelText || "Cancel", cancelText: options.cancelText || m.soft_bold_ant_attend(),
}; };
inputValue.value = options.defaultValue || ""; inputValue.value = options.defaultValue || "";

View file

@ -1,22 +1,22 @@
<template> <template>
<div class="flex flex-row w-full items-stretch justify-around text-sm *:max-w-28 *:w-full *:text-muted-foreground"> <div class="flex flex-row w-full items-stretch justify-around text-sm *:max-w-28 *:w-full *:text-muted-foreground">
<Button variant="ghost" @click="emit('reply')" title="Reply" :disabled="!identity"> <Button variant="ghost" @click="emit('reply')" :title="m.drab_tense_turtle_comfort()" :disabled="!identity">
<Reply class="size-5 text-primary" /> <Reply class="size-5 text-primary" />
{{ numberFormat(replyCount) }} {{ numberFormat(replyCount) }}
</Button> </Button>
<Button variant="ghost" @click="liked ? unlike() : like()" :title="liked ? 'Unlike' : 'Like'" :disabled="!identity"> <Button variant="ghost" @click="liked ? unlike() : like()" :title="liked ? m.vexed_fluffy_clownfish_dance() : m.royal_close_samuel_scold()" :disabled="!identity">
<Heart class="size-5 text-primary" /> <Heart class="size-5 text-primary" />
{{ numberFormat(likeCount) }} {{ numberFormat(likeCount) }}
</Button> </Button>
<Button variant="ghost" @click="reblogged ? unreblog() : reblog()" :title="reblogged ? 'Unreblog' : 'Reblog'" :disabled="!identity"> <Button variant="ghost" @click="reblogged ? unreblog() : reblog()" :title="reblogged ? m.lime_neat_ox_stab() : m.aware_helpful_marlin_drop()" :disabled="!identity">
<Repeat class="size-5 text-primary" /> <Repeat class="size-5 text-primary" />
{{ numberFormat(reblogCount) }} {{ numberFormat(reblogCount) }}
</Button> </Button>
<Button variant="ghost" @click="emit('quote')" title="Quote" :disabled="!identity"> <Button variant="ghost" @click="emit('quote')" :title="m.true_shy_jackal_drip()" :disabled="!identity">
<Quote class="size-5 text-primary" /> <Quote class="size-5 text-primary" />
</Button> </Button>
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId" @edit="emit('edit')" :note-id="noteId" @delete="emit('delete')"> <Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId" @edit="emit('edit')" :note-id="noteId" @delete="emit('delete')">
<Button variant="ghost" title="Actions"> <Button variant="ghost" :title="m.busy_merry_cowfish_absorb()">
<Ellipsis class="size-5 text-primary" /> <Ellipsis class="size-5 text-primary" />
</Button> </Button>
</Menu> </Menu>
@ -27,6 +27,8 @@
import { Ellipsis, Heart, Quote, Repeat, Reply } from "lucide-vue-next"; import { Ellipsis, Heart, Quote, Repeat, Reply } from "lucide-vue-next";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
import { languageTag } from "~/paraglide/runtime";
import { SettingIds } from "~/settings"; import { SettingIds } from "~/settings";
import { confirmModalService } from "../modals/composable"; import { confirmModalService } from "../modals/composable";
import Menu from "./menu.vue"; import Menu from "./menu.vue";
@ -58,9 +60,9 @@ const confirmReblogs = useSetting(SettingIds.ConfirmReblog);
const like = async () => { const like = async () => {
if (confirmLikes.value.value) { if (confirmLikes.value.value) {
const confirmation = await confirmModalService.confirm({ const confirmation = await confirmModalService.confirm({
title: "Like status", title: m.slimy_least_ray_aid(),
message: "Are you sure you want to like this status?", message: m.stale_new_ray_jolt(),
confirmText: "Like", confirmText: m.royal_close_samuel_scold(),
inputType: "none", inputType: "none",
}); });
@ -69,19 +71,19 @@ const like = async () => {
} }
} }
const id = toast.loading("Liking status..."); const id = toast.loading(m.slimy_candid_tiger_read());
const { data } = await client.value.favouriteStatus(noteId); const { data } = await client.value.favouriteStatus(noteId);
toast.dismiss(id); toast.dismiss(id);
toast.success("Status liked"); toast.success(m.mealy_slow_buzzard_commend());
useEvent("note:edit", data); useEvent("note:edit", data);
}; };
const unlike = async () => { const unlike = async () => {
if (confirmLikes.value.value) { if (confirmLikes.value.value) {
const confirmation = await confirmModalService.confirm({ const confirmation = await confirmModalService.confirm({
title: "Unlike status", title: m.odd_strong_halibut_prosper(),
message: "Are you sure you want to unlike this status?", message: m.slow_blue_parrot_savor(),
confirmText: "Unlike", confirmText: m.vexed_fluffy_clownfish_dance(),
inputType: "none", inputType: "none",
}); });
@ -90,19 +92,19 @@ const unlike = async () => {
} }
} }
const id = toast.loading("Unliking status..."); const id = toast.loading(m.busy_active_leopard_strive());
const { data } = await client.value.unfavouriteStatus(noteId); const { data } = await client.value.unfavouriteStatus(noteId);
toast.dismiss(id); toast.dismiss(id);
toast.success("Status unliked"); toast.success(m.fresh_direct_bear_affirm());
useEvent("note:edit", data); useEvent("note:edit", data);
}; };
const reblog = async () => { const reblog = async () => {
if (confirmReblogs.value.value) { if (confirmReblogs.value.value) {
const confirmation = await confirmModalService.confirm({ const confirmation = await confirmModalService.confirm({
title: "Reblog status", title: m.best_mellow_llama_surge(),
message: "Are you sure you want to reblog this status?", message: m.salty_plain_mallard_gaze(),
confirmText: "Reblog", confirmText: m.aware_helpful_marlin_drop(),
inputType: "none", inputType: "none",
}); });
@ -111,19 +113,19 @@ const reblog = async () => {
} }
} }
const id = toast.loading("Reblogging status..."); const id = toast.loading(m.late_sunny_cobra_scold());
const { data } = await client.value.reblogStatus(noteId); const { data } = await client.value.reblogStatus(noteId);
toast.dismiss(id); toast.dismiss(id);
toast.success("Status reblogged"); toast.success(m.weird_moving_hawk_lift());
useEvent("note:edit", data.reblog || data); useEvent("note:edit", data.reblog || data);
}; };
const unreblog = async () => { const unreblog = async () => {
if (confirmReblogs.value.value) { if (confirmReblogs.value.value) {
const confirmation = await confirmModalService.confirm({ const confirmation = await confirmModalService.confirm({
title: "Unreblog status", title: m.main_fancy_octopus_loop(),
message: "Are you sure you want to unreblog this status?", message: m.odd_alive_swan_express(),
confirmText: "Unreblog", confirmText: m.lime_neat_ox_stab(),
inputType: "none", inputType: "none",
}); });
@ -132,16 +134,16 @@ const unreblog = async () => {
} }
} }
const id = toast.loading("Unreblogging status..."); const id = toast.loading(m.white_sharp_gorilla_embrace());
const { data } = await client.value.unreblogStatus(noteId); const { data } = await client.value.unreblogStatus(noteId);
toast.dismiss(id); toast.dismiss(id);
toast.success("Status unreblogged"); toast.success(m.royal_polite_moose_catch());
useEvent("note:edit", data); useEvent("note:edit", data);
}; };
const numberFormat = (number = 0) => const numberFormat = (number = 0) =>
number !== 0 number !== 0
? new Intl.NumberFormat(undefined, { ? new Intl.NumberFormat(languageTag(), {
notation: "compact", notation: "compact",
compactDisplay: "short", compactDisplay: "short",
maximumFractionDigits: 1, maximumFractionDigits: 1,

View file

@ -1,14 +1,14 @@
<template> <template>
<Alert variant="warning" v-if="(sensitive || contentWarning) && showCw.value" <Alert variant="warning" v-if="(sensitive || contentWarning) && showCw.value"
class="mb-4 py-2 px-4 grid grid-cols-[auto,1fr,auto] gap-2 items-center [&>svg~*]:pl-0 [&>svg+div]:translate-y-0 [&>svg]:static"> class="mb-4 py-2 px-4 grid grid-cols-[auto,1fr,auto] gap-2 items-center [&>svg~*]:pl-0 [&>svg+div]:translate-y-0 [&>svg]:static">
<AlertTitle class="sr-only">Sensitive content</AlertTitle> <AlertTitle class="sr-only">{{ m.livid_tangy_lionfish_clasp() }}</AlertTitle>
<div> <div>
<TriangleAlert class="size-4" /> <TriangleAlert class="size-4" />
</div> </div>
<AlertDescription> <AlertDescription>
{{ contentWarning || "This content is sensitive" }} {{ contentWarning || m.sour_seemly_bird_hike() }}
</AlertDescription> </AlertDescription>
<Button @click="blurred = !blurred" variant="outline" size="sm">{{ blurred ? "Show" : "Hide" }}</Button> <Button @click="blurred = !blurred" variant="outline" size="sm">{{ blurred ? m.bald_direct_turtle_win() : m.known_flaky_cockroach_dash() }}</Button>
</Alert> </Alert>
<div ref="container" :class="cn('overflow-y-hidden relative duration-200', (blurred && showCw.value) && 'blur-md')" :style="{ <div ref="container" :class="cn('overflow-y-hidden relative duration-200', (blurred && showCw.value) && 'blur-md')" :style="{
@ -23,9 +23,10 @@
<Button v-if="isOverflowing" @click="collapsed = !collapsed" <Button v-if="isOverflowing" @click="collapsed = !collapsed"
class="absolute bottom-2 right-1/2 translate-x-1/2">{{ class="absolute bottom-2 right-1/2 translate-x-1/2">{{
collapsed collapsed
? `Show more${plainContent ? `${formattedCharacterCount} characters` : "" ? `${m.lazy_honest_mammoth_bump()}${plainContent ? `${m.dark_spare_goldfish_charm({
}` count: formattedCharacterCount ?? '0',
: "Show less" })}` : "" }`
: m.that_misty_mule_arrive()
}}</Button> }}</Button>
</div> </div>
@ -41,6 +42,7 @@ import { cn } from "@/lib/utils";
import type { Attachment, Emoji, Status } from "@versia/client/types"; import type { Attachment, Emoji, Status } from "@versia/client/types";
import { TriangleAlert } from "lucide-vue-next"; import { TriangleAlert } from "lucide-vue-next";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
import { languageTag } from "~/paraglide/runtime"; import { languageTag } from "~/paraglide/runtime";
import { type BooleanSetting, SettingIds } from "~/settings"; import { type BooleanSetting, SettingIds } from "~/settings";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert";

View file

@ -7,7 +7,7 @@
<span @click="copyText" <span @click="copyText"
class="select-none cursor-pointer space-x-1"> class="select-none cursor-pointer space-x-1">
<Clipboard class="size-4 -translate-y-0.5 inline" /> <Clipboard class="size-4 -translate-y-0.5 inline" />
Click to copy {{ m.clean_yummy_owl_reside() }}
</span> </span>
</span> </span>
</span> </span>
@ -18,6 +18,7 @@ import { cn } from "@/lib/utils";
import { Check, Clipboard } from "lucide-vue-next"; import { Check, Clipboard } from "lucide-vue-next";
import type { HTMLAttributes } from "vue"; import type { HTMLAttributes } from "vue";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import * as m from "~/paraglide/messages.js";
const { text } = defineProps<{ const { text } = defineProps<{
text: string; text: string;

View file

@ -21,6 +21,7 @@ import {
} from "lucide-vue-next"; } from "lucide-vue-next";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import { confirmModalService } from "~/components/modals/composable.ts"; import { confirmModalService } from "~/components/modals/composable.ts";
import * as m from "~/paraglide/messages.js";
import { SettingIds } from "~/settings"; import { SettingIds } from "~/settings";
const { authorId, noteId } = defineProps<{ const { authorId, noteId } = defineProps<{
@ -45,23 +46,23 @@ const confirmDeletes = useSetting(SettingIds.ConfirmDelete);
const copyText = (text: string) => { const copyText = (text: string) => {
copy(text); copy(text);
toast.success("Copied to clipboard"); toast.success(m.flat_nice_worm_dream());
}; };
const blockUser = async (userId: string) => { const blockUser = async (userId: string) => {
const id = toast.loading("Blocking user..."); const id = toast.loading(m.top_cute_bison_nudge());
await client.value.blockAccount(userId); await client.value.blockAccount(userId);
toast.dismiss(id); toast.dismiss(id);
toast.success("User blocked"); toast.success(m.main_weary_racoon_peek());
}; };
const _delete = async () => { const _delete = async () => {
if (confirmDeletes.value.value) { if (confirmDeletes.value.value) {
const confirmation = await confirmModalService.confirm({ const confirmation = await confirmModalService.confirm({
title: "Delete status", title: m.calm_icy_weasel_twirl(),
message: "Are you sure you want to delete this status?", message: m.gray_fun_toucan_slide(),
confirmText: "Delete", confirmText: m.royal_best_tern_transform(),
inputType: "none", inputType: "none",
}); });
@ -70,11 +71,11 @@ const _delete = async () => {
} }
} }
const id = toast.loading("Deleting status..."); const id = toast.loading(m.new_funny_fox_boil());
await client.value.deleteStatus(noteId); await client.value.deleteStatus(noteId);
toast.dismiss(id); toast.dismiss(id);
toast.success("Status deleted"); toast.success(m.green_tasty_bumblebee_beam());
emit("delete"); emit("delete");
}; };
</script> </script>
@ -85,57 +86,57 @@ const _delete = async () => {
<slot /> <slot />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="min-w-56"> <DropdownMenuContent class="min-w-56">
<DropdownMenuLabel>Note Actions</DropdownMenuLabel> <DropdownMenuLabel>{{ m.many_misty_parakeet_fall() }}</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem v-if="authorIsMe" as="button" @click="emit('edit')"> <DropdownMenuItem v-if="authorIsMe" as="button" @click="emit('edit')">
<Pencil class="mr-2 size-4" /> <Pencil class="mr-2 size-4" />
<span>Edit</span> {{ m.front_lime_grizzly_persist() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(apiNoteString)"> <DropdownMenuItem as="button" @click="copyText(apiNoteString)">
<Code class="mr-2 size-4" /> <Code class="mr-2 size-4" />
<span>Copy API data</span> {{ m.yummy_moving_scallop_sail() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(noteId)"> <DropdownMenuItem as="button" @click="copyText(noteId)">
<Hash class="mr-2 size-4" /> <Hash class="mr-2 size-4" />
<span>Copy ID</span> {{ m.sunny_zany_jellyfish_pop() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem as="button" @click="copyText(url)"> <DropdownMenuItem as="button" @click="copyText(url)">
<Link class="mr-2 size-4" /> <Link class="mr-2 size-4" />
<span>Copy link</span> {{ m.ago_new_pelican_drip() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" v-if="isRemote" @click="copyText(remoteUrl)"> <DropdownMenuItem as="button" v-if="isRemote" @click="copyText(remoteUrl)">
<Link class="mr-2 size-4" /> <Link class="mr-2 size-4" />
<span>Copy link (origin)</span> {{ m.solid_witty_zebra_walk() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="remoteUrl"> <DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="remoteUrl">
<ExternalLink class="mr-2 size-4" /> <ExternalLink class="mr-2 size-4" />
<span>Open on remote</span> {{ m.active_trite_lark_inspire() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="authorIsMe" /> <DropdownMenuSeparator v-if="authorIsMe" />
<DropdownMenuGroup v-if="authorIsMe"> <DropdownMenuGroup v-if="authorIsMe">
<DropdownMenuItem as="button" :disabled="true"> <DropdownMenuItem as="button" :disabled="true">
<Delete class="mr-2 size-4" /> <Delete class="mr-2 size-4" />
<span>Delete and redraft</span> {{ m.real_green_clownfish_pet() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="_delete"> <DropdownMenuItem as="button" @click="_delete">
<Trash class="mr-2 size-4" /> <Trash class="mr-2 size-4" />
<span>Delete</span> {{ m.tense_quick_cod_favor() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="loggedIn && !authorIsMe" /> <DropdownMenuSeparator v-if="loggedIn && !authorIsMe" />
<DropdownMenuGroup v-if="loggedIn && !authorIsMe"> <DropdownMenuGroup v-if="loggedIn && !authorIsMe">
<DropdownMenuItem as="button" :disabled="true"> <DropdownMenuItem as="button" :disabled="true">
<Flag class="mr-2 size-4" /> <Flag class="mr-2 size-4" />
<span>Report</span> {{ m.great_few_jaguar_rise() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="blockUser(authorId)"> <DropdownMenuItem as="button" @click="blockUser(authorId)">
<Ban class="mr-2 size-4" /> <Ban class="mr-2 size-4" />
<span>Block user</span> {{ m.misty_soft_sparrow_vent() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
</DropdownMenuContent> </DropdownMenuContent>

View file

@ -55,6 +55,7 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "~/components/ui/tooltip"; } from "~/components/ui/tooltip";
import * as m from "~/paraglide/messages.js";
import Note from "../notes/note.vue"; import Note from "../notes/note.vue";
import Avatar from "../profiles/avatar.vue"; import Avatar from "../profiles/avatar.vue";
import FollowRequest from "./follow-request.vue"; import FollowRequest from "./follow-request.vue";
@ -85,17 +86,17 @@ const icon = computed(() => {
const text = computed(() => { const text = computed(() => {
switch (notification.type) { switch (notification.type) {
case "mention": case "mention":
return "Mentioned you"; return m.fuzzy_orange_tuna_succeed();
case "reblog": case "reblog":
return "Reblogged your note"; return m.grand_proof_quail_read();
case "follow": case "follow":
return "Followed you"; return m.top_steep_scallop_care();
case "favourite": case "favourite":
return "Liked your note"; return m.swift_just_beetle_devour();
case "follow_request": case "follow_request":
return "Requested to follow you"; return m.seemly_short_thrush_bloom();
case "follow_accept": case "follow_accept":
return "Accepted your follow request"; return m.weird_seemly_termite_scold();
default: default:
return ""; return "";
} }

View file

@ -10,7 +10,7 @@
{{ emoji.shortcode }} {{ emoji.shortcode }}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
{{ emoji.global ? m.lime_day_squid_pout() : m.witty_heroic_trout_cry() }} {{ emoji.global ? m.real_tame_moose_greet() : m.witty_heroic_trout_cry() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="p-0" v-if="canEdit"> <CardFooter class="p-0" v-if="canEdit">

View file

@ -2,10 +2,10 @@
<Card class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"> <Card class="grid grid-cols-[1fr,auto] items-center p-6 gap-2">
<CardHeader class="space-y-0.5 p-0"> <CardHeader class="space-y-0.5 p-0">
<CardTitle class="text-base"> <CardTitle class="text-base">
{{ setting.title }} {{ setting.title() }}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
{{ setting.description }} {{ setting.description() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="p-0"> <CardFooter class="p-0">
@ -15,7 +15,7 @@
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem v-for="option of setting.options" :value="option.value"> <SelectItem v-for="option of setting.options" :value="option.value">
{{ option.label }} {{ option.label() }}
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>

View file

@ -2,10 +2,10 @@
<Card class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"> <Card class="grid grid-cols-[1fr,auto] items-center p-6 gap-2">
<CardHeader class="space-y-0.5 p-0"> <CardHeader class="space-y-0.5 p-0">
<CardTitle class="text-base"> <CardTitle class="text-base">
{{ setting.title }} {{ setting.title() }}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
{{ setting.description }} {{ setting.description() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="p-0"> <CardFooter class="p-0">

View file

@ -120,7 +120,6 @@ import {
SidebarRail, SidebarRail,
} from "~/components/ui/sidebar"; } from "~/components/ui/sidebar";
import * as m from "~/paraglide/messages.js"; import * as m from "~/paraglide/messages.js";
import { setLanguageTag } from "~/paraglide/runtime";
import { type EnumSetting, SettingIds } from "~/settings"; import { type EnumSetting, SettingIds } from "~/settings";
import Avatar from "../profiles/avatar.vue"; import Avatar from "../profiles/avatar.vue";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
@ -128,8 +127,6 @@ import AccountSwitcher from "./account-switcher.vue";
const sidebarStyle = useSetting(SettingIds.SidebarStyle) as Ref<EnumSetting>; const sidebarStyle = useSetting(SettingIds.SidebarStyle) as Ref<EnumSetting>;
setLanguageTag("fr");
const data = { const data = {
navMain: [ navMain: [
{ {

View file

@ -18,9 +18,9 @@
<!-- If there are some posts, but the user scrolled to the end --> <!-- If there are some posts, but the user scrolled to the end -->
<Card v-if="hasReachedEnd && items.length > 0" class="shadow-none bg-transparent border-none p-4"> <Card v-if="hasReachedEnd && items.length > 0" class="shadow-none bg-transparent border-none p-4">
<CardHeader class="text-center gap-y-4"> <CardHeader class="text-center gap-y-4">
<CardTitle class="text-">No more data.</CardTitle> <CardTitle>{{ m.steep_suave_fish_snap() }}</CardTitle>
<CardDescription> <CardDescription>
You've scrolled so far, there's nothing left to show. {{ m.muddy_bland_shark_accept() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
@ -28,16 +28,16 @@
<!-- If there are no posts at all --> <!-- If there are no posts at all -->
<Card v-else-if="hasReachedEnd && items.length === 0" class="shadow-none bg-transparent border-none p-4"> <Card v-else-if="hasReachedEnd && items.length === 0" class="shadow-none bg-transparent border-none p-4">
<CardHeader class="text-center gap-y-4"> <CardHeader class="text-center gap-y-4">
<CardTitle class="text-">There's nothing to show here.</CardTitle> <CardTitle>{{ m.fine_arable_lemming_fold() }}</CardTitle>
<CardDescription> <CardDescription>
Either you're all caught up or there's nothing to show. {{ m.petty_honest_fish_stir() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
<div v-else-if="!infiniteScroll.value" class="py-10 px-4"> <div v-else-if="!infiniteScroll.value" class="py-10 px-4">
<Button variant="secondary" @click="loadNext" :disabled="isLoading" class="w-full"> <Button variant="secondary" @click="loadNext" :disabled="isLoading" class="w-full">
Load More {{ m.gaudy_bland_gorilla_talk() }}
</Button> </Button>
</div> </div>
@ -55,6 +55,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import * as m from "~/paraglide/messages.js";
import { SettingIds } from "~/settings"; import { SettingIds } from "~/settings";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import TimelineItem from "./timeline-item.vue"; import TimelineItem from "./timeline-item.vue";

View file

@ -1,5 +1,7 @@
import type { Client } from "@versia/client"; import type { Client } from "@versia/client";
import type { RolePermission } from "@versia/client/types"; import type { RolePermission } from "@versia/client/types";
import { toast } from "vue-sonner";
import * as m from "~/paraglide/messages.js";
export const useCacheRefresh = (client: MaybeRef<Client | null>) => { export const useCacheRefresh = (client: MaybeRef<Client | null>) => {
// Refresh custom emojis and instance data and me on every reload // Refresh custom emojis and instance data and me on every reload
@ -21,11 +23,8 @@ export const useCacheRefresh = (client: MaybeRef<Client | null>) => {
if (code === 401) { if (code === 401) {
// Reset tokenData // Reset tokenData
identity.value = null; identity.value = null;
useEvent("notification:new", { toast.error(m.fancy_this_wasp_renew(), {
type: "error", description: m.real_weird_deer_stop(),
title: "Your session has expired",
description:
"You have been logged out. Please log in again.",
}); });
} }
}); });

7
composables/Language.ts Normal file
View file

@ -0,0 +1,7 @@
import { SettingIds } from "~/settings";
export const useLanguage = () => {
const lang = useSetting(SettingIds.Language);
return computed(() => lang.value.value as "en" | "fr");
};

View file

@ -7,7 +7,7 @@ import {
} from "~/settings"; } from "~/settings";
const useSettings = () => { const useSettings = () => {
return useLocalStorage<Settings>("versia:settings", settingsJson, { return useLocalStorage<Settings>("versia:settings", settingsJson(), {
serializer: { serializer: {
read(raw) { read(raw) {
const json = StorageSerializers.object.read(raw); const json = StorageSerializers.object.read(raw);

View file

@ -4,14 +4,14 @@
<slot v-if="!route.meta.requiresAuth || identity" /> <slot v-if="!route.meta.requiresAuth || identity" />
<Card v-else class="shadow-none bg-transparent border-none p-4 max-w-md mx-auto"> <Card v-else class="shadow-none bg-transparent border-none p-4 max-w-md mx-auto">
<CardHeader class="text-center gap-y-4"> <CardHeader class="text-center gap-y-4">
<CardTitle class="text-">Not signed in</CardTitle> <CardTitle>{{ m.sunny_quick_lionfish_flip() }}</CardTitle>
<CardDescription> <CardDescription>
This page requires you to be authenticated. Please sign in to continue. {{ m.brave_known_pelican_drip() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter> <CardFooter>
<Button variant="secondary" class="w-full" @click="signInAction"> <Button variant="secondary" class="w-full" @click="signInAction">
Sign in {{ m.fuzzy_sea_moth_absorb() }}
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
@ -31,6 +31,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import * as m from "~/paraglide/messages.js";
const appData = useAppData(); const appData = useAppData();
const signInAction = () => signIn(appData); const signInAction = () => signIn(appData);

View file

@ -56,10 +56,8 @@
"awake_quick_cuckoo_smile": "User followed", "awake_quick_cuckoo_smile": "User followed",
"funny_aloof_swan_loop": "Unfollow user", "funny_aloof_swan_loop": "Unfollow user",
"cute_polite_oryx_blend": "Unfollow", "cute_polite_oryx_blend": "Unfollow",
"dirty_inclusive_meerkat_nudge": "Cancel",
"big_safe_guppy_mix": "Unfollowing user...", "big_safe_guppy_mix": "Unfollowing user...",
"misty_level_stingray_expand": "User unfollowed", "misty_level_stingray_expand": "User unfollowed",
"lime_day_squid_pout": "Global",
"witty_heroic_trout_cry": "Uploaded by you", "witty_heroic_trout_cry": "Uploaded by you",
"cuddly_such_swallow_hush": "Rename", "cuddly_such_swallow_hush": "Rename",
"tense_quick_cod_favor": "Delete", "tense_quick_cod_favor": "Delete",
@ -100,7 +98,7 @@
"sunny_novel_otter_glow": "Must be at least 3 characters long", "sunny_novel_otter_glow": "Must be at least 3 characters long",
"fluffy_soft_wolf_cook": "Email (or username)", "fluffy_soft_wolf_cook": "Email (or username)",
"livid_bright_wallaby_quiz": "Password", "livid_bright_wallaby_quiz": "Password",
"fuzzy_sea_moth_absorb": "Sign In", "fuzzy_sea_moth_absorb": "Sign in",
"tidy_tidy_cow_cut": "Or continue with", "tidy_tidy_cow_cut": "Or continue with",
"slow_these_kestrel_sail": "Accept", "slow_these_kestrel_sail": "Accept",
"weary_steep_yak_embrace": "Reject", "weary_steep_yak_embrace": "Reject",
@ -108,5 +106,221 @@
"busy_awful_mouse_jump": "Follow request accepted.", "busy_awful_mouse_jump": "Follow request accepted.",
"front_sunny_penguin_flip": "Rejecting follow request...", "front_sunny_penguin_flip": "Rejecting follow request...",
"green_flat_mayfly_trust": "Follow request rejected.", "green_flat_mayfly_trust": "Follow request rejected.",
"large_vivid_horse_catch": "reblogged" "large_vivid_horse_catch": "reblogged",
"sour_major_baboon_wish": "Copied to clipboard",
"top_cute_bison_nudge": "Blocking user...",
"main_weary_racoon_peek": "User blocked",
"calm_icy_weasel_twirl": "Delete note",
"gray_fun_toucan_slide": "Are you sure you want to delete this note?",
"royal_best_tern_transform": "Delete",
"new_funny_fox_boil": "Deleting note...",
"green_tasty_bumblebee_beam": "Note deleted",
"many_misty_parakeet_fall": "Note Actions",
"front_lime_grizzly_persist": "Edit",
"wise_crazy_eel_honor": "Copy API data",
"dizzy_alive_wombat_bump": "Copy ID",
"slimy_livid_rabbit_belong": "Copy link",
"inner_pink_mare_renew": "Copy link (origin)",
"calm_fuzzy_thrush_soar": "Open on remote",
"real_green_clownfish_pet": " Delete and redraft",
"zippy_key_antelope_drop": "Delete",
"chunky_dry_gull_sing": "Report",
"bald_vexed_firefox_startle": "Block user",
"clean_yummy_owl_reside": "Click to copy",
"livid_tangy_lionfish_clasp": "Sensitive content",
"sour_seemly_bird_hike": "This content is sensitive",
"bald_direct_turtle_win": "Show",
"known_flaky_cockroach_dash": "Hide",
"dirty_sound_skunk_dust": "Show more${plainContent ? ` • ${formattedCharacterCount} characters",
"mad_direct_opossum_gleam": "`Show more${plainContent ? ` • ${formattedCharacterCount} characters` : \"\"\n }`",
"that_misty_mule_arrive": "Show less",
"lazy_honest_mammoth_bump": "Show more",
"dark_spare_goldfish_charm": "{count} characters",
"drab_tense_turtle_comfort": "Reply",
"vexed_fluffy_clownfish_dance": "Unlike",
"royal_close_samuel_scold": "Like",
"lime_neat_ox_stab": "Unreblog",
"aware_helpful_marlin_drop": "Reblog",
"true_shy_jackal_drip": "Quote",
"busy_merry_cowfish_absorb": "Actions",
"slimy_least_ray_aid": "Like note",
"stale_new_ray_jolt": "Are you sure you want to like this note?",
"slimy_candid_tiger_read": "Liking note...",
"mealy_slow_buzzard_commend": "Note liked",
"odd_strong_halibut_prosper": "Unlike note",
"slow_blue_parrot_savor": "Are you sure you want to unlike this note?",
"busy_active_leopard_strive": "Unliking note...",
"fresh_direct_bear_affirm": "Note unliked",
"best_mellow_llama_surge": "Reblog note",
"salty_plain_mallard_gaze": "Are you sure you want to reblog this note?",
"late_sunny_cobra_scold": "Reblogging note...",
"weird_moving_hawk_lift": "Note reblogged",
"main_fancy_octopus_loop": "Unreblog note",
"odd_alive_swan_express": "Are you sure you want to unreblog this note?",
"white_sharp_gorilla_embrace": "Unreblogging note...",
"royal_polite_moose_catch": "Note unreblogged",
"mean_mean_badger_inspire": "Value",
"antsy_whole_alligator_blink": "Confirm",
"game_tough_seal_adore": "Mention someone",
"plane_born_koala_hope": "Enable Markdown",
"blue_ornate_coyote_tickle": "Insert emoji",
"top_patchy_earthworm_vent": "Attach a file",
"frail_broad_mallard_dart": "Mark as sensitive",
"gaudy_strong_puma_slide": "Save",
"free_teal_bulldog_learn": "Send",
"last_mean_peacock_zip": "Can be seen by anyone.",
"funny_slow_jannes_walk": "Unlisted",
"grassy_empty_raven_startle": "Private",
"pretty_bold_baboon_wave": "Direct",
"grand_strong_gibbon_race": "Can be seen by anyone with the link.",
"white_teal_ostrich_yell": "Can only be seen by your followers.",
"lucky_mean_robin_link": "Can only be seen by mentioned users.",
"wise_late_fireant_walk": "Loading note data...",
"loved_busy_mantis_slide": "Reply",
"tired_grassy_vulture_forgive": "Reply to this note",
"solid_slow_angelfish_pave": "Compose",
"livid_livid_nils_snip": "Quote this note",
"brief_cool_capybara_fear": "Compose a new note",
"chunky_dull_marlin_trip": "Compose",
"steep_suave_fish_snap": "No more data.",
"muddy_bland_shark_accept": "You've scrolled so far, there's nothing left to show.",
"petty_honest_fish_stir": "Either you're all caught up or there's nothing to show.",
"fine_arable_lemming_fold": "There's nothing to show here.",
"gaudy_bland_gorilla_talk": "Load More",
"fancy_this_wasp_renew": "Your session has expired",
"real_weird_deer_stop": "You have been logged out. Please log in again.",
"sunny_quick_lionfish_flip": "Not signed in",
"brave_known_pelican_drip": "This page requires you to be authenticated. Please sign in to continue.",
"chunky_awake_mallard_grow": "Note",
"steep_sour_warthog_aim": "Loading",
"tough_nice_ox_drum": "Profile",
"noble_cute_ocelot_aim": "Register",
"novel_fine_stork_snap": "Log in to your account.",
"smug_main_whale_snip": "Enter your credentials for <code>{host}</code>.",
"aware_awful_crow_spur": "Here's your code",
"mushy_soft_lizard_propel": "You have signed in successfully.",
"short_arable_leopard_zap": "Paste the following code into your app:",
"spare_aqua_warthog_mend": "Authorization Code",
"fresh_broad_cockroach_radiate": "Authorize “{application}”?",
"gross_antsy_kangaroo_succeed": "You are signing in to <b>{application}</b> with your account.",
"hour_close_giraffe_mop": "This allows <b>{application}</b> to perform the above actions.",
"last_spare_polecat_reside": "Authorize",
"lower_factual_frog_evoke": "Authorization",
"awake_ago_capybara_kick": "$VERB your account information",
"teary_zesty_racoon_transform": "$VERB your block list",
"whole_flaky_nuthatch_rush": "$VERB your bookmarks",
"still_spicy_lionfish_quell": "$VERB your favourites",
"away_mean_dolphin_empower": "$VERB your filters",
"sleek_empty_penguin_radiate": "$VERB your follows",
"every_silly_racoon_lift": "$VERB your lists",
"top_careful_scallop_clip": "$VERB your mutes",
"this_short_bulldog_walk": "$VERB your notifications",
"fresh_odd_rook_forgive": "Perform searches",
"witty_whole_capybara_pull": "$VERB your notes",
"agent_warm_javelina_blink": "Edit your conversations",
"dirty_red_jellyfish_ascend": "Upload media",
"crisp_vivid_seahorse_tend": "Report users",
"teary_such_jay_fade": "Read and write",
"smug_safe_warthog_dare": "Read",
"loose_large_blackbird_peek": "Write",
"late_mean_capybara_fade": "Success",
"brave_acidic_lobster_fetch": "Your password has been reset. You can now log in with your new password.",
"every_tangy_koala_persist": "Back to front page",
"good_plane_gazelle_glow": "Your password has been reset by an administrator. Please change it here.",
"east_loud_lobster_explore": "Info",
"solid_slow_platypus_talk": "Enter your new password below. Make sure to put it in a password manager.",
"tired_green_sloth_evoke": "Reset your password",
"true_male_gadfly_stab": "New password",
"awful_cozy_jannes_rise": "Confirm password",
"noisy_round_skate_yell": "Reset",
"smart_bold_macaw_aid": "Must be at least {count} characters long",
"dry_smug_goldfish_promise": "Must be at most {count} characters long",
"candid_fancy_leopard_prosper": "Passwords do not match",
"arable_arable_herring_lead": "Reset Password",
"broad_whole_herring_reside": "Preferences",
"tasty_late_termite_sew": "Account",
"actual_mean_cow_dare": "Account Preferences",
"suave_smart_mantis_climb": "Emojis",
"lucky_suave_myna_adore": "Ask your administrator to add some emojis.",
"actual_steep_llama_rest": "No emojis found.",
"mild_many_dolphin_mend": "Emoji Preferences",
"lucky_ago_rat_pinch": "Uncategorized",
"empty_awful_lark_dart": "Account not found.",
"clean_even_mayfly_tap": "Check for typos or try again later.",
"vexed_each_falcon_enjoy": "Error",
"wide_topical_vole_walk": "Create an account",
"keen_clean_nils_slurp": "Username",
"top_inclusive_wallaby_hack": "Email address",
"mad_this_bumblebee_burn": "Password",
"happy_house_dragonfly_clap": "Passwords are never stored in plain text.",
"early_last_ocelot_praise": "Register",
"safe_candid_horse_jump": "Registrations are disabled on this instance.",
"wide_away_cat_taste": "Sorry :c",
"sea_maroon_peacock_yell": "Must be lowercase letters, numbers, underscores or hyphens",
"civil_loose_coyote_jump": "You must agree to the Terms of Service",
"plane_quick_chipmunk_rush": "I agree to the",
"glad_last_crow_dine": "Terms of Service",
"left_maroon_myna_drip": "You've successfully registered. You can now log in with your new account.",
"steep_aqua_fox_harbor": "Timelines",
"silly_sour_fireant_fear": "Failed to create app",
"level_due_ox_greet": "Signing in...",
"candid_frail_lion_value": "Failed to generate auth URL",
"wide_least_samuel_conquer": "Style of the left sidebar.",
"fluffy_north_crow_blink": "Inset",
"day_polite_newt_loop": "Sidebar",
"jolly_mad_jackdaw_assure": "Floating",
"agent_misty_firefox_arise": "Shape of all user avatars.",
"polite_awful_ladybug_greet": "Round",
"sad_each_cowfish_lock": "Square",
"fit_cool_bulldog_dine": "Avatar Shape",
"deft_seemly_donkey_slide": "Sidebar Style",
"quaint_clear_boar_attend": "Render MFM",
"aloof_helpful_larva_spur": "Render Misskey-Flavoured Markdown.",
"smart_awake_dachshund_view": "Custom CSS",
"loved_topical_rat_coax": "Custom CSS for the UI.",
"wise_neat_ox_buzz": "Dark",
"each_strong_snail_aid": "Light",
"helpful_raw_seal_nurture": "System",
"male_stout_florian_feast": "UI theme.",
"hour_elegant_mink_grip": "Theme",
"loud_raw_sheep_imagine": "Render Custom Emojis",
"inclusive_pink_tuna_enjoy": "Render custom emojis. Requires a page reload to apply.",
"fair_swift_elephant_hunt": "Blur Sensitive Content",
"gray_minor_bee_endure": "Blur notes marked sensitive/spoiler.",
"stock_large_marten_comfort": "Background URL",
"mean_weird_donkey_stab": "Change the background image of the site.",
"tired_jumpy_rook_slurp": "Notifications Sidebar",
"wide_new_robin_empower": "Display a sidebar with notifications on desktop.",
"less_early_lionfish_honor": "Fluent Emojis (flat version)",
"many_tasty_midge_zoom": "Fluent Emojis",
"shy_clear_spider_cook": "Noto Emoji",
"new_brave_maggot_relish": "Twitter Emojis",
"slimy_sound_termite_hug": "Operating System",
"warm_round_dove_skip": "Theme used for rendering emojis. Requires a page reload to apply.",
"weak_bad_martin_glow": "Emoji Theme",
"equal_blue_zebra_launch": "Ctrl+Enter to Send",
"heavy_pink_meerkat_affirm": "Send a note by pressing ⌘+Enter or Ctrl+Enter.",
"north_nimble_turkey_transform": "Popup Profile Hover",
"bold_moving_fly_savor": "Show profile popup when hovering over a user's avatar.",
"plane_dark_salmon_pout": "Automatically load more notes when reaching the bottom of the page.",
"helpful_early_worm_laugh": "Confirm before deleting a note.",
"trite_salty_eel_race": "Confirm Delete",
"sleek_this_earthworm_hug": "Infinite Scroll",
"jolly_empty_bullock_mend": "Confirm Follow",
"cool_tasty_mule_soar": "Confirm before following/unfollowing a user.",
"calm_male_wombat_relish": "Confirm before following/unfollowing a user.",
"wacky_inner_osprey_intend": "Confirm before reblogging a note.",
"honest_great_rooster_taste": "Confirm Reblog",
"patchy_basic_alligator_inspire": "Confirm Like",
"antsy_weak_raven_treat": "Confirm before liking a note.",
"fuzzy_orange_tuna_succeed": "Mentioned you",
"grand_proof_quail_read": "Reblogged your note",
"top_steep_scallop_care": "Followed you",
"swift_just_beetle_devour": "Liked your note",
"seemly_short_thrush_bloom": "Requested to follow you",
"weird_seemly_termite_scold": "Accepted your follow request",
"pretty_born_jackal_dial": "Language",
"tired_happy_lobster_pet": "Change the language of the interface. Requires a reload to apply.",
"keen_aware_goldfish_thrive": "English",
"vivid_mellow_sawfish_approve": "French"
} }

View file

@ -55,10 +55,8 @@
"awake_quick_cuckoo_smile": "Utilisateur suivi", "awake_quick_cuckoo_smile": "Utilisateur suivi",
"funny_aloof_swan_loop": "Se désabonner", "funny_aloof_swan_loop": "Se désabonner",
"cute_polite_oryx_blend": "Se désabonner", "cute_polite_oryx_blend": "Se désabonner",
"dirty_inclusive_meerkat_nudge": "Annuler",
"big_safe_guppy_mix": "Désabonnement...", "big_safe_guppy_mix": "Désabonnement...",
"misty_level_stingray_expand": "Utilisateur désabonné", "misty_level_stingray_expand": "Utilisateur désabonné",
"lime_day_squid_pout": "Global",
"witty_heroic_trout_cry": "Ajouté par vous", "witty_heroic_trout_cry": "Ajouté par vous",
"cuddly_such_swallow_hush": "Renommer", "cuddly_such_swallow_hush": "Renommer",
"tense_quick_cod_favor": "Supprimer", "tense_quick_cod_favor": "Supprimer",
@ -105,5 +103,201 @@
"busy_awful_mouse_jump": "Demande de suivi acceptée.", "busy_awful_mouse_jump": "Demande de suivi acceptée.",
"front_sunny_penguin_flip": "Rejet de la demande de suivi...", "front_sunny_penguin_flip": "Rejet de la demande de suivi...",
"green_flat_mayfly_trust": "Demande de suivi rejetée.", "green_flat_mayfly_trust": "Demande de suivi rejetée.",
"large_vivid_horse_catch": "a reblogué•e" "large_vivid_horse_catch": "a reblogué•e",
"top_cute_bison_nudge": "Blocage de l'utilisateur...",
"main_weary_racoon_peek": "Utilisateur bloqué",
"calm_icy_weasel_twirl": "Supprimer la note",
"gray_fun_toucan_slide": "Etes-vous sûr de vouloir supprimer cette note ?",
"royal_best_tern_transform": "Supprimer",
"new_funny_fox_boil": "Suppression de la note...",
"green_tasty_bumblebee_beam": "Note supprimée",
"many_misty_parakeet_fall": "Actions sur la note",
"front_lime_grizzly_persist": "Modifier",
"real_green_clownfish_pet": " Supprimer et reformuler",
"clean_yummy_owl_reside": "Cliquez pour copier",
"livid_tangy_lionfish_clasp": "Contenu sensible",
"sour_seemly_bird_hike": "Ce contenu est sensible",
"bald_direct_turtle_win": "Montrer",
"known_flaky_cockroach_dash": "Cacher",
"that_misty_mule_arrive": "Afficher moins",
"lazy_honest_mammoth_bump": "Afficher plus",
"dark_spare_goldfish_charm": "{count} caractères",
"drab_tense_turtle_comfort": "Répondre",
"vexed_fluffy_clownfish_dance": "Dé-aimer",
"royal_close_samuel_scold": "Aimer",
"lime_neat_ox_stab": "Dé-rebloguer",
"aware_helpful_marlin_drop": "Rebloguer",
"true_shy_jackal_drip": "Citer",
"busy_merry_cowfish_absorb": "Actions",
"slimy_least_ray_aid": "Aimer note",
"stale_new_ray_jolt": "Etes-vous sûr de vouloir aimer cette note ?",
"slimy_candid_tiger_read": "En cours...",
"mealy_slow_buzzard_commend": "Note aimé",
"odd_strong_halibut_prosper": "Dé-aimer note",
"slow_blue_parrot_savor": "Etes-vous sûr de vouloir dé-aimer cette note ?",
"busy_active_leopard_strive": "En cours...",
"fresh_direct_bear_affirm": "Note dé-aimée",
"best_mellow_llama_surge": "Rebloguer note",
"salty_plain_mallard_gaze": "Etes-vous sûr de vouloir rebloguer cette note ?",
"late_sunny_cobra_scold": "Rebloguage de la note...",
"weird_moving_hawk_lift": "Note rebloguée",
"main_fancy_octopus_loop": "Dé-rebloguer la note",
"odd_alive_swan_express": "Etes-vous sûr de vouloir dé-rebloguer cette note ?",
"white_sharp_gorilla_embrace": "Dé-rebloguage de la note...",
"royal_polite_moose_catch": "Note dé-rebloguée",
"mean_mean_badger_inspire": "Valeur",
"antsy_whole_alligator_blink": "Confirmer",
"game_tough_seal_adore": "Mentionner quelqu'un",
"plane_born_koala_hope": "Activer le Markdown",
"blue_ornate_coyote_tickle": "Insérer un emoji",
"top_patchy_earthworm_vent": "Joindre un fichier",
"frail_broad_mallard_dart": "Marquer comme sensible",
"gaudy_strong_puma_slide": "Sauvegarder",
"free_teal_bulldog_learn": "Envoyer",
"last_mean_peacock_zip": "Peut être vu par tout le monde.",
"funny_slow_jannes_walk": "Non répertorié",
"grassy_empty_raven_startle": "Privé",
"pretty_bold_baboon_wave": "Direct",
"grand_strong_gibbon_race": "Peut être vu par toute personne avec le lien.",
"white_teal_ostrich_yell": "Ne peut être vu que par vos abonnés.",
"lucky_mean_robin_link": "Ne peut être vu que par les utilisateurs mentionnés.",
"wise_late_fireant_walk": "Chargement des données de la note...",
"loved_busy_mantis_slide": "Répondre",
"tired_grassy_vulture_forgive": "Répondre à cette note",
"livid_livid_nils_snip": "Citer cette note",
"brief_cool_capybara_fear": "Composer une nouvelle note",
"chunky_dull_marlin_trip": "Composer",
"steep_suave_fish_snap": "Plus de données.",
"muddy_bland_shark_accept": "Vous avez tellement défilé•e, qu'il il n'y a plus rien à afficher.",
"petty_honest_fish_stir": "Soit vous avez tout rattrapé, soit il n'y a rien à montrer.",
"fine_arable_lemming_fold": "Il n'y a rien ici.",
"gaudy_bland_gorilla_talk": "Charger plus",
"fancy_this_wasp_renew": "Votre session a expirée",
"real_weird_deer_stop": "Vous avez été déconnecté•e. Veuillez vous reconnecter.",
"sunny_quick_lionfish_flip": "Non connecté•e",
"brave_known_pelican_drip": "Cette page nécessite une authentification. Veuillez vous connecter pour continuer.",
"chunky_awake_mallard_grow": "Note",
"steep_sour_warthog_aim": "Chargement",
"tough_nice_ox_drum": "Profil",
"noble_cute_ocelot_aim": "Créer un compte",
"novel_fine_stork_snap": "Connectez-vous à votre compte.",
"smug_main_whale_snip": "Saisissez vos informations d'identification pour <code>{host}</code> .",
"aware_awful_crow_spur": "Voici votre code",
"mushy_soft_lizard_propel": "Vous vous êtes connecté avec succès.",
"short_arable_leopard_zap": "Collez le code suivant dans votre application :",
"spare_aqua_warthog_mend": "Code d'autorisation",
"fresh_broad_cockroach_radiate": "Autoriser « {application} » ?",
"gross_antsy_kangaroo_succeed": "Vous vous connectez à <b>{application}</b> avec votre compte.",
"hour_close_giraffe_mop": "Cela permet à <b>{application}</b> d'effectuer les actions listées.",
"last_spare_polecat_reside": "Autoriser",
"lower_factual_frog_evoke": "Autorisation",
"awake_ago_capybara_kick": "$VERB vos informations de compte",
"teary_zesty_racoon_transform": "$VERB votre liste de blocage",
"whole_flaky_nuthatch_rush": "$VERB vos signets",
"still_spicy_lionfish_quell": "$VERB vos favoris",
"away_mean_dolphin_empower": "$VERB vos filtres",
"sleek_empty_penguin_radiate": "$VERB vos suivis",
"every_silly_racoon_lift": "$VERB vos listes",
"top_careful_scallop_clip": "$VERB votre liste de mutage",
"this_short_bulldog_walk": "$VERB vos notifications",
"fresh_odd_rook_forgive": "Effectuer des recherches",
"witty_whole_capybara_pull": "$VERB vos notes",
"agent_warm_javelina_blink": "Modifier vos conversations",
"dirty_red_jellyfish_ascend": "Ajouter des médias",
"crisp_vivid_seahorse_tend": "Signaler des utilisateurs",
"teary_such_jay_fade": "Lire et modifier",
"smug_safe_warthog_dare": "Lire",
"loose_large_blackbird_peek": "Modifier",
"late_mean_capybara_fade": "Succès",
"brave_acidic_lobster_fetch": "Votre mot de passe a été réinitialisé. Vous pouvez désormais vous connecter avec votre nouveau mot de passe.",
"every_tangy_koala_persist": "Retour à la page d'accueil",
"good_plane_gazelle_glow": "Votre mot de passe a été réinitialisé par un administrateur. Veuillez le modifier ici.",
"east_loud_lobster_explore": "Info",
"solid_slow_platypus_talk": "Saisissez votre nouveau mot de passe ci-dessous. Assurez-vous de l'enregistrer dans un gestionnaire de mots de passe.",
"tired_green_sloth_evoke": "Réinitialisez votre mot de passe",
"true_male_gadfly_stab": "Nouveau mot de passe",
"awful_cozy_jannes_rise": "Confirmez le mot de passe",
"noisy_round_skate_yell": "Réinitialiser",
"smart_bold_macaw_aid": "Doit comporter au moins {count} caractères",
"dry_smug_goldfish_promise": "Doit comporter au maximum {count} caractères",
"candid_fancy_leopard_prosper": "Les mots de passe ne correspondent pas",
"arable_arable_herring_lead": "Réinitialiser le mot de passe",
"broad_whole_herring_reside": "Préférences",
"tasty_late_termite_sew": "Compte",
"actual_mean_cow_dare": "Préférences du compte",
"suave_smart_mantis_climb": "Émojis",
"lucky_suave_myna_adore": "Demandez à votre administrateur dajouter des émojis.",
"actual_steep_llama_rest": "Aucun emoji trouvé.",
"mild_many_dolphin_mend": "Préférences d'emoji",
"lucky_ago_rat_pinch": "Non classé",
"empty_awful_lark_dart": "Compte non trouvé.",
"clean_even_mayfly_tap": "Changez d'utilisateur ou réessayez plus tard.",
"early_last_ocelot_praise": "Créer un compte",
"dirty_inclusive_meerkat_nudge": "Annuler",
"sea_maroon_peacock_yell": "Ne peut contenir que des lettres minuscules, des chiffres, des tirets ou des traits de soulignement",
"civil_loose_coyote_jump": "Vous devez accepter les conditions d'utilisation",
"plane_quick_chipmunk_rush": "J'accepte les",
"glad_last_crow_dine": "Conditions d'Utilisation",
"left_maroon_myna_drip": "Vous vous êtes inscrit. Vous pouvez désormais vous connecter avec votre nouveau compte.",
"steep_aqua_fox_harbor": "Fils",
"silly_sour_fireant_fear": "Échec de la création de l'application",
"level_due_ox_greet": "Connexion...",
"candid_frail_lion_value": "Échec de la génération de l'URL d'authentification",
"wide_least_samuel_conquer": "Style de la barre latérale gauche.",
"fluffy_north_crow_blink": "Encart",
"day_polite_newt_loop": "Barre latérale",
"jolly_mad_jackdaw_assure": "Flottant",
"agent_misty_firefox_arise": "Forme de tous les avatars utilisateurs.",
"polite_awful_ladybug_greet": "Rond",
"sad_each_cowfish_lock": "Carré",
"fit_cool_bulldog_dine": "Forme d'avatar",
"deft_seemly_donkey_slide": "Style de la barre latérale",
"quaint_clear_boar_attend": "Rendu MFM",
"aloof_helpful_larva_spur": "Afficher le Misskey-Flavoured Markdown",
"smart_awake_dachshund_view": "CSS personnalisé",
"loved_topical_rat_coax": "CSS personnalisé, appliqué à l'interface utilisateur.",
"wise_neat_ox_buzz": "Sombre",
"each_strong_snail_aid": "Clair",
"helpful_raw_seal_nurture": "Système",
"male_stout_florian_feast": "Thème de l'interface.",
"hour_elegant_mink_grip": "Thème",
"loud_raw_sheep_imagine": "Afficher les émojis personnalisés",
"inclusive_pink_tuna_enjoy": "Afficher les émojis personnalisés. Nécessite un rechargement de la page.",
"fair_swift_elephant_hunt": "Flouter les contenus sensibles",
"gray_minor_bee_endure": "Floute les notes marquées comme sensibles/spoiler.",
"stock_large_marten_comfort": "URL d'arrière-plan",
"mean_weird_donkey_stab": "Change l'image d'arrière-plan du site.",
"tired_jumpy_rook_slurp": "Barre de notifications",
"wide_new_robin_empower": "Affiche une barre latérale avec des notifications.",
"less_early_lionfish_honor": "Emojis Fluents (version plate)",
"many_tasty_midge_zoom": "Émojis Fluent",
"shy_clear_spider_cook": "Émojis Noto",
"new_brave_maggot_relish": "Émojis Twitter",
"slimy_sound_termite_hug": "Système",
"warm_round_dove_skip": "Thème utilisé pour le rendu des émojis. Nécessite un rechargement de la page.",
"weak_bad_martin_glow": "Thème Emoji",
"equal_blue_zebra_launch": "Ctrl+Entrée pour envoyer",
"heavy_pink_meerkat_affirm": "Envoyez une note en appuyant sur ⌘+Entrée ou Ctrl+Entrée.",
"north_nimble_turkey_transform": "Popup sur les profils",
"bold_moving_fly_savor": "Afficher un popup d'iinformations lorsque vous survolez l'avatar d'un utilisateur.",
"plane_dark_salmon_pout": "Charger automatiquement plus de notes lorsque vous atteignez le bas de la page.",
"helpful_early_worm_laugh": "Confirmer avant de supprimer une note.",
"trite_salty_eel_race": "Confirmer les suppressions",
"sleek_this_earthworm_hug": "Défilement infini",
"jolly_empty_bullock_mend": "Confirmer suivre",
"calm_male_wombat_relish": "Confirmez avant de suivre/ne plus suivre un utilisateur.",
"wacky_inner_osprey_intend": "Confirmer avant de rebloguer une note.",
"honest_great_rooster_taste": "Confirmer les reblogs",
"patchy_basic_alligator_inspire": "Confirmer J'aime",
"antsy_weak_raven_treat": "Confirmer avant d'aimer une note.",
"fuzzy_orange_tuna_succeed": "Vous a mentionné•e",
"grand_proof_quail_read": "A reblogué•e votre note",
"top_steep_scallop_care": "Vous a suivi•e",
"swift_just_beetle_devour": "A aimé•e votre note",
"seemly_short_thrush_bloom": "Fait une demande de suivi",
"weird_seemly_termite_scold": "A accepté•e votre demande de suivi",
"pretty_born_jackal_dial": "Langue",
"tired_happy_lobster_pet": "Changer la langue de l'interface. Nécessite un rechargement.",
"keen_aware_goldfish_thrive": "Anglais",
"vivid_mellow_sawfish_approve": "Français"
} }

View file

@ -11,15 +11,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useTitle } from "@vueuse/core";
import { Loader } from "lucide-vue-next"; import { Loader } from "lucide-vue-next";
import Note from "~/components/notes/note.vue"; import Note from "~/components/notes/note.vue";
import * as m from "~/paraglide/messages.js";
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Note", text: m.chunky_awake_mallard_grow(),
}, },
], ],
}); });
@ -57,7 +57,9 @@ watch(
useSeoMeta({ useSeoMeta({
title: computed(() => title: computed(() =>
note.value ? note.value.account.display_name : "Loading", note.value
? note.value.account.display_name
: m.steep_sour_warthog_aim(),
), ),
description: computed(() => (note.value ? note.value.content : undefined)), description: computed(() => (note.value ? note.value.content : undefined)),
ogImage: computed(() => ogImage: computed(() =>

View file

@ -9,9 +9,9 @@
</TimelineScroller> </TimelineScroller>
<Card v-else class="shadow-none bg-transparent border-none p-4"> <Card v-else class="shadow-none bg-transparent border-none p-4">
<CardHeader class="text-center gap-y-4"> <CardHeader class="text-center gap-y-4">
<CardTitle class="text-">Account not found.</CardTitle> <CardTitle>{{ m.empty_awful_lark_dart() }}</CardTitle>
<CardDescription> <CardDescription>
Check for typos or try again later. {{ m.clean_even_mayfly_tap() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
@ -23,6 +23,7 @@ import { Loader } from "lucide-vue-next";
import AccountProfile from "~/components/profiles/profile.vue"; import AccountProfile from "~/components/profiles/profile.vue";
import AccountTimeline from "~/components/timelines/account.vue"; import AccountTimeline from "~/components/timelines/account.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
const route = useRoute(); const route = useRoute();
const username = (route.params.username as string).startsWith("@") const username = (route.params.username as string).startsWith("@")
@ -33,7 +34,7 @@ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Profile", text: m.tough_nice_ox_drum(),
}, },
], ],
}); });
@ -43,10 +44,10 @@ const accountId = computed(() => account.value?.id ?? undefined);
useSeoMeta({ useSeoMeta({
title: computed(() => title: computed(() =>
account.value ? account.value.display_name : "Loading", account.value ? account.value.display_name : m.steep_sour_warthog_aim(),
), ),
ogTitle: computed(() => ogTitle: computed(() =>
account.value ? account.value.display_name : "Loading", account.value ? account.value.display_name : m.steep_sour_warthog_aim(),
), ),
ogImage: computed(() => (account.value ? account.value.avatar : undefined)), ogImage: computed(() => (account.value ? account.value.avatar : undefined)),
ogType: "profile", ogType: "profile",

View file

@ -9,19 +9,20 @@
<script setup lang="ts"> <script setup lang="ts">
import Global from "~/components/timelines/global.vue"; import Global from "~/components/timelines/global.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Global", title: m.real_tame_moose_greet(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Timelines", text: m.steep_aqua_fox_harbor(),
}, },
{ {
text: "Global", text: m.real_tame_moose_greet(),
href: "/global", href: "/global",
}, },
], ],

View file

@ -9,6 +9,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Home from "~/components/timelines/home.vue"; import Home from "~/components/timelines/home.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Home", title: "Home",
@ -18,10 +19,10 @@ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Timelines", text: m.steep_aqua_fox_harbor(),
}, },
{ {
text: "Home", text: m.bland_chunky_sparrow_propel(),
href: "/home", href: "/home",
}, },
], ],

View file

@ -12,24 +12,27 @@
import Home from "~/components/timelines/home.vue"; import Home from "~/components/timelines/home.vue";
import Public from "~/components/timelines/public.vue"; import Public from "~/components/timelines/public.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: identity.value ? "Home" : "Public", title: identity.value
? m.bland_chunky_sparrow_propel()
: m.lost_trick_dog_grace(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Timelines", text: m.steep_aqua_fox_harbor(),
}, },
identity.value identity.value
? { ? {
text: "Home", text: m.bland_chunky_sparrow_propel(),
href: "/home", href: "/home",
} }
: { : {
text: "Public", text: m.lost_trick_dog_grace(),
href: "/public", href: "/public",
}, },
], ],

View file

@ -10,19 +10,20 @@
<script lang="ts" setup> <script lang="ts" setup>
import Local from "~/components/timelines/local.vue"; import Local from "~/components/timelines/local.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Local", title: m.crazy_game_parrot_pave(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Timelines", text: m.steep_aqua_fox_harbor(),
}, },
{ {
text: "Local", text: m.crazy_game_parrot_pave(),
href: "/local", href: "/local",
}, },
], ],

View file

@ -22,16 +22,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import Notifications from "~/components/timelines/notifications.vue"; import Notifications from "~/components/timelines/notifications.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Notifications", title: m.that_patchy_mare_snip(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Notifications", text: m.that_patchy_mare_snip(),
href: "/notifications", href: "/notifications",
}, },
], ],

View file

@ -4,10 +4,11 @@ import { AlertCircle, Loader } from "lucide-vue-next";
import UserAuthForm from "~/components/oauth/login.vue"; import UserAuthForm from "~/components/oauth/login.vue";
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components"; import { NuxtLink } from "#components";
useHead({ useHead({
title: "Sign In", title: m.fuzzy_sea_moth_absorb(),
}); });
const host = new URL(useBaseUrl().value).host; const host = new URL(useBaseUrl().value).host;
@ -18,7 +19,7 @@ const { error, error_description } = useUrlSearchParams();
<template> <template>
<div class="container relative flex h-svh flex-col items-center justify-center md:flex-row lg:max-w-none lg:px-0"> <div class="container relative flex h-svh flex-col items-center justify-center md:flex-row lg:max-w-none lg:px-0">
<Button :as="NuxtLink" href="/register" variant="link" class="absolute right-4 top-4 md:right-8 md:top-8"> <Button :as="NuxtLink" href="/register" variant="link" class="absolute right-4 top-4 md:right-8 md:top-8">
Register {{ m.noble_cute_ocelot_aim() }}
</Button> </Button>
<div class="relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex grow bg-center bg-no-repeat bg-cover" <div class="relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex grow bg-center bg-no-repeat bg-cover"
:style="{ :style="{
@ -55,10 +56,11 @@ const { error, error_description } = useUrlSearchParams();
</Alert> </Alert>
<div class="flex flex-col space-y-2 text-center"> <div class="flex flex-col space-y-2 text-center">
<h1 class="text-2xl font-semibold tracking-tight"> <h1 class="text-2xl font-semibold tracking-tight">
Log in to your account. {{ m.novel_fine_stork_snap() }}
</h1> </h1>
<p class="text-sm text-muted-foreground"> <p class="text-sm text-muted-foreground" v-html="m.smug_main_whale_snip({
Enter your credentials for <code>{{ host }}</code>. host,
})">
</p> </p>
</div> </div>
<UserAuthForm v-if="instance" :instance="instance" /> <UserAuthForm v-if="instance" :instance="instance" />

View file

@ -4,8 +4,8 @@
}"> }">
<Card class="w-full max-w-md"> <Card class="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Here's your code</CardTitle> <CardTitle>{{ m.aware_awful_crow_spur() }}</CardTitle>
<CardDescription>You have signed in successfully.<br />Paste the following code into your app: <CardDescription>{{ m.mushy_soft_lizard_propel() }}<br />{{ m.short_arable_leopard_zap() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent class="grid"> <CardContent class="grid">
@ -23,9 +23,10 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Authorization Code", title: m.spare_aqua_warthog_mend(),
}); });
const { code } = useUrlSearchParams(); const { code } = useUrlSearchParams();

View file

@ -5,7 +5,9 @@
<Card class="w-full max-w-md" as="form" method="POST" :action="url.pathname.replace('/oauth/consent', '/oauth/authorize')"> <Card class="w-full max-w-md" as="form" method="POST" :action="url.pathname.replace('/oauth/consent', '/oauth/authorize')">
<input type="hidden" v-for="([key, value]) in url.searchParams" :key="key" :name="key" :value="value" /> <input type="hidden" v-for="([key, value]) in url.searchParams" :key="key" :name="key" :value="value" />
<CardHeader> <CardHeader>
<CardTitle as="h1" class="text-2xl break-words">Authorize &ldquo;{{ application }}&rdquo;?</CardTitle> <CardTitle as="h1" class="text-2xl break-words">{{ m.fresh_broad_cockroach_radiate({
application: application ?? "",
}) }}</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Card> <Card>
@ -23,16 +25,17 @@
</li> </li>
</ul> </ul>
<div class="flex-col flex gap-y-1 text-sm"> <div class="flex-col flex gap-y-1 text-sm">
<p>You are signing in to <b>{{ application }}</b> with your <p v-html="m.gross_antsy_kangaroo_succeed({
account.</p> application: application ?? '',
<p>This allows <b>{{ application }}</b> to perform the above })"></p>
account <p v-html="m.hour_close_giraffe_mop({
actions.</p> application: application ?? '',
})"></p>
</div> </div>
</CardContent> </CardContent>
<CardFooter class="grid gap-2"> <CardFooter class="grid gap-2">
<Button variant="default" type="submit">Authorize</Button> <Button variant="default" type="submit">{{ m.last_spare_polecat_reside() }}</Button>
<Button :as="NuxtLink" href="/" variant="secondary">Cancel</Button> <Button :as="NuxtLink" href="/" variant="secondary">{{ m.soft_bold_ant_attend() }}</Button>
</CardFooter> </CardFooter>
</Card> </Card>
</div> </div>
@ -42,10 +45,11 @@
import { Check } from "lucide-vue-next"; import { Check } from "lucide-vue-next";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components"; import { NuxtLink } from "#components";
useHead({ useHead({
title: "Authorization", title: m.lower_factual_frog_evoke(),
}); });
const url = useRequestURL(); const url = useRequestURL();
@ -62,20 +66,20 @@ const scope = params.scope ? decodeURIComponent(params.scope as string) : "";
const validUrlParameters = application && redirectUri && clientId && scope; const validUrlParameters = application && redirectUri && clientId && scope;
const oauthScopeText: Record<string, string> = { const oauthScopeText: Record<string, string> = {
"rw:accounts": "$VERB your account information", "rw:accounts": m.awake_ago_capybara_kick(),
"rw:blocks": "$VERB your block list", "rw:blocks": m.teary_zesty_racoon_transform(),
"rw:bookmarks": "$VERB your bookmarks", "rw:bookmarks": m.whole_flaky_nuthatch_rush(),
"rw:favourites": "$VERB your favourites", "rw:favourites": m.still_spicy_lionfish_quell(),
"rw:filters": "$VERB your filters", "rw:filters": m.away_mean_dolphin_empower(),
"rw:follows": "$VERB your follows", "rw:follows": m.sleek_empty_penguin_radiate(),
"rw:lists": "$VERB your lists", "rw:lists": m.every_silly_racoon_lift(),
"rw:mutes": "$VERB your mutes", "rw:mutes": m.top_careful_scallop_clip(),
"rw:notifications": "$VERB your notifications", "rw:notifications": m.this_short_bulldog_walk(),
"r:search": "Perform searches", "r:search": m.fresh_odd_rook_forgive(),
"rw:statuses": "$VERB your statuses", "rw:statuses": m.witty_whole_capybara_pull(),
"w:conversations": "Edit your conversations", "w:conversations": m.agent_warm_javelina_blink(),
"w:media": "Upload media", "w:media": m.dirty_red_jellyfish_ascend(),
"w:reports": "Report users", "w:reports": m.crisp_vivid_seahorse_tend(),
}; };
const scopes = scope.split(" "); const scopes = scope.split(" ");
@ -103,7 +107,7 @@ const getScopeText = (fullScopes: string[]) => {
) { ) {
if (oauthScopeText[possibleScope]?.includes("$VERB")) { if (oauthScopeText[possibleScope]?.includes("$VERB")) {
scopeTexts.push([ scopeTexts.push([
"Read and write", m.teary_such_jay_fade(),
oauthScopeText[possibleScope]?.replace("$VERB", "") ?? "", oauthScopeText[possibleScope]?.replace("$VERB", "") ?? "",
]); ]);
} else { } else {
@ -119,7 +123,7 @@ const getScopeText = (fullScopes: string[]) => {
) { ) {
if (oauthScopeText[possibleScope]?.includes("$VERB")) { if (oauthScopeText[possibleScope]?.includes("$VERB")) {
scopeTexts.push([ scopeTexts.push([
"Read", m.smug_safe_warthog_dare(),
oauthScopeText[possibleScope]?.replace("$VERB", "") ?? "", oauthScopeText[possibleScope]?.replace("$VERB", "") ?? "",
]); ]);
} else { } else {
@ -134,7 +138,7 @@ const getScopeText = (fullScopes: string[]) => {
) { ) {
if (oauthScopeText[possibleScope]?.includes("$VERB")) { if (oauthScopeText[possibleScope]?.includes("$VERB")) {
scopeTexts.push([ scopeTexts.push([
"Write", m.loose_large_blackbird_peek(),
oauthScopeText[possibleScope]?.replace("$VERB", "") ?? "", oauthScopeText[possibleScope]?.replace("$VERB", "") ?? "",
]); ]);
} else { } else {

View file

@ -4,14 +4,14 @@
}"> }">
<Card v-if="params.success" class="w-full max-w-md"> <Card v-if="params.success" class="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Success</CardTitle> <CardTitle>{{ m.late_mean_capybara_fade() }}</CardTitle>
<CardDescription> <CardDescription>
Your password has been reset. You can now log in with your new password. {{ m.brave_acidic_lobster_fetch() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="grid"> <CardFooter class="grid">
<Button :as="NuxtLink" href="/" variant="default"> <Button :as="NuxtLink" href="/" variant="default">
Back to front page {{ m.every_tangy_koala_persist() }}
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
@ -20,9 +20,9 @@
<CardHeader> <CardHeader>
<Alert v-if="params.login_reset" variant="default" class="mb-4"> <Alert v-if="params.login_reset" variant="default" class="mb-4">
<AlertCircle class="size-4" /> <AlertCircle class="size-4" />
<AlertTitle>Info</AlertTitle> <AlertTitle>{{ m.east_loud_lobster_explore() }}</AlertTitle>
<AlertDescription> <AlertDescription>
Your password has been reset by an administrator. Please change it here. {{ m.good_plane_gazelle_glow() }}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<Alert v-if="params.error" variant="destructive" class="mb-4"> <Alert v-if="params.error" variant="destructive" class="mb-4">
@ -32,9 +32,9 @@
{{ params.error_description }} {{ params.error_description }}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<CardTitle as="h1">Reset your password</CardTitle> <CardTitle as="h1">{{ m.tired_green_sloth_evoke() }}</CardTitle>
<CardDescription> <CardDescription>
Enter your new password below. Make sure to put it in a password manager. {{ m.solid_slow_platypus_talk() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent class="grid gap-6"> <CardContent class="grid gap-6">
@ -48,7 +48,7 @@
<FormField v-slot="{ componentField }" name="password"> <FormField v-slot="{ componentField }" name="password">
<FormItem> <FormItem>
<FormLabel> <FormLabel>
New password {{ m.true_male_gadfly_stab() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="hunter2" type="password" auto-capitalize="none" auto-correct="off" <Input placeholder="hunter2" type="password" auto-capitalize="none" auto-correct="off"
@ -60,7 +60,7 @@
<FormField v-slot="{ componentField }" name="password-confirm"> <FormField v-slot="{ componentField }" name="password-confirm">
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Confirm password {{ m.awful_cozy_jannes_rise() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="hunter2" type="password" auto-capitalize="none" auto-correct="off" <Input placeholder="hunter2" type="password" auto-capitalize="none" auto-correct="off"
@ -71,7 +71,7 @@
</FormField> </FormField>
</CardContent> </CardContent>
<CardFooter class="grid gap-2"> <CardFooter class="grid gap-2">
<Button variant="default" type="submit">Reset</Button> <Button variant="default" type="submit">{{ m.noisy_round_skate_yell() }}</Button>
</CardFooter> </CardFooter>
</form> </form>
</Card> </Card>
@ -100,10 +100,11 @@ import {
FormMessage, FormMessage,
} from "~/components/ui/form"; } from "~/components/ui/form";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components"; import { NuxtLink } from "#components";
useHead({ useHead({
title: "Reset Password", title: m.arable_arable_herring_lead(),
}); });
identity.value = null; identity.value = null;
@ -115,18 +116,26 @@ const formSchema = toTypedSchema(
password: z password: z
.string() .string()
.min(3, { .min(3, {
message: "Must be at least 3 characters long", message: m.smart_bold_macaw_aid({
count: 3,
}),
}) })
.max(100, { .max(100, {
message: "Must be at most 100 characters long", message: m.dry_smug_goldfish_promise({
count: 100,
}),
}), }),
"password-confirm": z "password-confirm": z
.string() .string()
.min(3, { .min(3, {
message: "Must be at least 3 characters long", message: m.smart_bold_macaw_aid({
count: 3,
}),
}) })
.max(100, { .max(100, {
message: "Must be at most 100 characters long", message: m.dry_smug_goldfish_promise({
count: 100,
}),
}), }),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
@ -134,7 +143,7 @@ const formSchema = toTypedSchema(
ctx.addIssue({ ctx.addIssue({
path: [...ctx.path, "password-confirm"], path: [...ctx.path, "password-confirm"],
code: "custom", code: "custom",
message: "Passwords do not match", message: m.candid_fancy_leopard_prosper(),
}); });
} }
return {}; return {};

View file

@ -15,6 +15,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import SelectPreference from "~/components/preferences/select.vue"; import SelectPreference from "~/components/preferences/select.vue";
import SwitchPreference from "~/components/preferences/switch.vue"; import SwitchPreference from "~/components/preferences/switch.vue";
import * as m from "~/paraglide/messages.js";
import { import {
type BooleanSetting, type BooleanSetting,
type EnumSetting, type EnumSetting,
@ -25,14 +26,14 @@ import {
} from "~/settings.ts"; } from "~/settings.ts";
useHead({ useHead({
title: "Preferences", title: m.broad_whole_herring_reside(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Preferences", text: m.broad_whole_herring_reside(),
}, },
], ],
requiresAuth: true, requiresAuth: true,

View file

@ -2,7 +2,7 @@
<div class="md:px-8 px-4 py-2 max-w-7xl mx-auto w-full space-y-6"> <div class="md:px-8 px-4 py-2 max-w-7xl mx-auto w-full space-y-6">
<div :class="cn('grid gap-2', profileEditor?.dirty && 'grid-cols-[1fr,auto]')"> <div :class="cn('grid gap-2', profileEditor?.dirty && 'grid-cols-[1fr,auto]')">
<h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize"> <h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize">
Account {{ m.tasty_late_termite_sew() }}
</h1> </h1>
<Button class="ml-auto" v-if="profileEditor?.dirty" @click="profileEditor.submitForm">Save</Button> <Button class="ml-auto" v-if="profileEditor?.dirty" @click="profileEditor.submitForm">Save</Button>
</div> </div>
@ -17,16 +17,17 @@ import { cn } from "@/lib/utils";
// biome-ignore lint/style/useImportType: <explanation> // biome-ignore lint/style/useImportType: <explanation>
import ProfileEditor from "~/components/preferences/profile/editor.vue"; import ProfileEditor from "~/components/preferences/profile/editor.vue";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Account Preferences", title: m.actual_mean_cow_dare(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Preferences", text: m.broad_whole_herring_reside(),
}, },
], ],
requiresAuth: true, requiresAuth: true,

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="md:px-8 px-4 py-2 max-w-7xl mx-auto w-full space-y-6"> <div class="md:px-8 px-4 py-2 max-w-7xl mx-auto w-full space-y-6">
<h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize"> <h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize">
Emojis {{ m.suave_smart_mantis_climb() }}
</h1> </h1>
<div v-if="emojis.length > 0" class="max-w-sm w-full relative"> <div v-if="emojis.length > 0" class="max-w-sm w-full relative">
<Input v-model="search" placeholder="Search" class="pl-8" /> <Input v-model="search" placeholder="Search" class="pl-8" />
@ -11,9 +11,9 @@
:name="name" /> :name="name" />
<Card v-else class="shadow-none bg-transparent border-none p-4"> <Card v-else class="shadow-none bg-transparent border-none p-4">
<CardHeader class="text-center gap-y-4"> <CardHeader class="text-center gap-y-4">
<CardTitle class="text-">No emojis found.</CardTitle> <CardTitle>{{ m.actual_steep_llama_rest() }}</CardTitle>
<CardDescription> <CardDescription>
Ask your administrator to add some emojis. {{ m.lucky_suave_myna_adore() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
@ -24,17 +24,24 @@
import type { Emoji } from "@versia/client/types"; import type { Emoji } from "@versia/client/types";
import { Search } from "lucide-vue-next"; import { Search } from "lucide-vue-next";
import Category from "~/components/preferences/emojis/category.vue"; import Category from "~/components/preferences/emojis/category.vue";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Emoji Preferences", title: m.mild_many_dolphin_mend(),
}); });
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Preferences", text: m.broad_whole_herring_reside(),
}, },
], ],
requiresAuth: true, requiresAuth: true,
@ -56,11 +63,11 @@ const categories = computed(() => {
const categories = new Map<string, Emoji[]>(); const categories = new Map<string, Emoji[]>();
for (const emoji of emojis.value) { for (const emoji of emojis.value) {
if (!emoji.category) { if (!emoji.category) {
if (!categories.has("Uncategorized")) { if (!categories.has(m.lucky_ago_rat_pinch())) {
categories.set("Uncategorized", []); categories.set(m.lucky_ago_rat_pinch(), []);
} }
categories.get("Uncategorized")?.push(emoji); categories.get(m.lucky_ago_rat_pinch())?.push(emoji);
continue; continue;
} }

View file

@ -5,11 +5,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as m from "~/paraglide/messages.js";
definePageMeta({ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Preferences", text: m.broad_whole_herring_reside(),
}, },
], ],
requiresAuth: true, requiresAuth: true,

View file

@ -9,6 +9,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Public from "~/components/timelines/public.vue"; import Public from "~/components/timelines/public.vue";
import TimelineScroller from "~/components/timelines/timeline-scroller.vue"; import TimelineScroller from "~/components/timelines/timeline-scroller.vue";
import * as m from "~/paraglide/messages.js";
useHead({ useHead({
title: "Public", title: "Public",
@ -18,10 +19,10 @@ definePageMeta({
layout: "app", layout: "app",
breadcrumbs: [ breadcrumbs: [
{ {
text: "Timelines", text: m.steep_aqua_fox_harbor(),
}, },
{ {
text: "Public", text: m.lost_trick_dog_grace(),
href: "/public", href: "/public",
}, },
], ],

View file

@ -6,18 +6,18 @@
<CardHeader> <CardHeader>
<Alert v-if="errors.error" variant="destructive" class="mb-4"> <Alert v-if="errors.error" variant="destructive" class="mb-4">
<AlertCircle class="size-4" /> <AlertCircle class="size-4" />
<AlertTitle>Error</AlertTitle> <AlertTitle>{{ m.vexed_each_falcon_enjoy() }}</AlertTitle>
<AlertDescription> <AlertDescription>
{{ errors.error }} {{ errors.error }}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<CardTitle as="h1" class="text-2xl break-words">Create an account</CardTitle> <CardTitle as="h1" class="text-2xl break-words">{{ m.wide_topical_vole_walk() }}</CardTitle>
</CardHeader> </CardHeader>
<CardContent v-if="instance && tos" class="grid gap-6"> <CardContent v-if="instance && tos" class="grid gap-6">
<FormField v-slot="{ componentField }" name="username"> <FormField v-slot="{ componentField }" name="username">
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Username {{ m.keen_clean_nils_slurp() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="petergriffin" type="text" auto-capitalize="none" <Input placeholder="petergriffin" type="text" auto-capitalize="none"
@ -30,7 +30,7 @@
<FormField v-slot="{ componentField }" name="email"> <FormField v-slot="{ componentField }" name="email">
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Email address {{ m.top_inclusive_wallaby_hack() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="peter.griffin@fox.com" type="email" auto-capitalize="none" <Input placeholder="peter.griffin@fox.com" type="email" auto-capitalize="none"
@ -43,7 +43,7 @@
<FormField v-slot="{ componentField }" name="password"> <FormField v-slot="{ componentField }" name="password">
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Password {{ m.livid_bright_wallaby_quiz() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="hunter2" type="password" auto-capitalize="none" auto-complete="password" <Input placeholder="hunter2" type="password" auto-capitalize="none" auto-complete="password"
@ -55,7 +55,7 @@
<FormField v-slot="{ componentField }" name="password-confirm"> <FormField v-slot="{ componentField }" name="password-confirm">
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Confirm password {{ m.awful_cozy_jannes_rise() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="hunter2" type="password" auto-capitalize="none" auto-complete="password" <Input placeholder="hunter2" type="password" auto-capitalize="none" auto-complete="password"
@ -72,8 +72,8 @@
</FormControl> </FormControl>
<FormLabel> <FormLabel>
<Dialog> <Dialog>
I agree to the <DialogTrigger :as-child="true"><Button variant="link" {{ m.plane_quick_chipmunk_rush() }} <DialogTrigger :as-child="true"><Button variant="link"
class="px-0 underline">Terms of Service</Button>.</DialogTrigger> class="px-0 underline">{{ m.glad_last_crow_dine() }}</Button>.</DialogTrigger>
<DialogContent class="!max-h-[90vh] overflow-auto"> <DialogContent class="!max-h-[90vh] overflow-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>{{ instance.title }} <DialogTitle>{{ instance.title }}
@ -88,11 +88,11 @@
</FormItem> </FormItem>
</FormField> </FormField>
<div class="flex-col flex gap-y-1 text-sm text-muted-foreground"> <div class="flex-col flex gap-y-1 text-sm text-muted-foreground">
<p>Passwords are never stored in plain text.</p> <p>{{ m.happy_house_dragonfly_clap() }}</p>
</div> </div>
</CardContent> </CardContent>
<CardFooter v-if="instance && tos" class="grid gap-2"> <CardFooter v-if="instance && tos" class="grid gap-2">
<Button variant="default" type="submit">Register</Button> <Button variant="default" type="submit">{{ m.early_last_ocelot_praise() }}</Button>
</CardFooter> </CardFooter>
<div v-else class="p-4 flex items-center justify-center h-48"> <div v-else class="p-4 flex items-center justify-center h-48">
<Loader class="size-8 animate-spin" /> <Loader class="size-8 animate-spin" />
@ -100,14 +100,14 @@
</Card> </Card>
<Card v-else class="w-full max-w-md"> <Card v-else class="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Sorry :c</CardTitle> <CardTitle>{{ m.wide_away_cat_taste() }}</CardTitle>
<CardDescription> <CardDescription>
Registrations are disabled on this instance. {{ m.safe_candid_horse_jump() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="grid"> <CardFooter class="grid">
<Button :as="NuxtLink" href="/" variant="default"> <Button :as="NuxtLink" href="/" variant="default">
Back to front page {{ m.every_tangy_koala_persist() }}}
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
@ -117,7 +117,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { toTypedSchema } from "@vee-validate/zod"; import { toTypedSchema } from "@vee-validate/zod";
import { Client, type ResponseError } from "@versia/client"; import { Client, type ResponseError } from "@versia/client";
import { AlertCircle, Check, Loader } from "lucide-vue-next"; import { AlertCircle, Loader } from "lucide-vue-next";
import { useForm } from "vee-validate"; import { useForm } from "vee-validate";
import { z } from "zod"; import { z } from "zod";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
@ -126,10 +126,11 @@ import { Checkbox } from "~/components/ui/checkbox";
import { Dialog, DialogContent, DialogHeader } from "~/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader } from "~/components/ui/dialog";
import { FormItem } from "~/components/ui/form"; import { FormItem } from "~/components/ui/form";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components"; import { NuxtLink } from "#components";
useHead({ useHead({
title: "Register", title: m.early_last_ocelot_praise(),
}); });
const schema = toTypedSchema( const schema = toTypedSchema(
@ -141,13 +142,10 @@ const schema = toTypedSchema(
username: z username: z
.string() .string()
.min(3) .min(3)
.regex( .regex(/^[a-z0-9_-]+$/, m.sea_maroon_peacock_yell()),
/^[a-z0-9_]+$/,
"Must be lowercase letters, numbers, or underscores",
),
reason: z.string().optional(), reason: z.string().optional(),
tos: z.boolean().refine((value) => value, { tos: z.boolean().refine((value) => value, {
message: "You must agree to the Terms of Service", message: m.civil_loose_coyote_jump(),
}), }),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
@ -155,7 +153,7 @@ const schema = toTypedSchema(
ctx.addIssue({ ctx.addIssue({
path: [...ctx.path, "password-confirm"], path: [...ctx.path, "password-confirm"],
code: "custom", code: "custom",
message: "Passwords do not match", message: m.candid_fancy_leopard_prosper(),
}); });
} }
return {}; return {};

View file

@ -4,14 +4,14 @@
}"> }">
<Card class="w-full max-w-md"> <Card class="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>Success</CardTitle> <CardTitle>{{ m.late_mean_capybara_fade() }}</CardTitle>
<CardDescription> <CardDescription>
You've successfully registered. You can now log in with your new account. {{ m.left_maroon_myna_drip() }}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="grid"> <CardFooter class="grid">
<Button :as="NuxtLink" href="/" variant="default"> <Button :as="NuxtLink" href="/" variant="default">
Back to front page {{ m.every_tangy_koala_persist() }}
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
@ -21,6 +21,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"; import { Card, CardFooter, CardHeader, CardTitle } from "~/components/ui/card";
import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components"; import { NuxtLink } from "#components";
useHead({ useHead({

View file

@ -1,3 +1,6 @@
import * as m from "~/paraglide/messages.js";
import { setLanguageTag } from "./paraglide/runtime";
export enum SettingType { export enum SettingType {
String = "string", String = "string",
Boolean = "boolean", Boolean = "boolean",
@ -8,8 +11,8 @@ export enum SettingType {
} }
export type Setting = { export type Setting = {
title: string; title: () => string;
description: string; description: () => string;
notImplemented?: boolean; notImplemented?: boolean;
type: SettingType; type: SettingType;
value: unknown; value: unknown;
@ -31,7 +34,7 @@ export type EnumSetting = Setting & {
value: string; value: string;
options: { options: {
value: string; value: string;
label: string; label: () => string;
icon?: string; icon?: string;
}[]; }[];
}; };
@ -66,6 +69,7 @@ export enum SettingPages {
} }
export enum SettingIds { export enum SettingIds {
Language = "language",
Mfm = "mfm", Mfm = "mfm",
CustomCSS = "custom-css", CustomCSS = "custom-css",
Theme = "theme", Theme = "theme",
@ -85,195 +89,226 @@ export enum SettingIds {
SidebarStyle = "sidebar-style", SidebarStyle = "sidebar-style",
} }
export const settings: Record<SettingIds, Setting> = { export const settings = (): Record<SettingIds, Setting> => {
[SettingIds.Mfm]: { return {
title: "Render MFM", [SettingIds.Mfm]: {
description: "Render Misskey-Flavoured Markdown.", title: m.quaint_clear_boar_attend,
type: SettingType.Boolean, description: m.aloof_helpful_larva_spur,
value: false, type: SettingType.Boolean,
page: SettingPages.Behaviour, value: false,
notImplemented: true, page: SettingPages.Behaviour,
} as BooleanSetting, notImplemented: true,
[SettingIds.SidebarStyle]: { } as BooleanSetting,
title: "Sidebar Style", [SettingIds.Language]: {
description: "Style of the left sidebar.", title: m.pretty_born_jackal_dial,
type: SettingType.Enum, description: m.tired_happy_lobster_pet,
value: "inset", type: SettingType.Enum,
options: [ value: "en",
{ options: [
value: "inset", {
label: "Inset", value: "en",
}, label: () =>
{ m.keen_aware_goldfish_thrive(
value: "sidebar", {},
label: "Sidebar", {
}, languageTag: "en",
{ },
value: "floating", ),
label: "Floating", },
}, {
], value: "fr",
page: SettingPages.Appearance, label: () =>
} as EnumSetting, m.vivid_mellow_sawfish_approve(
[SettingIds.AvatarShape]: { {},
title: "Avatar Shape", {
description: "Shape of all user avatars.", languageTag: "fr",
type: SettingType.Enum, },
value: "square", ),
options: [ },
{ ],
value: "circle", page: SettingPages.Behaviour,
label: "Round", } as EnumSetting,
}, [SettingIds.SidebarStyle]: {
{ title: m.deft_seemly_donkey_slide,
value: "square", description: m.wide_least_samuel_conquer,
label: "Square", type: SettingType.Enum,
}, value: "inset",
], options: [
page: SettingPages.Appearance, {
} as EnumSetting, value: "inset",
[SettingIds.CustomCSS]: { label: m.fluffy_north_crow_blink,
title: "Custom CSS", },
description: "Custom CSS for the UI.", {
type: SettingType.Code, value: "sidebar",
value: "", label: m.day_polite_newt_loop,
language: "css", },
page: SettingPages.Appearance, {
} as CodeSetting, value: "floating",
[SettingIds.Theme]: { label: m.jolly_mad_jackdaw_assure,
title: "Theme", },
description: "UI theme.", ],
type: SettingType.Enum, page: SettingPages.Appearance,
value: "dark", } as EnumSetting,
options: [ [SettingIds.AvatarShape]: {
{ title: m.fit_cool_bulldog_dine,
value: "dark", description: m.agent_misty_firefox_arise,
label: "Dark", type: SettingType.Enum,
}, value: "square",
{ options: [
value: "light", {
label: "Light", value: "circle",
}, label: m.polite_awful_ladybug_greet,
{ },
value: "system", {
label: "System", value: "square",
}, label: m.sad_each_cowfish_lock,
], },
page: SettingPages.Appearance, ],
} as EnumSetting, page: SettingPages.Appearance,
[SettingIds.CustomEmojis]: { } as EnumSetting,
title: "Render Custom Emojis", [SettingIds.CustomCSS]: {
description: "Render custom emojis. Requires a page reload to apply.", title: m.smart_awake_dachshund_view,
type: SettingType.Boolean, description: m.loved_topical_rat_coax,
value: true, type: SettingType.Code,
page: SettingPages.Behaviour, value: "",
} as BooleanSetting, language: "css",
[SettingIds.ShowContentWarning]: { page: SettingPages.Appearance,
title: "Blur Sensitive Content", } as CodeSetting,
description: "Blur notes marked sensitive/spoiler.", [SettingIds.Theme]: {
type: SettingType.Boolean, title: m.hour_elegant_mink_grip,
value: true, description: m.male_stout_florian_feast,
page: SettingPages.Behaviour, type: SettingType.Enum,
} as BooleanSetting, value: "dark",
[SettingIds.PopupAvatarHover]: { options: [
title: "Popup Profile Hover", {
description: "Show profile popup when hovering over a user's avatar.", value: "dark",
type: SettingType.Boolean, label: m.wise_neat_ox_buzz,
value: true, },
page: SettingPages.Behaviour, {
} as BooleanSetting, value: "light",
[SettingIds.InfiniteScroll]: { label: m.each_strong_snail_aid,
title: "Infinite Scroll", },
description: {
"Automatically load more notes when reaching the bottom of the page.", value: "system",
type: SettingType.Boolean, label: m.helpful_raw_seal_nurture,
value: true, },
page: SettingPages.Behaviour, ],
} as BooleanSetting, page: SettingPages.Appearance,
[SettingIds.ConfirmDelete]: { } as EnumSetting,
title: "Confirm Delete", [SettingIds.CustomEmojis]: {
description: "Confirm before deleting a note.", title: m.loud_raw_sheep_imagine,
type: SettingType.Boolean, description: m.inclusive_pink_tuna_enjoy,
value: true, type: SettingType.Boolean,
page: SettingPages.Behaviour, value: true,
} as BooleanSetting, page: SettingPages.Behaviour,
[SettingIds.ConfirmFollow]: { } as BooleanSetting,
title: "Confirm Follow", [SettingIds.ShowContentWarning]: {
description: "Confirm before following/unfollowing a user.", title: m.fair_swift_elephant_hunt,
type: SettingType.Boolean, description: m.gray_minor_bee_endure,
value: false, type: SettingType.Boolean,
page: SettingPages.Behaviour, value: true,
} as BooleanSetting, page: SettingPages.Behaviour,
[SettingIds.ConfirmReblog]: { } as BooleanSetting,
title: "Confirm Reblog", [SettingIds.PopupAvatarHover]: {
description: "Confirm before reblogging a note.", title: m.north_nimble_turkey_transform,
type: SettingType.Boolean, description: m.bold_moving_fly_savor,
value: false, type: SettingType.Boolean,
page: SettingPages.Behaviour, value: true,
} as BooleanSetting, page: SettingPages.Behaviour,
[SettingIds.ConfirmLike]: { } as BooleanSetting,
title: "Confirm Like", [SettingIds.InfiniteScroll]: {
description: "Confirm before liking a note.", title: m.sleek_this_earthworm_hug,
type: SettingType.Boolean, description: m.plane_dark_salmon_pout,
value: false, type: SettingType.Boolean,
page: SettingPages.Behaviour, value: true,
} as BooleanSetting, page: SettingPages.Behaviour,
[SettingIds.CtrlEnterToSend]: { } as BooleanSetting,
title: "Ctrl+Enter to Send", [SettingIds.ConfirmDelete]: {
description: "Send a note by pressing ⌘+Enter or Ctrl+Enter.", title: m.trite_salty_eel_race,
type: SettingType.Boolean, description: m.helpful_early_worm_laugh,
value: true, type: SettingType.Boolean,
page: SettingPages.Behaviour, value: true,
} as BooleanSetting, page: SettingPages.Behaviour,
[SettingIds.EmojiTheme]: { } as BooleanSetting,
title: "Emoji Theme", [SettingIds.ConfirmFollow]: {
description: title: m.jolly_empty_bullock_mend,
"Theme used for rendering emojis. Requires a page reload to apply.", description: m.calm_male_wombat_relish,
type: SettingType.Enum, type: SettingType.Boolean,
value: "native", value: false,
options: [ page: SettingPages.Behaviour,
{ } as BooleanSetting,
value: "native", [SettingIds.ConfirmReblog]: {
label: "Operating System", title: m.honest_great_rooster_taste,
}, description: m.wacky_inner_osprey_intend,
{ type: SettingType.Boolean,
value: "twemoji", value: false,
label: "Twitter Emojis", page: SettingPages.Behaviour,
}, } as BooleanSetting,
{ [SettingIds.ConfirmLike]: {
value: "noto", title: m.patchy_basic_alligator_inspire,
label: "Noto Emoji", description: m.antsy_weak_raven_treat,
}, type: SettingType.Boolean,
{ value: false,
value: "fluent", page: SettingPages.Behaviour,
label: "Fluent Emojis", } as BooleanSetting,
}, [SettingIds.CtrlEnterToSend]: {
{ title: m.equal_blue_zebra_launch,
value: "fluent-flat", description: m.heavy_pink_meerkat_affirm,
label: "Fluent Emojis (flat version)", type: SettingType.Boolean,
}, value: true,
], page: SettingPages.Behaviour,
page: SettingPages.Appearance, } as BooleanSetting,
} as EnumSetting, [SettingIds.EmojiTheme]: {
[SettingIds.BackgroundURL]: { title: m.weak_bad_martin_glow,
title: "Background URL", description: m.warm_round_dove_skip,
description: "Change the background image of the site.", type: SettingType.Enum,
type: SettingType.String, value: "native",
value: "", options: [
page: SettingPages.Appearance, {
} as StringSetting, value: "native",
[SettingIds.NotificationsSidebar]: { label: m.slimy_sound_termite_hug,
title: "Notifications Sidebar", },
description: "Display a sidebar with notifications on desktop.", {
type: SettingType.Boolean, value: "twemoji",
value: true, label: m.new_brave_maggot_relish,
page: SettingPages.Appearance, },
} as BooleanSetting, {
value: "noto",
label: m.shy_clear_spider_cook,
},
{
value: "fluent",
label: m.many_tasty_midge_zoom,
},
{
value: "fluent-flat",
label: m.less_early_lionfish_honor,
},
],
page: SettingPages.Appearance,
} as EnumSetting,
[SettingIds.BackgroundURL]: {
title: m.stock_large_marten_comfort,
description: m.mean_weird_donkey_stab,
type: SettingType.String,
value: "",
page: SettingPages.Appearance,
} as StringSetting,
[SettingIds.NotificationsSidebar]: {
title: m.tired_jumpy_rook_slurp,
description: m.wide_new_robin_empower,
type: SettingType.Boolean,
value: true,
page: SettingPages.Appearance,
} as BooleanSetting,
};
}; };
export const getSettingsForPage = (page: SettingPages): Partial<Settings> => { export const getSettingsForPage = (page: SettingPages): Partial<Settings> => {
return Object.fromEntries( return Object.fromEntries(
Object.entries(settings).filter(([, setting]) => setting.page === page), Object.entries(settings()).filter(
([, setting]) => setting.page === page,
),
); );
}; };
@ -284,14 +319,14 @@ export const getSettingsForPage = (page: SettingPages): Partial<Settings> => {
export const mergeSettings = ( export const mergeSettings = (
settingsToMerge: Record<SettingIds, Setting["value"]>, settingsToMerge: Record<SettingIds, Setting["value"]>,
): Settings => { ): Settings => {
const finalSettings = structuredClone(settings); const finalSettings = settings();
for (const [key, value] of Object.entries(settingsToMerge)) { for (const [key, value] of Object.entries(settingsToMerge)) {
if (key in settings) { if (key in settings()) {
finalSettings[key as SettingIds].value = value; finalSettings[key as SettingIds].value = value;
} }
} }
return finalSettings; return finalSettings;
}; };
export type Settings = typeof settings; export type Settings = ReturnType<typeof settings>;

View file

@ -1,9 +1,10 @@
import type { ApplicationData } from "@versia/client/types"; import type { ApplicationData } from "@versia/client/types";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import * as m from "~/paraglide/messages.js";
export const signIn = async (appData: Ref<ApplicationData | null>) => { export const signIn = async (appData: Ref<ApplicationData | null>) => {
const id = toast.loading("Signing in..."); const id = toast.loading(m.level_due_ox_greet());
const output = await client.value.createApp("Versia", { const output = await client.value.createApp("Versia", {
scopes: ["read", "write", "follow", "push"], scopes: ["read", "write", "follow", "push"],
@ -13,7 +14,7 @@ export const signIn = async (appData: Ref<ApplicationData | null>) => {
if (!output?.data) { if (!output?.data) {
toast.dismiss(id); toast.dismiss(id);
toast.error("Failed to create app"); toast.error(m.silly_sour_fireant_fear());
return; return;
} }
@ -30,7 +31,7 @@ export const signIn = async (appData: Ref<ApplicationData | null>) => {
if (!url) { if (!url) {
toast.dismiss(id); toast.dismiss(id);
toast.error("Failed to generate auth URL"); toast.error(m.candid_frail_lion_value());
return; return;
} }