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 ConfirmationModal from "./components/modals/confirm.vue";
import { Toaster } from "./components/ui/sonner";
import { setLanguageTag } from "./paraglide/runtime";
import { type EnumSetting, SettingIds } from "./settings";
// Sin
//import "~/styles/mcdonalds.css";
const lang = useLanguage();
setLanguageTag(lang.value);
const code = useRequestURL().searchParams.get("code");
const appData = useAppData();
const instance = useInstance();

View file

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

View file

@ -2,6 +2,7 @@
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import type { Status, StatusSource } from "@versia/client/types";
import { toast } from "vue-sonner";
import * as m from "~/paraglide/messages.js";
import Composer from "./composer.vue";
useListen("composer:open", () => {
@ -11,7 +12,7 @@ useListen("composer:open", () => {
});
useListen("composer:edit", async (note) => {
const id = toast.loading("Loading note data...", {
const id = toast.loading(m.wise_late_fireant_walk(), {
duration: 0,
});
const { data: source } = await client.value.getStatusSource(note.id);
@ -56,13 +57,16 @@ const relation = ref(
</script>
<template>
<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">
<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">
<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>
<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>
<Composer :relation="relation ?? undefined" />
</DialogContent>

View file

@ -12,6 +12,7 @@ import {
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import * as m from "~/paraglide/messages.js";
import type { ConfirmModalOptions, ConfirmModalResult } from "./composable.ts";
defineProps<{
@ -41,14 +42,14 @@ const inputValue = ref<string>("");
<div v-if="modalOptions.inputType === 'text'" class="grid gap-4 py-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" />
</div>
</div>
<div v-else-if="modalOptions.inputType === 'textarea'" class="grid gap-4 py-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" />
</div>
</div>

View file

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

View file

@ -1,22 +1,22 @@
<template>
<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" />
{{ numberFormat(replyCount) }}
</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" />
{{ numberFormat(likeCount) }}
</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" />
{{ numberFormat(reblogCount) }}
</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" />
</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')">
<Button variant="ghost" title="Actions">
<Button variant="ghost" :title="m.busy_merry_cowfish_absorb()">
<Ellipsis class="size-5 text-primary" />
</Button>
</Menu>
@ -27,6 +27,8 @@
import { Ellipsis, Heart, Quote, Repeat, Reply } from "lucide-vue-next";
import { toast } from "vue-sonner";
import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
import { languageTag } from "~/paraglide/runtime";
import { SettingIds } from "~/settings";
import { confirmModalService } from "../modals/composable";
import Menu from "./menu.vue";
@ -58,9 +60,9 @@ const confirmReblogs = useSetting(SettingIds.ConfirmReblog);
const like = async () => {
if (confirmLikes.value.value) {
const confirmation = await confirmModalService.confirm({
title: "Like status",
message: "Are you sure you want to like this status?",
confirmText: "Like",
title: m.slimy_least_ray_aid(),
message: m.stale_new_ray_jolt(),
confirmText: m.royal_close_samuel_scold(),
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);
toast.dismiss(id);
toast.success("Status liked");
toast.success(m.mealy_slow_buzzard_commend());
useEvent("note:edit", data);
};
const unlike = async () => {
if (confirmLikes.value.value) {
const confirmation = await confirmModalService.confirm({
title: "Unlike status",
message: "Are you sure you want to unlike this status?",
confirmText: "Unlike",
title: m.odd_strong_halibut_prosper(),
message: m.slow_blue_parrot_savor(),
confirmText: m.vexed_fluffy_clownfish_dance(),
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);
toast.dismiss(id);
toast.success("Status unliked");
toast.success(m.fresh_direct_bear_affirm());
useEvent("note:edit", data);
};
const reblog = async () => {
if (confirmReblogs.value.value) {
const confirmation = await confirmModalService.confirm({
title: "Reblog status",
message: "Are you sure you want to reblog this status?",
confirmText: "Reblog",
title: m.best_mellow_llama_surge(),
message: m.salty_plain_mallard_gaze(),
confirmText: m.aware_helpful_marlin_drop(),
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);
toast.dismiss(id);
toast.success("Status reblogged");
toast.success(m.weird_moving_hawk_lift());
useEvent("note:edit", data.reblog || data);
};
const unreblog = async () => {
if (confirmReblogs.value.value) {
const confirmation = await confirmModalService.confirm({
title: "Unreblog status",
message: "Are you sure you want to unreblog this status?",
confirmText: "Unreblog",
title: m.main_fancy_octopus_loop(),
message: m.odd_alive_swan_express(),
confirmText: m.lime_neat_ox_stab(),
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);
toast.dismiss(id);
toast.success("Status unreblogged");
toast.success(m.royal_polite_moose_catch());
useEvent("note:edit", data);
};
const numberFormat = (number = 0) =>
number !== 0
? new Intl.NumberFormat(undefined, {
? new Intl.NumberFormat(languageTag(), {
notation: "compact",
compactDisplay: "short",
maximumFractionDigits: 1,

View file

@ -1,14 +1,14 @@
<template>
<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">
<AlertTitle class="sr-only">Sensitive content</AlertTitle>
<AlertTitle class="sr-only">{{ m.livid_tangy_lionfish_clasp() }}</AlertTitle>
<div>
<TriangleAlert class="size-4" />
</div>
<AlertDescription>
{{ contentWarning || "This content is sensitive" }}
{{ contentWarning || m.sour_seemly_bird_hike() }}
</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>
<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"
class="absolute bottom-2 right-1/2 translate-x-1/2">{{
collapsed
? `Show more${plainContent ? `${formattedCharacterCount} characters` : ""
}`
: "Show less"
? `${m.lazy_honest_mammoth_bump()}${plainContent ? `${m.dark_spare_goldfish_charm({
count: formattedCharacterCount ?? '0',
})}` : "" }`
: m.that_misty_mule_arrive()
}}</Button>
</div>
@ -41,6 +42,7 @@ import { cn } from "@/lib/utils";
import type { Attachment, Emoji, Status } from "@versia/client/types";
import { TriangleAlert } from "lucide-vue-next";
import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
import { languageTag } from "~/paraglide/runtime";
import { type BooleanSetting, SettingIds } from "~/settings";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@
{{ emoji.shortcode }}
</CardTitle>
<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>
</CardHeader>
<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">
<CardHeader class="space-y-0.5 p-0">
<CardTitle class="text-base">
{{ setting.title }}
{{ setting.title() }}
</CardTitle>
<CardDescription>
{{ setting.description }}
{{ setting.description() }}
</CardDescription>
</CardHeader>
<CardFooter class="p-0">
@ -15,7 +15,7 @@
</SelectTrigger>
<SelectContent>
<SelectItem v-for="option of setting.options" :value="option.value">
{{ option.label }}
{{ option.label() }}
</SelectItem>
</SelectContent>
</Select>

View file

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

View file

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

View file

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

View file

@ -1,5 +1,7 @@
import type { Client } from "@versia/client";
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>) => {
// 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) {
// Reset tokenData
identity.value = null;
useEvent("notification:new", {
type: "error",
title: "Your session has expired",
description:
"You have been logged out. Please log in again.",
toast.error(m.fancy_this_wasp_renew(), {
description: m.real_weird_deer_stop(),
});
}
});

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";
const useSettings = () => {
return useLocalStorage<Settings>("versia:settings", settingsJson, {
return useLocalStorage<Settings>("versia:settings", settingsJson(), {
serializer: {
read(raw) {
const json = StorageSerializers.object.read(raw);

View file

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

View file

@ -56,10 +56,8 @@
"awake_quick_cuckoo_smile": "User followed",
"funny_aloof_swan_loop": "Unfollow user",
"cute_polite_oryx_blend": "Unfollow",
"dirty_inclusive_meerkat_nudge": "Cancel",
"big_safe_guppy_mix": "Unfollowing user...",
"misty_level_stingray_expand": "User unfollowed",
"lime_day_squid_pout": "Global",
"witty_heroic_trout_cry": "Uploaded by you",
"cuddly_such_swallow_hush": "Rename",
"tense_quick_cod_favor": "Delete",
@ -100,7 +98,7 @@
"sunny_novel_otter_glow": "Must be at least 3 characters long",
"fluffy_soft_wolf_cook": "Email (or username)",
"livid_bright_wallaby_quiz": "Password",
"fuzzy_sea_moth_absorb": "Sign In",
"fuzzy_sea_moth_absorb": "Sign in",
"tidy_tidy_cow_cut": "Or continue with",
"slow_these_kestrel_sail": "Accept",
"weary_steep_yak_embrace": "Reject",
@ -108,5 +106,221 @@
"busy_awful_mouse_jump": "Follow request accepted.",
"front_sunny_penguin_flip": "Rejecting follow request...",
"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",
"funny_aloof_swan_loop": "Se désabonner",
"cute_polite_oryx_blend": "Se désabonner",
"dirty_inclusive_meerkat_nudge": "Annuler",
"big_safe_guppy_mix": "Désabonnement...",
"misty_level_stingray_expand": "Utilisateur désabonné",
"lime_day_squid_pout": "Global",
"witty_heroic_trout_cry": "Ajouté par vous",
"cuddly_such_swallow_hush": "Renommer",
"tense_quick_cod_favor": "Supprimer",
@ -105,5 +103,201 @@
"busy_awful_mouse_jump": "Demande de suivi acceptée.",
"front_sunny_penguin_flip": "Rejet de la demande de suivi...",
"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>
<script setup lang="ts">
import { useTitle } from "@vueuse/core";
import { Loader } from "lucide-vue-next";
import Note from "~/components/notes/note.vue";
import * as m from "~/paraglide/messages.js";
definePageMeta({
layout: "app",
breadcrumbs: [
{
text: "Note",
text: m.chunky_awake_mallard_grow(),
},
],
});
@ -57,7 +57,9 @@ watch(
useSeoMeta({
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)),
ogImage: computed(() =>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,10 +4,11 @@ import { AlertCircle, Loader } from "lucide-vue-next";
import UserAuthForm from "~/components/oauth/login.vue";
import { Alert, AlertDescription, AlertTitle } from "~/components/ui/alert";
import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components";
useHead({
title: "Sign In",
title: m.fuzzy_sea_moth_absorb(),
});
const host = new URL(useBaseUrl().value).host;
@ -18,7 +19,7 @@ const { error, error_description } = useUrlSearchParams();
<template>
<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">
Register
{{ m.noble_cute_ocelot_aim() }}
</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"
:style="{
@ -55,10 +56,11 @@ const { error, error_description } = useUrlSearchParams();
</Alert>
<div class="flex flex-col space-y-2 text-center">
<h1 class="text-2xl font-semibold tracking-tight">
Log in to your account.
{{ m.novel_fine_stork_snap() }}
</h1>
<p class="text-sm text-muted-foreground">
Enter your credentials for <code>{{ host }}</code>.
<p class="text-sm text-muted-foreground" v-html="m.smug_main_whale_snip({
host,
})">
</p>
</div>
<UserAuthForm v-if="instance" :instance="instance" />

View file

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

View file

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

View file

@ -15,6 +15,7 @@
<script lang="ts" setup>
import SelectPreference from "~/components/preferences/select.vue";
import SwitchPreference from "~/components/preferences/switch.vue";
import * as m from "~/paraglide/messages.js";
import {
type BooleanSetting,
type EnumSetting,
@ -25,14 +26,14 @@ import {
} from "~/settings.ts";
useHead({
title: "Preferences",
title: m.broad_whole_herring_reside(),
});
definePageMeta({
layout: "app",
breadcrumbs: [
{
text: "Preferences",
text: m.broad_whole_herring_reside(),
},
],
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="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">
Account
{{ m.tasty_late_termite_sew() }}
</h1>
<Button class="ml-auto" v-if="profileEditor?.dirty" @click="profileEditor.submitForm">Save</Button>
</div>
@ -17,16 +17,17 @@ import { cn } from "@/lib/utils";
// biome-ignore lint/style/useImportType: <explanation>
import ProfileEditor from "~/components/preferences/profile/editor.vue";
import { Button } from "~/components/ui/button";
import * as m from "~/paraglide/messages.js";
useHead({
title: "Account Preferences",
title: m.actual_mean_cow_dare(),
});
definePageMeta({
layout: "app",
breadcrumbs: [
{
text: "Preferences",
text: m.broad_whole_herring_reside(),
},
],
requiresAuth: true,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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