You've already forked AstralRinth
forked from didirus/AstralRinth
Add translations for profile page (#1340)
Co-authored-by: Sasha Sorokin <10401817+brawaru@users.noreply.github.com>
This commit is contained in:
@@ -33,3 +33,7 @@ body {
|
|||||||
position: absolute !important;
|
position: absolute !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preserve-lines {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"button.cancel": {
|
||||||
|
"message": "Cancel"
|
||||||
|
},
|
||||||
|
"button.edit": {
|
||||||
|
"message": "Edit"
|
||||||
|
},
|
||||||
|
"button.save": {
|
||||||
|
"message": "Save"
|
||||||
|
},
|
||||||
"frog": {
|
"frog": {
|
||||||
"message": "You've been frogged! 🐸"
|
"message": "You've been frogged! 🐸"
|
||||||
},
|
},
|
||||||
@@ -14,6 +23,102 @@
|
|||||||
"frog.title": {
|
"frog.title": {
|
||||||
"message": "Frog"
|
"message": "Frog"
|
||||||
},
|
},
|
||||||
|
"input.view.gallery": {
|
||||||
|
"message": "Gallery view"
|
||||||
|
},
|
||||||
|
"input.view.grid": {
|
||||||
|
"message": "Grid view"
|
||||||
|
},
|
||||||
|
"input.view.list": {
|
||||||
|
"message": "List view"
|
||||||
|
},
|
||||||
|
"notification.error.title": {
|
||||||
|
"message": "An error occurred"
|
||||||
|
},
|
||||||
|
"profile.button.manage-projects": {
|
||||||
|
"message": "Manage projects"
|
||||||
|
},
|
||||||
|
"profile.button.report": {
|
||||||
|
"message": "Report"
|
||||||
|
},
|
||||||
|
"profile.error.not-found": {
|
||||||
|
"message": "User not found"
|
||||||
|
},
|
||||||
|
"profile.input.upload-avatar": {
|
||||||
|
"message": "Upload avatar"
|
||||||
|
},
|
||||||
|
"profile.joined-at": {
|
||||||
|
"message": "Joined {ago}"
|
||||||
|
},
|
||||||
|
"profile.joined-at.tooltip": {
|
||||||
|
"message": "{date, date, long} at {time, time, short}"
|
||||||
|
},
|
||||||
|
"profile.label.edit-bio": {
|
||||||
|
"message": "Bio"
|
||||||
|
},
|
||||||
|
"profile.label.edit-username": {
|
||||||
|
"message": "Username"
|
||||||
|
},
|
||||||
|
"profile.label.no-projects": {
|
||||||
|
"message": "This user has no projects!"
|
||||||
|
},
|
||||||
|
"profile.label.no-projects-auth": {
|
||||||
|
"message": "You don't have any projects.\nWould you like to <create-link>create one</create-link>?"
|
||||||
|
},
|
||||||
|
"profile.meta.description": {
|
||||||
|
"message": "Download {username}'s projects on Modrinth"
|
||||||
|
},
|
||||||
|
"profile.meta.description-with-bio": {
|
||||||
|
"message": "{bio} - Download {username}'s projects on Modrinth"
|
||||||
|
},
|
||||||
|
"profile.stats.downloads": {
|
||||||
|
"message": "{count, plural, one {<stat>{count}</stat> download} other {<stat>{count}</stat> downloads}}"
|
||||||
|
},
|
||||||
|
"profile.stats.projects-followers": {
|
||||||
|
"message": "{count, plural, one {<stat>{count}</stat> follower} other {<stat>{count}</stat> followers}} of projects"
|
||||||
|
},
|
||||||
|
"profile.user-id": {
|
||||||
|
"message": "User ID: {id}"
|
||||||
|
},
|
||||||
|
"project-type.all": {
|
||||||
|
"message": "All"
|
||||||
|
},
|
||||||
|
"project-type.datapack.plural": {
|
||||||
|
"message": "Data Packs"
|
||||||
|
},
|
||||||
|
"project-type.datapack.singular": {
|
||||||
|
"message": "Data Pack"
|
||||||
|
},
|
||||||
|
"project-type.mod.plural": {
|
||||||
|
"message": "Mods"
|
||||||
|
},
|
||||||
|
"project-type.mod.singular": {
|
||||||
|
"message": "Mod"
|
||||||
|
},
|
||||||
|
"project-type.modpack.plural": {
|
||||||
|
"message": "Modpacks"
|
||||||
|
},
|
||||||
|
"project-type.modpack.singular": {
|
||||||
|
"message": "Modpack"
|
||||||
|
},
|
||||||
|
"project-type.plugin.plural": {
|
||||||
|
"message": "Plugins"
|
||||||
|
},
|
||||||
|
"project-type.plugin.singular": {
|
||||||
|
"message": "Plugin"
|
||||||
|
},
|
||||||
|
"project-type.resourcepack.plural": {
|
||||||
|
"message": "Resource Packs"
|
||||||
|
},
|
||||||
|
"project-type.resourcepack.singular": {
|
||||||
|
"message": "Resource Pack"
|
||||||
|
},
|
||||||
|
"project-type.shader.plural": {
|
||||||
|
"message": "Shaders"
|
||||||
|
},
|
||||||
|
"project-type.shader.singular": {
|
||||||
|
"message": "Shader"
|
||||||
|
},
|
||||||
"settings.language.categories.auto": {
|
"settings.language.categories.auto": {
|
||||||
"message": "Automatic"
|
"message": "Automatic"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,9 +38,9 @@
|
|||||||
v-if="isEditing"
|
v-if="isEditing"
|
||||||
:max-size="262144"
|
:max-size="262144"
|
||||||
:show-icon="true"
|
:show-icon="true"
|
||||||
|
:prompt="formatMessage(messages.profileUploadAvatarInput)"
|
||||||
accept="image/png,image/jpeg,image/gif,image/webp"
|
accept="image/png,image/jpeg,image/gif,image/webp"
|
||||||
class="choose-image iconified-button"
|
class="choose-image iconified-button"
|
||||||
prompt="Upload avatar"
|
|
||||||
@change="showPreviewImage"
|
@change="showPreviewImage"
|
||||||
>
|
>
|
||||||
<UploadIcon />
|
<UploadIcon />
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
@click="isEditing = true"
|
@click="isEditing = true"
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
Edit
|
{{ formatMessage(commonMessages.editButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else-if="auth.user"
|
v-else-if="auth.user"
|
||||||
@@ -59,18 +59,26 @@
|
|||||||
@click="$refs.modal_report.show()"
|
@click="$refs.modal_report.show()"
|
||||||
>
|
>
|
||||||
<ReportIcon aria-hidden="true" />
|
<ReportIcon aria-hidden="true" />
|
||||||
Report
|
{{ formatMessage(messages.profileReportButton) }}
|
||||||
</button>
|
</button>
|
||||||
<nuxt-link v-else class="iconified-button" to="/auth/sign-in">
|
<nuxt-link v-else class="iconified-button" to="/auth/sign-in">
|
||||||
<ReportIcon aria-hidden="true" />
|
<ReportIcon aria-hidden="true" />
|
||||||
Report
|
{{ formatMessage(messages.profileReportButton) }}
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="isEditing">
|
<template v-if="isEditing">
|
||||||
<div class="inputs universal-labels">
|
<div class="inputs universal-labels">
|
||||||
<label for="user-username"><span class="label__title">Username</span></label>
|
<label for="user-username">
|
||||||
|
<span class="label__title">
|
||||||
|
{{ formatMessage(messages.profileEditUsernameLabel) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<input id="user-username" v-model="user.username" maxlength="39" type="text" />
|
<input id="user-username" v-model="user.username" maxlength="39" type="text" />
|
||||||
<label for="user-bio"><span class="label__title">Bio</span></label>
|
<label for="user-bio">
|
||||||
|
<span class="label__title">
|
||||||
|
{{ formatMessage(messages.profileEditBioLabel) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<div class="textarea-wrapper">
|
<div class="textarea-wrapper">
|
||||||
<textarea id="user-bio" v-model="user.bio" maxlength="160" />
|
<textarea id="user-bio" v-model="user.bio" maxlength="160" />
|
||||||
</div>
|
</div>
|
||||||
@@ -87,10 +95,10 @@
|
|||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<CrossIcon /> Cancel
|
<CrossIcon /> {{ formatMessage(commonMessages.cancelButton) }}
|
||||||
</button>
|
</button>
|
||||||
<button class="iconified-button brand-button" @click="saveChanges">
|
<button class="iconified-button brand-button" @click="saveChanges">
|
||||||
<SaveIcon /> Save
|
<SaveIcon /> {{ formatMessage(commonMessages.saveButton) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -104,30 +112,59 @@
|
|||||||
<div class="primary-stat">
|
<div class="primary-stat">
|
||||||
<DownloadIcon class="primary-stat__icon" aria-hidden="true" />
|
<DownloadIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div class="primary-stat__text">
|
<div class="primary-stat__text">
|
||||||
<span class="primary-stat__counter">{{ sumDownloads }}</span>
|
<IntlFormatted
|
||||||
downloads
|
:message-id="messages.profileDownloadsStats"
|
||||||
|
:values="{ count: formatCompactNumber(sumDownloads) }"
|
||||||
|
>
|
||||||
|
<template #stat="{ children }">
|
||||||
|
<span class="primary-stat__counter">
|
||||||
|
<component :is="() => normalizeChildren(children)" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="primary-stat">
|
<div class="primary-stat">
|
||||||
<HeartIcon class="primary-stat__icon" aria-hidden="true" />
|
<HeartIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
<div class="primary-stat__text">
|
<div class="primary-stat__text">
|
||||||
<span class="primary-stat__counter">{{ sumFollows }}</span>
|
<IntlFormatted
|
||||||
followers of projects
|
:message-id="messages.profileProjectsFollowersStats"
|
||||||
|
:values="{ count: formatCompactNumber(sumFollows) }"
|
||||||
|
>
|
||||||
|
<template #stat="{ children }">
|
||||||
|
<span class="primary-stat__counter">
|
||||||
|
<component :is="() => normalizeChildren(children)" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats-block__item secondary-stat">
|
<div class="stats-block__item secondary-stat">
|
||||||
<SunriseIcon class="secondary-stat__icon" aria-hidden="true" />
|
<SunriseIcon class="secondary-stat__icon" aria-hidden="true" />
|
||||||
<span
|
<span
|
||||||
v-tooltip="$dayjs(user.created).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="
|
||||||
|
formatMessage(messages.profileJoinedAtTooltip, {
|
||||||
|
date: new Date(user.created),
|
||||||
|
time: new Date(user.created),
|
||||||
|
})
|
||||||
|
"
|
||||||
class="secondary-stat__text date"
|
class="secondary-stat__text date"
|
||||||
>
|
>
|
||||||
Joined {{ fromNow(user.created) }}
|
{{
|
||||||
|
formatMessage(messages.profileJoinedAt, { ago: formatRelativeTime(user.created) })
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<hr class="card-divider" />
|
<hr class="card-divider" />
|
||||||
<div class="stats-block__item secondary-stat">
|
<div class="stats-block__item secondary-stat">
|
||||||
<UserIcon class="secondary-stat__icon" aria-hidden="true" />
|
<UserIcon class="secondary-stat__icon" aria-hidden="true" />
|
||||||
<span class="secondary-stat__text"> User ID: <CopyCode :text="user.id" /> </span>
|
<span class="secondary-stat__text">
|
||||||
|
<IntlFormatted :message-id="messages.profileUserId">
|
||||||
|
<template #~id>
|
||||||
|
<CopyCode :text="user.id" />
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,12 +175,12 @@
|
|||||||
<NavRow
|
<NavRow
|
||||||
:links="[
|
:links="[
|
||||||
{
|
{
|
||||||
label: 'all',
|
label: formatMessage(commonMessages.allProjectType),
|
||||||
href: `/user/${user.username}`,
|
href: `/user/${user.username}`,
|
||||||
},
|
},
|
||||||
...projectTypes.map((x) => {
|
...projectTypes.map((x) => {
|
||||||
return {
|
return {
|
||||||
label: $formatProjectType(x) + 's',
|
label: formatMessage(getProjectTypeMessage(x, true)),
|
||||||
href: `/user/${user.username}/${x}s`,
|
href: `/user/${user.username}/${x}s`,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -156,11 +193,15 @@
|
|||||||
to="/dashboard/projects"
|
to="/dashboard/projects"
|
||||||
>
|
>
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
Manage projects
|
{{ formatMessage(messages.profileManageProjectsButton) }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<button
|
<button
|
||||||
v-tooltip="$capitalizeString(cosmetics.searchDisplayMode.user) + ' view'"
|
v-tooltip="
|
||||||
:aria-label="$capitalizeString(cosmetics.searchDisplayMode.user) + ' view'"
|
formatMessage(commonMessages[`${cosmetics.searchDisplayMode.user}InputView`])
|
||||||
|
"
|
||||||
|
:aria-label="
|
||||||
|
formatMessage(commonMessages[`${cosmetics.searchDisplayMode.user}InputView`])
|
||||||
|
"
|
||||||
class="square-button"
|
class="square-button"
|
||||||
@click="cycleSearchDisplayMode()"
|
@click="cycleSearchDisplayMode()"
|
||||||
>
|
>
|
||||||
@@ -216,12 +257,16 @@
|
|||||||
</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 === user.id" class="text">
|
<span v-if="auth.user && auth.user.id === user.id" class="preserve-lines text">
|
||||||
You don't have any projects.<br />
|
<IntlFormatted :message-id="messages.profileNoProjectsAuthLabel">
|
||||||
Would you like to
|
<template #create-link="{ children }">
|
||||||
<a class="link" @click.prevent="$refs.modal_creation.show()"> create one</a>?
|
<a class="link" @click.prevent="$refs.modal_creation.show()">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text">This user has no projects!</span>
|
<span v-else class="text">{{ formatMessage(messages.profileNoProjectsLabel) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,6 +304,79 @@ const auth = await useAuth()
|
|||||||
const cosmetics = useCosmetics()
|
const cosmetics = useCosmetics()
|
||||||
const tags = useTags()
|
const tags = useTags()
|
||||||
|
|
||||||
|
const vintl = useVIntl()
|
||||||
|
const { formatMessage } = vintl
|
||||||
|
|
||||||
|
const formatCompactNumber = useCompactNumber()
|
||||||
|
|
||||||
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
profileDownloadsStats: {
|
||||||
|
id: 'profile.stats.downloads',
|
||||||
|
defaultMessage:
|
||||||
|
'{count, plural, one {<stat>{count}</stat> download} other {<stat>{count}</stat> downloads}}',
|
||||||
|
},
|
||||||
|
profileProjectsFollowersStats: {
|
||||||
|
id: 'profile.stats.projects-followers',
|
||||||
|
defaultMessage:
|
||||||
|
'{count, plural, one {<stat>{count}</stat> follower} other {<stat>{count}</stat> followers}} of projects',
|
||||||
|
},
|
||||||
|
profileJoinedAt: {
|
||||||
|
id: 'profile.joined-at',
|
||||||
|
defaultMessage: 'Joined {ago}',
|
||||||
|
},
|
||||||
|
profileJoinedAtTooltip: {
|
||||||
|
id: 'profile.joined-at.tooltip',
|
||||||
|
defaultMessage: '{date, date, long} at {time, time, short}',
|
||||||
|
},
|
||||||
|
profileUserId: {
|
||||||
|
id: 'profile.user-id',
|
||||||
|
defaultMessage: 'User ID: {id}',
|
||||||
|
},
|
||||||
|
profileManageProjectsButton: {
|
||||||
|
id: 'profile.button.manage-projects',
|
||||||
|
defaultMessage: 'Manage projects',
|
||||||
|
},
|
||||||
|
profileMetaDescription: {
|
||||||
|
id: 'profile.meta.description',
|
||||||
|
defaultMessage: "Download {username}'s projects on Modrinth",
|
||||||
|
},
|
||||||
|
profileMetaDescriptionWithBio: {
|
||||||
|
id: 'profile.meta.description-with-bio',
|
||||||
|
defaultMessage: "{bio} - Download {username}'s projects on Modrinth",
|
||||||
|
},
|
||||||
|
profileReportButton: {
|
||||||
|
id: 'profile.button.report',
|
||||||
|
defaultMessage: 'Report',
|
||||||
|
},
|
||||||
|
profileUploadAvatarInput: {
|
||||||
|
id: 'profile.input.upload-avatar',
|
||||||
|
defaultMessage: 'Upload avatar',
|
||||||
|
},
|
||||||
|
profileEditUsernameLabel: {
|
||||||
|
id: 'profile.label.edit-username',
|
||||||
|
defaultMessage: 'Username',
|
||||||
|
},
|
||||||
|
profileEditBioLabel: {
|
||||||
|
id: 'profile.label.edit-bio',
|
||||||
|
defaultMessage: 'Bio',
|
||||||
|
},
|
||||||
|
profileNoProjectsLabel: {
|
||||||
|
id: 'profile.label.no-projects',
|
||||||
|
defaultMessage: 'This user has no projects!',
|
||||||
|
},
|
||||||
|
profileNoProjectsAuthLabel: {
|
||||||
|
id: 'profile.label.no-projects-auth',
|
||||||
|
defaultMessage:
|
||||||
|
"You don't have any projects.\n Would you like to <create-link>create one</create-link>?",
|
||||||
|
},
|
||||||
|
userNotFoundError: {
|
||||||
|
id: 'profile.error.not-found',
|
||||||
|
defaultMessage: 'User not found',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
let user, projects
|
let user, projects
|
||||||
try {
|
try {
|
||||||
;[{ data: user }, { data: projects }] = await Promise.all([
|
;[{ data: user }, { data: projects }] = await Promise.all([
|
||||||
@@ -286,7 +404,7 @@ try {
|
|||||||
throw createError({
|
throw createError({
|
||||||
fatal: true,
|
fatal: true,
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
message: 'User not found',
|
message: formatMessage(messages.userNotFoundError),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,7 +412,7 @@ if (!user.value) {
|
|||||||
throw createError({
|
throw createError({
|
||||||
fatal: true,
|
fatal: true,
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
message: 'User not found',
|
message: formatMessage(messages.userNotFoundError),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,8 +422,11 @@ if (user.value.username !== route.params.id) {
|
|||||||
|
|
||||||
const metaDescription = ref(
|
const metaDescription = ref(
|
||||||
user.value.bio
|
user.value.bio
|
||||||
? `${user.value.bio} - Download ${user.value.username}'s projects on Modrinth`
|
? `${formatMessage(messages.profileMetaDescriptionWithBio, {
|
||||||
: `Download ${user.value.username}'s projects on Modrinth`
|
bio: user.value.bio,
|
||||||
|
username: user.value.username,
|
||||||
|
})}`
|
||||||
|
: `${formatMessage(messages.profileMetaDescription, { username: user.value.username })}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const projectTypes = computed(() => {
|
const projectTypes = computed(() => {
|
||||||
@@ -324,7 +445,7 @@ const sumDownloads = computed(() => {
|
|||||||
sum += project.downloads
|
sum += project.downloads
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.$formatNumber(sum)
|
return sum
|
||||||
})
|
})
|
||||||
const sumFollows = computed(() => {
|
const sumFollows = computed(() => {
|
||||||
let sum = 0
|
let sum = 0
|
||||||
@@ -333,7 +454,7 @@ const sumFollows = computed(() => {
|
|||||||
sum += project.followers
|
sum += project.followers
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.$formatNumber(sum)
|
return sum
|
||||||
})
|
})
|
||||||
|
|
||||||
const isEditing = ref(false)
|
const isEditing = ref(false)
|
||||||
@@ -383,7 +504,7 @@ async function saveChanges() {
|
|||||||
console.error(err)
|
console.error(err)
|
||||||
data.$notify({
|
data.$notify({
|
||||||
group: 'main',
|
group: 'main',
|
||||||
title: 'An error occurred',
|
title: commonMessages.errorNotificationTitle,
|
||||||
text: err.data.description,
|
text: err.data.description,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
|
|||||||
34
utils/common-messages.ts
Normal file
34
utils/common-messages.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export const commonMessages = defineMessages({
|
||||||
|
allProjectType: {
|
||||||
|
id: 'project-type.all',
|
||||||
|
defaultMessage: 'All',
|
||||||
|
},
|
||||||
|
cancelButton: {
|
||||||
|
id: 'button.cancel',
|
||||||
|
defaultMessage: 'Cancel',
|
||||||
|
},
|
||||||
|
editButton: {
|
||||||
|
id: 'button.edit',
|
||||||
|
defaultMessage: 'Edit',
|
||||||
|
},
|
||||||
|
galleryInputView: {
|
||||||
|
id: 'input.view.gallery',
|
||||||
|
defaultMessage: 'Gallery view',
|
||||||
|
},
|
||||||
|
gridInputView: {
|
||||||
|
id: 'input.view.grid',
|
||||||
|
defaultMessage: 'Grid view',
|
||||||
|
},
|
||||||
|
listInputView: {
|
||||||
|
id: 'input.view.list',
|
||||||
|
defaultMessage: 'List view',
|
||||||
|
},
|
||||||
|
errorNotificationTitle: {
|
||||||
|
id: 'notification.error.title',
|
||||||
|
defaultMessage: 'An error occurred',
|
||||||
|
},
|
||||||
|
saveButton: {
|
||||||
|
id: 'button.save',
|
||||||
|
defaultMessage: 'Save',
|
||||||
|
},
|
||||||
|
})
|
||||||
58
utils/i18n-project-type.ts
Normal file
58
utils/i18n-project-type.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const projectTypeMessages = defineMessages({
|
||||||
|
datapack: {
|
||||||
|
id: 'project-type.datapack.singular',
|
||||||
|
defaultMessage: 'Data Pack',
|
||||||
|
},
|
||||||
|
datapacks: {
|
||||||
|
id: 'project-type.datapack.plural',
|
||||||
|
defaultMessage: 'Data Packs',
|
||||||
|
},
|
||||||
|
mod: {
|
||||||
|
id: 'project-type.mod.singular',
|
||||||
|
defaultMessage: 'Mod',
|
||||||
|
},
|
||||||
|
mods: {
|
||||||
|
id: 'project-type.mod.plural',
|
||||||
|
defaultMessage: 'Mods',
|
||||||
|
},
|
||||||
|
modpack: {
|
||||||
|
id: 'project-type.modpack.singular',
|
||||||
|
defaultMessage: 'Modpack',
|
||||||
|
},
|
||||||
|
modpacks: {
|
||||||
|
id: 'project-type.modpack.plural',
|
||||||
|
defaultMessage: 'Modpacks',
|
||||||
|
},
|
||||||
|
plugin: {
|
||||||
|
id: 'project-type.plugin.singular',
|
||||||
|
defaultMessage: 'Plugin',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
id: 'project-type.plugin.plural',
|
||||||
|
defaultMessage: 'Plugins',
|
||||||
|
},
|
||||||
|
resourcepack: {
|
||||||
|
id: 'project-type.resourcepack.singular',
|
||||||
|
defaultMessage: 'Resource Pack',
|
||||||
|
},
|
||||||
|
resourcepacks: {
|
||||||
|
id: 'project-type.resourcepack.plural',
|
||||||
|
defaultMessage: 'Resource Packs',
|
||||||
|
},
|
||||||
|
shader: {
|
||||||
|
id: 'project-type.shader.singular',
|
||||||
|
defaultMessage: 'Shader',
|
||||||
|
},
|
||||||
|
shaders: {
|
||||||
|
id: 'project-type.shader.plural',
|
||||||
|
defaultMessage: 'Shaders',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type ExtractSingulars<K extends string> = K extends `${infer T}s` ? T : never
|
||||||
|
|
||||||
|
type ProjectType = ExtractSingulars<keyof typeof projectTypeMessages>
|
||||||
|
|
||||||
|
export function getProjectTypeMessage(type: ProjectType, plural = false) {
|
||||||
|
return projectTypeMessages[`${type}${plural ? 's' : ''}`]
|
||||||
|
}
|
||||||
25
utils/vue-children.ts
Normal file
25
utils/vue-children.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { createTextVNode, isVNode, toDisplayString, type VNode } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a specific child is a VNode. If not, converts it to a display
|
||||||
|
* string and then creates text VNode for the result.
|
||||||
|
*
|
||||||
|
* @param child Child to normalize.
|
||||||
|
* @returns Either the original VNode or a text VNode containing child converted
|
||||||
|
* to a display string.
|
||||||
|
*/
|
||||||
|
function normalizeChild(child: any): VNode {
|
||||||
|
return isVNode(child) ? child : createTextVNode(toDisplayString(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in an array of VNodes and other children. It then converts each child
|
||||||
|
* that is not already a VNode to a display string, and creates a text VNode for
|
||||||
|
* that string.
|
||||||
|
*
|
||||||
|
* @param children Children to normalize.
|
||||||
|
* @returns Children with all of non-VNodes converted to display strings.
|
||||||
|
*/
|
||||||
|
export function normalizeChildren(children: any | any[]): VNode[] {
|
||||||
|
return Array.isArray(children) ? children.map(normalizeChild) : [normalizeChild(children)]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user