Add translation keys for collections (#1496)
* Begin Work * Add more common messages * Work on modal * Add more keys * Add keys for icon buttons * Add more keys * Handle error keys * Add more keys * Add more keys * Edit fields keys * Finish (almost) * Finish work for collection page * Dashboard Nav stack & Format * WIP * Move some messages to common * Finish work * Format * Reorganization * Fix some mistake * add common collections label * Add collections label key to default layout * Make title and description reactive (#8) --------- Co-authored-by: Sasha Sorokin <10401817+brawaru@users.noreply.github.com>
This commit is contained in:
parent
0195e94aa7
commit
52b299315d
@ -99,7 +99,7 @@
|
|||||||
<hr class="divider" />
|
<hr class="divider" />
|
||||||
<NuxtLink class="item button-transparent" to="/dashboard/collections">
|
<NuxtLink class="item button-transparent" to="/dashboard/collections">
|
||||||
<LibraryIcon class="icon" />
|
<LibraryIcon class="icon" />
|
||||||
<span class="title">Collections</span>
|
<span class="title">{{ formatMessage(commonMessages.collectionsLabel) }}</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="item button-transparent" to="/dashboard/notifications">
|
<NuxtLink class="item button-transparent" to="/dashboard/notifications">
|
||||||
<NotificationIcon class="icon" />
|
<NotificationIcon class="icon" />
|
||||||
@ -398,6 +398,8 @@ import NavRow from '~/components/ui/NavRow.vue'
|
|||||||
import ModalCreation from '~/components/ui/ModalCreation.vue'
|
import ModalCreation from '~/components/ui/ModalCreation.vue'
|
||||||
import Avatar from '~/components/ui/Avatar.vue'
|
import Avatar from '~/components/ui/Avatar.vue'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const app = useNuxtApp()
|
const app = useNuxtApp()
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
const cosmetics = useCosmetics()
|
const cosmetics = useCosmetics()
|
||||||
|
|||||||
@ -26,6 +26,78 @@
|
|||||||
"button.save": {
|
"button.save": {
|
||||||
"message": "Save"
|
"message": "Save"
|
||||||
},
|
},
|
||||||
|
"collection.button.delete-icon": {
|
||||||
|
"message": "Delete icon"
|
||||||
|
},
|
||||||
|
"collection.button.edit-icon": {
|
||||||
|
"message": "Edit icon"
|
||||||
|
},
|
||||||
|
"collection.button.remove-project": {
|
||||||
|
"message": "Remove project"
|
||||||
|
},
|
||||||
|
"collection.button.unfollow-project": {
|
||||||
|
"message": "Unfollow project"
|
||||||
|
},
|
||||||
|
"collection.button.upload-icon": {
|
||||||
|
"message": "Upload icon"
|
||||||
|
},
|
||||||
|
"collection.delete-modal.description": {
|
||||||
|
"message": "This will remove this collection forever. This action cannot be undone."
|
||||||
|
},
|
||||||
|
"collection.delete-modal.title": {
|
||||||
|
"message": "Are you sure you want to delete this collection?"
|
||||||
|
},
|
||||||
|
"collection.description": {
|
||||||
|
"message": "{description} - View the collection {name} by {username} on Modrinth"
|
||||||
|
},
|
||||||
|
"collection.description.following": {
|
||||||
|
"message": "Auto-generated collection of all the projects you're following."
|
||||||
|
},
|
||||||
|
"collection.error.not-found": {
|
||||||
|
"message": "Collection not found"
|
||||||
|
},
|
||||||
|
"collection.label.collection": {
|
||||||
|
"message": "Collection"
|
||||||
|
},
|
||||||
|
"collection.label.created-at": {
|
||||||
|
"message": "Created {ago}"
|
||||||
|
},
|
||||||
|
"collection.label.curated-by": {
|
||||||
|
"message": "Curated by"
|
||||||
|
},
|
||||||
|
"collection.label.no-projects": {
|
||||||
|
"message": "This collection has no projects!"
|
||||||
|
},
|
||||||
|
"collection.label.no-projects-auth": {
|
||||||
|
"message": "You don't have any projects.\nWould you like to <create-link>add one</create-link>?"
|
||||||
|
},
|
||||||
|
"collection.label.owner": {
|
||||||
|
"message": "Owner"
|
||||||
|
},
|
||||||
|
"collection.label.private": {
|
||||||
|
"message": "Private"
|
||||||
|
},
|
||||||
|
"collection.label.projects-count": {
|
||||||
|
"message": "{count, plural, one {<stat>{count}</stat> project} other {<stat>{count}</stat> projects}}"
|
||||||
|
},
|
||||||
|
"collection.label.updated-at": {
|
||||||
|
"message": "Updated {ago}"
|
||||||
|
},
|
||||||
|
"collection.title": {
|
||||||
|
"message": "{name} - Collection"
|
||||||
|
},
|
||||||
|
"dashboard.collections.button.create-new": {
|
||||||
|
"message": "Create new"
|
||||||
|
},
|
||||||
|
"dashboard.collections.label.projects-count": {
|
||||||
|
"message": "{count, plural, one {{count} project} other {{count} projects}}"
|
||||||
|
},
|
||||||
|
"dashboard.collections.label.search-input": {
|
||||||
|
"message": "Search your collections"
|
||||||
|
},
|
||||||
|
"dashboard.collections.long-title": {
|
||||||
|
"message": "Your collections"
|
||||||
|
},
|
||||||
"frog": {
|
"frog": {
|
||||||
"message": "You've been frogged! 🐸"
|
"message": "You've been frogged! 🐸"
|
||||||
},
|
},
|
||||||
@ -50,6 +122,33 @@
|
|||||||
"input.view.list": {
|
"input.view.list": {
|
||||||
"message": "List view"
|
"message": "List view"
|
||||||
},
|
},
|
||||||
|
"label.collections": {
|
||||||
|
"message": "Collections"
|
||||||
|
},
|
||||||
|
"label.delete": {
|
||||||
|
"message": "Delete"
|
||||||
|
},
|
||||||
|
"label.description": {
|
||||||
|
"message": "Description"
|
||||||
|
},
|
||||||
|
"label.followed-projects": {
|
||||||
|
"message": "Followed projects"
|
||||||
|
},
|
||||||
|
"label.public": {
|
||||||
|
"message": "Public"
|
||||||
|
},
|
||||||
|
"label.rejected": {
|
||||||
|
"message": "Rejected"
|
||||||
|
},
|
||||||
|
"label.title": {
|
||||||
|
"message": "Title"
|
||||||
|
},
|
||||||
|
"label.unlisted": {
|
||||||
|
"message": "Unlisted"
|
||||||
|
},
|
||||||
|
"label.visibility": {
|
||||||
|
"message": "Visibility"
|
||||||
|
},
|
||||||
"notification.error.title": {
|
"notification.error.title": {
|
||||||
"message": "An error occurred"
|
"message": "An error occurred"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,10 +3,10 @@
|
|||||||
<ModalConfirm
|
<ModalConfirm
|
||||||
v-if="auth.user && auth.user.id === creator.id"
|
v-if="auth.user && auth.user.id === creator.id"
|
||||||
ref="deleteModal"
|
ref="deleteModal"
|
||||||
title="Are you sure you want to delete this collection?"
|
:title="formatMessage(messages.deleteModalTitle)"
|
||||||
description="This will remove this collection forever. This action cannot be undone."
|
:description="formatMessage(messages.deleteModalDescription)"
|
||||||
:has-to-type="false"
|
:has-to-type="false"
|
||||||
proceed-label="Delete"
|
:proceed-label="formatMessage(commonMessages.deleteLabel)"
|
||||||
@proceed="deleteCollection()"
|
@proceed="deleteCollection()"
|
||||||
/>
|
/>
|
||||||
<div class="normal-page">
|
<div class="normal-page">
|
||||||
@ -16,16 +16,16 @@
|
|||||||
<template v-if="canEdit && isEditing === false">
|
<template v-if="canEdit && isEditing === false">
|
||||||
<Button @click="isEditing = true">
|
<Button @click="isEditing = true">
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
Edit
|
{{ formatMessage(commonMessages.editButton) }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button id="delete-collection" @click="() => $refs.deleteModal.show()">
|
<Button id="delete-collection" @click="() => $refs.deleteModal.show()">
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Delete
|
{{ formatMessage(commonMessages.deleteLabel) }}
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="canEdit && isEditing === true">
|
<template v-else-if="canEdit && isEditing === true">
|
||||||
<PopoutMenu class="btn" position="bottom" direction="right">
|
<PopoutMenu class="btn" position="bottom" direction="right">
|
||||||
<EditIcon /> Edit icon
|
<EditIcon /> {{ formatMessage(messages.editIconButton) }}
|
||||||
<template #menu>
|
<template #menu>
|
||||||
<span class="icon-edit-menu">
|
<span class="icon-edit-menu">
|
||||||
<FileInput
|
<FileInput
|
||||||
@ -39,7 +39,7 @@
|
|||||||
@change="showPreviewImage"
|
@change="showPreviewImage"
|
||||||
>
|
>
|
||||||
<UploadIcon />
|
<UploadIcon />
|
||||||
Upload icon
|
{{ formatMessage(messages.uploadIconButton) }}
|
||||||
</FileInput>
|
</FileInput>
|
||||||
<Button
|
<Button
|
||||||
v-if="!deletedIcon && (previewImage || collection.icon_url)"
|
v-if="!deletedIcon && (previewImage || collection.icon_url)"
|
||||||
@ -53,7 +53,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Delete icon
|
{{ formatMessage(messages.deleteIconButton) }}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@ -70,17 +70,21 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<label for="collection-title">
|
<label for="collection-title">
|
||||||
<span class="label__title"> Title </span>
|
<span class="label__title"> {{ formatMessage(commonMessages.titleLabel) }} </span>
|
||||||
</label>
|
</label>
|
||||||
<input id="collection-title" v-model="name" maxlength="255" type="text" />
|
<input id="collection-title" v-model="name" maxlength="255" type="text" />
|
||||||
<label for="collection-description">
|
<label for="collection-description">
|
||||||
<span class="label__title"> Description </span>
|
<span class="label__title">
|
||||||
|
{{ formatMessage(commonMessages.descriptionLabel) }}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="textarea-wrapper">
|
<div class="textarea-wrapper">
|
||||||
<textarea id="collection-description" v-model="summary" maxlength="255" />
|
<textarea id="collection-description" v-model="summary" maxlength="255" />
|
||||||
</div>
|
</div>
|
||||||
<label for="visibility">
|
<label for="visibility">
|
||||||
<span class="label__title"> Visibility </span>
|
<span class="label__title">
|
||||||
|
{{ formatMessage(commonMessages.visibilityLabel) }}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
id="visibility"
|
id="visibility"
|
||||||
@ -90,8 +94,8 @@
|
|||||||
:multiple="false"
|
:multiple="false"
|
||||||
:display-name="
|
:display-name="
|
||||||
(s) => {
|
(s) => {
|
||||||
if (s === 'listed') return 'Public'
|
if (s === 'listed') return formatMessage(commonMessages.publicLabel)
|
||||||
return capitalizeString(s)
|
return formatMessage(commonMessages[`${s}Label`])
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
:searchable="false"
|
:searchable="false"
|
||||||
@ -100,11 +104,11 @@
|
|||||||
<div class="push-right input-group">
|
<div class="push-right input-group">
|
||||||
<Button @click="isEditing = false">
|
<Button @click="isEditing = false">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
Cancel
|
{{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" @click="saveChanges()">
|
<Button color="primary" @click="saveChanges()">
|
||||||
<SaveIcon />
|
<SaveIcon />
|
||||||
Save
|
{{ formatMessage(commonMessages.saveButton) }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -117,7 +121,9 @@
|
|||||||
<h1 class="title">{{ collection.name }}</h1>
|
<h1 class="title">{{ collection.name }}</h1>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span class="collection-label"><BoxIcon /> Collection</span>
|
<span class="collection-label">
|
||||||
|
<BoxIcon /> {{ formatMessage(messages.collectionLabel) }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="collection-info">
|
<div class="collection-info">
|
||||||
@ -131,25 +137,25 @@
|
|||||||
<template v-if="collection.status === 'listed'">
|
<template v-if="collection.status === 'listed'">
|
||||||
<WorldIcon class="primary-stat__icon" aria-hidden="true" />
|
<WorldIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div class="primary-stat__text">
|
<div class="primary-stat__text">
|
||||||
<strong> Public </strong>
|
<strong> {{ formatMessage(commonMessages.publicLabel) }} </strong>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="collection.status === 'unlisted'">
|
<template v-else-if="collection.status === 'unlisted'">
|
||||||
<LinkIcon class="primary-stat__icon" aria-hidden="true" />
|
<LinkIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div class="primary-stat__text">
|
<div class="primary-stat__text">
|
||||||
<strong> Unlisted </strong>
|
<strong> {{ formatMessage(commonMessages.unlistedLabel) }} </strong>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="collection.status === 'private'">
|
<template v-else-if="collection.status === 'private'">
|
||||||
<LockIcon class="primary-stat__icon" aria-hidden="true" />
|
<LockIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div class="primary-stat__text">
|
<div class="primary-stat__text">
|
||||||
<strong> Private </strong>
|
<strong> {{ formatMessage(commonMessages.privateLabel) }} </strong>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="collection.status === 'rejected'">
|
<template v-else-if="collection.status === 'rejected'">
|
||||||
<XIcon class="primary-stat__icon" aria-hidden="true" />
|
<XIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div class="primary-stat__text">
|
<div class="primary-stat__text">
|
||||||
<strong> Rejected </strong>
|
<strong> {{ formatMessage(commonMessages.rejectedLabel) }} </strong>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -158,35 +164,57 @@
|
|||||||
<div class="primary-stat">
|
<div class="primary-stat">
|
||||||
<LibraryIcon class="primary-stat__icon" aria-hidden="true" />
|
<LibraryIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div v-if="projects" class="primary-stat__text">
|
<div v-if="projects" class="primary-stat__text">
|
||||||
|
<IntlFormatted
|
||||||
|
:message-id="messages.projectsCountLabel"
|
||||||
|
:values="{ count: formatCompactNumber(projects.length || 0) }"
|
||||||
|
>
|
||||||
|
<template #stat="{ children }">
|
||||||
<span class="primary-stat__counter">
|
<span class="primary-stat__counter">
|
||||||
{{ $formatNumber(projects?.length || 0) }}
|
<component :is="() => normalizeChildren(children)" />
|
||||||
</span>
|
</span>
|
||||||
project<span v-if="projects?.length !== 1">s</span>
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="metadata-item">
|
<div class="metadata-item">
|
||||||
<div
|
<div
|
||||||
v-tooltip="$dayjs(collection.created).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="
|
||||||
|
formatMessage(commonMessages.dateAtTimeTooltip, {
|
||||||
|
date: new Date(collection.created),
|
||||||
|
time: new Date(collection.created),
|
||||||
|
})
|
||||||
|
"
|
||||||
class="date"
|
class="date"
|
||||||
>
|
>
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
<label>
|
<label>
|
||||||
Created
|
{{
|
||||||
{{ fromNow(collection.created) }}
|
formatMessage(messages.createdAtLabel, {
|
||||||
|
ago: formatRelativeTime(collection.created),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="collection.id !== 'following'" class="metadata-item">
|
<div v-if="collection.id !== 'following'" class="metadata-item">
|
||||||
<div
|
<div
|
||||||
v-tooltip="$dayjs(collection.updated).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="
|
||||||
|
formatMessage(commonMessages.dateAtTimeTooltip, {
|
||||||
|
date: new Date(collection.updated),
|
||||||
|
time: new Date(collection.updated),
|
||||||
|
})
|
||||||
|
"
|
||||||
class="date"
|
class="date"
|
||||||
>
|
>
|
||||||
<UpdatedIcon />
|
<UpdatedIcon />
|
||||||
<label>
|
<label>
|
||||||
Updated
|
{{
|
||||||
{{ fromNow(collection.updated) }}
|
formatMessage(messages.updatedAtLabel, {
|
||||||
|
ago: formatRelativeTime(collection.updated),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -195,7 +223,7 @@
|
|||||||
<hr class="card-divider" />
|
<hr class="card-divider" />
|
||||||
|
|
||||||
<div class="collection-info">
|
<div class="collection-info">
|
||||||
<h2 class="card-header">Curated by</h2>
|
<h2 class="card-header">{{ formatMessage(messages.curatedByLabel) }}</h2>
|
||||||
<div class="metadata-item">
|
<div class="metadata-item">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
class="team-member columns button-transparent"
|
class="team-member columns button-transparent"
|
||||||
@ -205,7 +233,7 @@
|
|||||||
|
|
||||||
<div class="member-info">
|
<div class="member-info">
|
||||||
<p class="name">{{ creator.username }}</p>
|
<p class="name">{{ creator.username }}</p>
|
||||||
<p class="role">Owner</p>
|
<p class="role">{{ formatMessage(messages.ownerLabel) }}</p>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
@ -304,7 +332,7 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Remove project
|
{{ formatMessage(messages.removeProjectButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="collection.id === 'following'"
|
v-if="collection.id === 'following'"
|
||||||
@ -312,18 +340,22 @@
|
|||||||
@click="unfollowProject(project)"
|
@click="unfollowProject(project)"
|
||||||
>
|
>
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Unfollow project
|
{{ formatMessage(messages.unfollowProjectButton) }}
|
||||||
</button>
|
</button>
|
||||||
</ProjectCard>
|
</ProjectCard>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="error">
|
<div v-else class="error">
|
||||||
<UpToDate class="icon" /><br />
|
<UpToDate class="icon" /><br />
|
||||||
<span v-if="auth.user && auth.user.id === creator.id" class="text">
|
<span v-if="auth.user && auth.user.id === creator.id" class="preserve-lines text">
|
||||||
You don't have any projects.<br />
|
<IntlFormatted :message-id="messages.noProjectsAuthLabel">
|
||||||
Would you like to
|
<template #create-link="{ children }">
|
||||||
<a class="link" @click.prevent="$router.push('/mods')"> add one</a>?
|
<a class="link" @click.prevent="$router.push('/mods')">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text">This collection has no projects!</span>
|
<span v-else class="text">{{ formatMessage(messages.noProjectsLabel) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -332,7 +364,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
capitalizeString,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
CalendarIcon,
|
CalendarIcon,
|
||||||
@ -364,6 +395,89 @@ import ProjectCard from '~/components/ui/ProjectCard.vue'
|
|||||||
|
|
||||||
const vintl = useVIntl()
|
const vintl = useVIntl()
|
||||||
const { formatMessage } = vintl
|
const { formatMessage } = vintl
|
||||||
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatCompactNumber = useCompactNumber()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
collectionDescription: {
|
||||||
|
id: 'collection.description',
|
||||||
|
defaultMessage: '{description} - View the collection {name} by {username} on Modrinth',
|
||||||
|
},
|
||||||
|
collectionLabel: {
|
||||||
|
id: 'collection.label.collection',
|
||||||
|
defaultMessage: 'Collection',
|
||||||
|
},
|
||||||
|
collectionTitle: {
|
||||||
|
id: 'collection.title',
|
||||||
|
defaultMessage: '{name} - Collection',
|
||||||
|
},
|
||||||
|
editIconButton: {
|
||||||
|
id: 'collection.button.edit-icon',
|
||||||
|
defaultMessage: 'Edit icon',
|
||||||
|
},
|
||||||
|
deleteIconButton: {
|
||||||
|
id: 'collection.button.delete-icon',
|
||||||
|
defaultMessage: 'Delete icon',
|
||||||
|
},
|
||||||
|
createdAtLabel: {
|
||||||
|
id: 'collection.label.created-at',
|
||||||
|
defaultMessage: 'Created {ago}',
|
||||||
|
},
|
||||||
|
collectionNotFoundError: {
|
||||||
|
id: 'collection.error.not-found',
|
||||||
|
defaultMessage: 'Collection not found',
|
||||||
|
},
|
||||||
|
curatedByLabel: {
|
||||||
|
id: 'collection.label.curated-by',
|
||||||
|
defaultMessage: 'Curated by',
|
||||||
|
},
|
||||||
|
deleteModalDescription: {
|
||||||
|
id: 'collection.delete-modal.description',
|
||||||
|
defaultMessage: 'This will remove this collection forever. This action cannot be undone.',
|
||||||
|
},
|
||||||
|
deleteModalTitle: {
|
||||||
|
id: 'collection.delete-modal.title',
|
||||||
|
defaultMessage: 'Are you sure you want to delete this collection?',
|
||||||
|
},
|
||||||
|
followingCollectionDescription: {
|
||||||
|
id: 'collection.description.following',
|
||||||
|
defaultMessage: "Auto-generated collection of all the projects you're following.",
|
||||||
|
},
|
||||||
|
noProjectsLabel: {
|
||||||
|
id: 'collection.label.no-projects',
|
||||||
|
defaultMessage: 'This collection has no projects!',
|
||||||
|
},
|
||||||
|
noProjectsAuthLabel: {
|
||||||
|
id: 'collection.label.no-projects-auth',
|
||||||
|
defaultMessage:
|
||||||
|
"You don't have any projects.\nWould you like to <create-link>add one</create-link>?",
|
||||||
|
},
|
||||||
|
ownerLabel: {
|
||||||
|
id: 'collection.label.owner',
|
||||||
|
defaultMessage: 'Owner',
|
||||||
|
},
|
||||||
|
projectsCountLabel: {
|
||||||
|
id: 'collection.label.projects-count',
|
||||||
|
defaultMessage:
|
||||||
|
'{count, plural, one {<stat>{count}</stat> project} other {<stat>{count}</stat> projects}}',
|
||||||
|
},
|
||||||
|
removeProjectButton: {
|
||||||
|
id: 'collection.button.remove-project',
|
||||||
|
defaultMessage: 'Remove project',
|
||||||
|
},
|
||||||
|
unfollowProjectButton: {
|
||||||
|
id: 'collection.button.unfollow-project',
|
||||||
|
defaultMessage: 'Unfollow project',
|
||||||
|
},
|
||||||
|
updatedAtLabel: {
|
||||||
|
id: 'collection.label.updated-at',
|
||||||
|
defaultMessage: 'Updated {ago}',
|
||||||
|
},
|
||||||
|
uploadIconButton: {
|
||||||
|
id: 'collection.button.upload-icon',
|
||||||
|
defaultMessage: 'Upload icon',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const data = useNuxtApp()
|
const data = useNuxtApp()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -388,8 +502,8 @@ try {
|
|||||||
collection = ref({
|
collection = ref({
|
||||||
id: 'following',
|
id: 'following',
|
||||||
icon_url: 'https://cdn.modrinth.com/follow-collection.png',
|
icon_url: 'https://cdn.modrinth.com/follow-collection.png',
|
||||||
name: 'Followed projects',
|
name: formatMessage(commonMessages.followedProjectsLabel),
|
||||||
description: "Auto-generated collection of all the projects you're following.",
|
description: formatMessage(messages.followingCollectionDescription),
|
||||||
status: 'private',
|
status: 'private',
|
||||||
user: auth.value.user.id,
|
user: auth.value.user.id,
|
||||||
created: auth.value.user.created,
|
created: auth.value.user.created,
|
||||||
@ -426,7 +540,7 @@ try {
|
|||||||
throw createError({
|
throw createError({
|
||||||
fatal: true,
|
fatal: true,
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
message: 'Collection not found',
|
message: formatMessage(messages.collectionNotFoundError),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,16 +548,22 @@ if (!collection.value) {
|
|||||||
throw createError({
|
throw createError({
|
||||||
fatal: true,
|
fatal: true,
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
message: 'Collection not found',
|
message: formatMessage(messages.collectionNotFoundError),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = `${collection.value.name} - Collection`
|
const title = computed(() =>
|
||||||
const description = `${collection.value.description} - View the collection ${collection.value.description} by ${creator.value.username} on Modrinth`
|
formatMessage(messages.collectionTitle, { name: collection.value.name })
|
||||||
|
)
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title,
|
title,
|
||||||
description,
|
description: () =>
|
||||||
|
formatMessage(messages.collectionDescription, {
|
||||||
|
name: collection.value.name,
|
||||||
|
description: collection.value.description,
|
||||||
|
username: creator.value.username,
|
||||||
|
}),
|
||||||
ogTitle: title,
|
ogTitle: title,
|
||||||
ogDescription: collection.value.description,
|
ogDescription: collection.value.description,
|
||||||
ogImage: collection.value.icon_url ?? 'https://cdn.modrinth.com/placeholder.png',
|
ogImage: collection.value.icon_url ?? 'https://cdn.modrinth.com/placeholder.png',
|
||||||
@ -526,7 +646,7 @@ async function saveChanges() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
group: 'main',
|
group: 'main',
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err,
|
text: err,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
@ -546,7 +666,7 @@ async function deleteCollection() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
addNotification({
|
addNotification({
|
||||||
group: 'main',
|
group: 'main',
|
||||||
title: 'An error occurred',
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
text: err.data.description,
|
text: err.data.description,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -24,7 +24,10 @@
|
|||||||
<NavStackItem v-if="true" link="/dashboard/organizations" label="Organizations">
|
<NavStackItem v-if="true" link="/dashboard/organizations" label="Organizations">
|
||||||
<OrganizationIcon />
|
<OrganizationIcon />
|
||||||
</NavStackItem>
|
</NavStackItem>
|
||||||
<NavStackItem link="/dashboard/collections" label="Collections">
|
<NavStackItem
|
||||||
|
link="/dashboard/collections"
|
||||||
|
:label="formatMessage(commonMessages.collectionsLabel)"
|
||||||
|
>
|
||||||
<LibraryIcon />
|
<LibraryIcon />
|
||||||
</NavStackItem>
|
</NavStackItem>
|
||||||
<NavStackItem link="/dashboard/revenue" label="Revenue">
|
<NavStackItem link="/dashboard/revenue" label="Revenue">
|
||||||
@ -50,6 +53,8 @@ import ReportIcon from '~/assets/images/utils/report.svg'
|
|||||||
import NotificationsIcon from '~/assets/images/utils/bell.svg'
|
import NotificationsIcon from '~/assets/images/utils/bell.svg'
|
||||||
import OrganizationIcon from '~/assets/images/utils/organization.svg'
|
import OrganizationIcon from '~/assets/images/utils/organization.svg'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="universal-card">
|
<div class="universal-card">
|
||||||
<CollectionCreateModal ref="modal_creation" />
|
<CollectionCreateModal ref="modal_creation" />
|
||||||
<h2>Collections</h2>
|
<h2>{{ formatMessage(commonMessages.collectionsLabel) }}</h2>
|
||||||
<div class="search-row">
|
<div class="search-row">
|
||||||
<div class="iconified-input">
|
<div class="iconified-input">
|
||||||
<label for="search-input" hidden>Search your collections</label>
|
<label for="search-input" hidden>{{ formatMessage(messages.searchInputLabel) }}</label>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
<input id="search-input" v-model="filterQuery" type="text" />
|
<input id="search-input" v-model="filterQuery" type="text" />
|
||||||
<Button v-if="filterQuery" class="r-btn" @click="() => (filterQuery = '')">
|
<Button v-if="filterQuery" class="r-btn" @click="() => (filterQuery = '')">
|
||||||
@ -12,7 +12,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button color="primary" @click="$refs.modal_creation.show()">
|
<Button color="primary" @click="$refs.modal_creation.show()">
|
||||||
<PlusIcon /> Create new
|
<PlusIcon /> {{ formatMessage(messages.createNewButton) }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="collections-grid">
|
<div class="collections-grid">
|
||||||
@ -23,13 +23,22 @@
|
|||||||
>
|
>
|
||||||
<Avatar src="https://cdn.modrinth.com/follow-collection.png" class="icon" />
|
<Avatar src="https://cdn.modrinth.com/follow-collection.png" class="icon" />
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<span class="title">Followed projects</span>
|
<span class="title">{{ formatMessage(commonMessages.followedProjectsLabel) }}</span>
|
||||||
<span class="description">
|
<span class="description">
|
||||||
Auto-generated collection of all the projects you're following.
|
{{ formatMessage(messages.followingCollectionDescription) }}
|
||||||
</span>
|
</span>
|
||||||
<div class="stat-bar">
|
<div class="stat-bar">
|
||||||
<div class="stats"><BoxIcon /> {{ user.follows.length }} projects</div>
|
<div class="stats">
|
||||||
<div class="stats"><LockIcon /> <span> Private </span></div>
|
<BoxIcon />
|
||||||
|
{{
|
||||||
|
formatMessage(messages.projectsCountLabel, {
|
||||||
|
count: formatCompactNumber(user.follows.length),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="stats">
|
||||||
|
<LockIcon /> <span> {{ formatMessage(commonMessages.privateLabel) }} </span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
@ -46,23 +55,30 @@
|
|||||||
{{ collection.description }}
|
{{ collection.description }}
|
||||||
</span>
|
</span>
|
||||||
<div class="stat-bar">
|
<div class="stat-bar">
|
||||||
<div class="stats"><BoxIcon /> {{ collection.projects?.length || 0 }} projects</div>
|
<div class="stats">
|
||||||
|
<BoxIcon />
|
||||||
|
{{
|
||||||
|
formatMessage(messages.projectsCountLabel, {
|
||||||
|
count: formatCompactNumber(collection.projects?.length || 0),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<template v-if="collection.status === 'listed'">
|
<template v-if="collection.status === 'listed'">
|
||||||
<WorldIcon />
|
<WorldIcon />
|
||||||
<span> Public </span>
|
<span> {{ formatMessage(commonMessages.publicLabel) }} </span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="collection.status === 'unlisted'">
|
<template v-else-if="collection.status === 'unlisted'">
|
||||||
<LinkIcon />
|
<LinkIcon />
|
||||||
<span> Unlisted </span>
|
<span> {{ formatMessage(commonMessages.unlistedLabel) }} </span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="collection.status === 'private'">
|
<template v-else-if="collection.status === 'private'">
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
<span> Private </span>
|
<span> {{ formatMessage(commonMessages.privateLabel) }} </span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="collection.status === 'rejected'">
|
<template v-else-if="collection.status === 'rejected'">
|
||||||
<XIcon />
|
<XIcon />
|
||||||
<span> Rejected </span>
|
<span> {{ formatMessage(commonMessages.rejectedLabel) }} </span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -76,12 +92,38 @@ import { Avatar, BoxIcon, SearchIcon, XIcon, Button, PlusIcon, LinkIcon, LockIco
|
|||||||
import WorldIcon from '~/assets/images/utils/world.svg'
|
import WorldIcon from '~/assets/images/utils/world.svg'
|
||||||
import CollectionCreateModal from '~/components/ui/CollectionCreateModal.vue'
|
import CollectionCreateModal from '~/components/ui/CollectionCreateModal.vue'
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatCompactNumber = useCompactNumber()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
createNewButton: {
|
||||||
|
id: 'dashboard.collections.button.create-new',
|
||||||
|
defaultMessage: 'Create new',
|
||||||
|
},
|
||||||
|
collectionsLongTitle: {
|
||||||
|
id: 'dashboard.collections.long-title',
|
||||||
|
defaultMessage: 'Your collections',
|
||||||
|
},
|
||||||
|
followingCollectionDescription: {
|
||||||
|
id: 'collection.description.following',
|
||||||
|
defaultMessage: "Auto-generated collection of all the projects you're following.",
|
||||||
|
},
|
||||||
|
projectsCountLabel: {
|
||||||
|
id: 'dashboard.collections.label.projects-count',
|
||||||
|
defaultMessage: '{count, plural, one {{count} project} other {{count} projects}}',
|
||||||
|
},
|
||||||
|
searchInputLabel: {
|
||||||
|
id: 'dashboard.collections.label.search-input',
|
||||||
|
defaultMessage: 'Search your collections',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
})
|
})
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Your collections - Modrinth',
|
title: () => `${formatMessage(messages.collectionsLongTitle)} - Modrinth`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const user = await useUser()
|
const user = await useUser()
|
||||||
|
|||||||
@ -7,6 +7,10 @@ export const commonMessages = defineMessages({
|
|||||||
id: 'button.cancel',
|
id: 'button.cancel',
|
||||||
defaultMessage: 'Cancel',
|
defaultMessage: 'Cancel',
|
||||||
},
|
},
|
||||||
|
collectionsLabel: {
|
||||||
|
id: 'label.collections',
|
||||||
|
defaultMessage: 'Collections',
|
||||||
|
},
|
||||||
continueButton: {
|
continueButton: {
|
||||||
id: 'button.continue',
|
id: 'button.continue',
|
||||||
defaultMessage: 'Continue',
|
defaultMessage: 'Continue',
|
||||||
@ -15,10 +19,26 @@ export const commonMessages = defineMessages({
|
|||||||
id: 'tooltip.date-at-time',
|
id: 'tooltip.date-at-time',
|
||||||
defaultMessage: '{date, date, long} at {time, time, short}',
|
defaultMessage: '{date, date, long} at {time, time, short}',
|
||||||
},
|
},
|
||||||
|
deleteLabel: {
|
||||||
|
id: 'label.delete',
|
||||||
|
defaultMessage: 'Delete',
|
||||||
|
},
|
||||||
|
descriptionLabel: {
|
||||||
|
id: 'label.description',
|
||||||
|
defaultMessage: 'Description',
|
||||||
|
},
|
||||||
editButton: {
|
editButton: {
|
||||||
id: 'button.edit',
|
id: 'button.edit',
|
||||||
defaultMessage: 'Edit',
|
defaultMessage: 'Edit',
|
||||||
},
|
},
|
||||||
|
errorNotificationTitle: {
|
||||||
|
id: 'notification.error.title',
|
||||||
|
defaultMessage: 'An error occurred',
|
||||||
|
},
|
||||||
|
followedProjectsLabel: {
|
||||||
|
id: 'label.followed-projects',
|
||||||
|
defaultMessage: 'Followed projects',
|
||||||
|
},
|
||||||
galleryInputView: {
|
galleryInputView: {
|
||||||
id: 'input.view.gallery',
|
id: 'input.view.gallery',
|
||||||
defaultMessage: 'Gallery view',
|
defaultMessage: 'Gallery view',
|
||||||
@ -31,12 +51,32 @@ export const commonMessages = defineMessages({
|
|||||||
id: 'input.view.list',
|
id: 'input.view.list',
|
||||||
defaultMessage: 'List view',
|
defaultMessage: 'List view',
|
||||||
},
|
},
|
||||||
errorNotificationTitle: {
|
privateLabel: {
|
||||||
id: 'notification.error.title',
|
id: 'collection.label.private',
|
||||||
defaultMessage: 'An error occurred',
|
defaultMessage: 'Private',
|
||||||
|
},
|
||||||
|
publicLabel: {
|
||||||
|
id: 'label.public',
|
||||||
|
defaultMessage: 'Public',
|
||||||
|
},
|
||||||
|
rejectedLabel: {
|
||||||
|
id: 'label.rejected',
|
||||||
|
defaultMessage: 'Rejected',
|
||||||
},
|
},
|
||||||
saveButton: {
|
saveButton: {
|
||||||
id: 'button.save',
|
id: 'button.save',
|
||||||
defaultMessage: 'Save',
|
defaultMessage: 'Save',
|
||||||
},
|
},
|
||||||
|
titleLabel: {
|
||||||
|
id: 'label.title',
|
||||||
|
defaultMessage: 'Title',
|
||||||
|
},
|
||||||
|
unlistedLabel: {
|
||||||
|
id: 'label.unlisted',
|
||||||
|
defaultMessage: 'Unlisted',
|
||||||
|
},
|
||||||
|
visibilityLabel: {
|
||||||
|
id: 'label.visibility',
|
||||||
|
defaultMessage: 'Visibility',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user