Make tags translatable, move icons to frontend, a few other things (#5229)
* Make tags translatable, move icons to frontend, a few other things * Migrate more things * fix import * more import fixes * export tag-messages * lint
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { Avatar, ButtonStyled, FormattedTag } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
|
||||
type Instance = {
|
||||
@@ -37,7 +36,8 @@ defineProps<{
|
||||
</span>
|
||||
<span class="text-secondary flex items-center gap-2 font-semibold">
|
||||
<GameIcon class="h-5 w-5 text-secondary" />
|
||||
{{ formatCategory(instance.loader) }} {{ instance.game_version }}
|
||||
<FormattedTag :tag="instance.loader" enforce-type="loader" />
|
||||
{{ instance.game_version }}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
|
||||
import { Avatar, TagItem } from '@modrinth/ui'
|
||||
import { formatCategory, formatNumber } from '@modrinth/utils'
|
||||
import { Avatar, FormattedTag, TagItem } from '@modrinth/ui'
|
||||
import { formatNumber } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { computed } from 'vue'
|
||||
@@ -107,7 +107,7 @@ const toTransparent = computed(() => {
|
||||
<div class="flex items-center gap-1 pr-2">
|
||||
<TagIcon />
|
||||
<TagItem>
|
||||
{{ formatCategory(featuredCategory) }}
|
||||
<FormattedTag :tag="featuredCategory" />
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
:key="tag"
|
||||
class="text-sm font-semibold text-secondary flex gap-1 px-[0.375rem] py-0.5 bg-button-bg rounded-full"
|
||||
>
|
||||
{{ formatCategory(tag.name) }}
|
||||
<FormattedTag :tag="tag.name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,8 +118,8 @@
|
||||
|
||||
<script setup>
|
||||
import { CheckIcon, DownloadIcon, HeartIcon, PlusIcon, TagsIcon } from '@modrinth/assets'
|
||||
import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
|
||||
import { formatCategory, formatNumber } from '@modrinth/utils'
|
||||
import { Avatar, ButtonStyled, FormattedTag, injectNotificationManager } from '@modrinth/ui'
|
||||
import { formatNumber } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DownloadIcon,
|
||||
getLoaderIcon,
|
||||
HammerIcon,
|
||||
IssuesIcon,
|
||||
SpinnerIcon,
|
||||
@@ -17,16 +18,11 @@ import {
|
||||
Chips,
|
||||
Combobox,
|
||||
defineMessages,
|
||||
getTagMessageOrDefault,
|
||||
injectNotificationManager,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import {
|
||||
formatCategory,
|
||||
type GameVersionTag,
|
||||
type PlatformTag,
|
||||
type Project,
|
||||
type Version,
|
||||
} from '@modrinth/utils'
|
||||
import type { GameVersionTag, PlatformTag, Project, Version } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
@@ -129,9 +125,7 @@ if (props.instance.linked_data && props.instance.linked_data.project_id && !prop
|
||||
fetching.value = false
|
||||
}
|
||||
|
||||
const currentLoaderIcon = computed(
|
||||
() => loaders?.value.find((x) => x.name === props.instance.loader)?.icon,
|
||||
)
|
||||
const currentLoaderIcon = computed(() => getLoaderIcon(props.instance.loader))
|
||||
|
||||
const gameVersionsForLoader = computed(() => {
|
||||
return all_game_versions?.value.filter((item) => {
|
||||
@@ -550,7 +544,7 @@ const messages = defineMessages({
|
||||
v-else
|
||||
class="w-10 h-10 flex items-center justify-center rounded-full bg-button-bg border-solid border-[1px] border-button-border p-2 [&_svg]:h-full [&_svg]:w-full"
|
||||
>
|
||||
<div v-if="!!currentLoaderIcon" class="contents" v-html="currentLoaderIcon" />
|
||||
<component :is="currentLoaderIcon" v-if="currentLoaderIcon" />
|
||||
<WrenchIcon v-else />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 justify-center">
|
||||
@@ -569,7 +563,10 @@ const messages = defineMessages({
|
||||
? modpackVersion
|
||||
? modpackVersion?.version_number
|
||||
: 'Unknown version'
|
||||
: formatCategory(instance.loader)
|
||||
: (() => {
|
||||
const message = getTagMessageOrDefault(instance.loader, 'loader')
|
||||
return typeof message === 'string' ? message : formatMessage(message)
|
||||
})()
|
||||
}}
|
||||
<template v-if="instance.loader !== 'vanilla' && !modpackProject">
|
||||
{{ instance.loader_version || formatMessage(messages.unknownVersion) }}
|
||||
@@ -677,7 +674,14 @@ const messages = defineMessages({
|
||||
</div>
|
||||
<template v-if="loader !== 'vanilla'">
|
||||
<h2 class="m-0 mt-4 text-lg font-extrabold text-contrast block">
|
||||
{{ formatMessage(messages.loaderVersion, { loader: formatCategory(loader) }) }}
|
||||
{{
|
||||
formatMessage(messages.loaderVersion, {
|
||||
loader: (() => {
|
||||
const message = getTagMessageOrDefault(loader, 'loader')
|
||||
return typeof message === 'string' ? message : formatMessage(message)
|
||||
})(),
|
||||
})
|
||||
}}
|
||||
</h2>
|
||||
<Combobox
|
||||
v-if="selectableLoaderVersions"
|
||||
@@ -709,7 +713,10 @@ const messages = defineMessages({
|
||||
? messages.alreadyInstalledVanilla
|
||||
: messages.alreadyInstalledModded,
|
||||
{
|
||||
platform: formatCategory(loader),
|
||||
platform: (() => {
|
||||
const message = getTagMessageOrDefault(loader, 'loader')
|
||||
return typeof message === 'string' ? message : formatMessage(message)
|
||||
})(),
|
||||
version: instance.loader_version,
|
||||
game_version: gameVersion,
|
||||
},
|
||||
|
||||
@@ -91,10 +91,9 @@
|
||||
|
||||
<script>
|
||||
import { CalendarIcon, DownloadIcon, HeartIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import { Avatar, ProjectStatusBadge, useRelativeTime } from '@modrinth/ui'
|
||||
import { Avatar, Categories, ProjectStatusBadge, useRelativeTime } from '@modrinth/ui'
|
||||
|
||||
import EnvironmentIndicator from '~/components/ui/EnvironmentIndicator.vue'
|
||||
import Categories from '~/components/ui/search/Categories.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"
|
||||
:style="`--_color: var(--color-platform-${loader.name})`"
|
||||
>
|
||||
<div v-html="loader.icon"></div>
|
||||
{{ formatCategory(loader.name) }}
|
||||
<component :is="getLoaderIcon(loader.name)" v-if="getLoaderIcon(loader.name)" />
|
||||
<FormattedTag :tag="loader.name" enforce-type="loader" />
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,8 +40,8 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { Chips, TagItem } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { getLoaderIcon } from '@modrinth/assets'
|
||||
import { Chips, FormattedTag, TagItem } from '@modrinth/ui'
|
||||
|
||||
const selectedLoaders = defineModel<string[]>({ default: [] })
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
class="border !border-solid border-surface-5 !transition-all hover:bg-button-bgHover hover:no-underline"
|
||||
:style="`--_color: var(--color-platform-${loader.name})`"
|
||||
>
|
||||
<div v-html="loader.icon"></div>
|
||||
{{ formatCategory(loader.name) }}
|
||||
<component :is="getLoaderIcon(loader.name)" v-if="getLoaderIcon(loader.name)" />
|
||||
<FormattedTag :tag="loader.name" enforce-type="loader" />
|
||||
<XIcon class="text-secondary" />
|
||||
</TagItem>
|
||||
</template>
|
||||
@@ -41,9 +41,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, TagItem } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { getLoaderIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, FormattedTag, TagItem } from '@modrinth/ui'
|
||||
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
class="border !border-solid border-surface-5 hover:no-underline"
|
||||
:style="`--_color: var(--color-platform-${loader.name})`"
|
||||
>
|
||||
<div v-html="loader.icon"></div>
|
||||
{{ formatCategory(loader.name) }}
|
||||
<component :is="getLoaderIcon(loader.name)" v-if="getLoaderIcon(loader.name)" />
|
||||
<FormattedTag :tag="loader.name" enforce-type="loader" />
|
||||
</TagItem>
|
||||
</template>
|
||||
|
||||
@@ -189,16 +189,16 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { EditIcon, UnknownIcon } from '@modrinth/assets'
|
||||
import { EditIcon, getLoaderIcon, UnknownIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
defineMessages,
|
||||
ENVIRONMENTS_COPY,
|
||||
FormattedTag,
|
||||
injectProjectPageContext,
|
||||
TagItem,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
|
||||
import { useGeneratedState } from '~/composables/generated'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<template>
|
||||
<div class="categories">
|
||||
<slot />
|
||||
<span
|
||||
v-for="category in categoriesFiltered"
|
||||
:key="category.name"
|
||||
v-html="category.icon + formatCategory(category.name)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
categories: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const tags = useGeneratedState()
|
||||
|
||||
return { tags }
|
||||
},
|
||||
computed: {
|
||||
categoriesFiltered() {
|
||||
return this.tags.categories
|
||||
.concat(this.tags.loaders)
|
||||
.filter(
|
||||
(x) =>
|
||||
this.categories.includes(x.name) && (!x.project_type || x.project_type === this.type),
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: { formatCategory },
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.categories {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
:deep(span) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: var(--spacing-card-md);
|
||||
}
|
||||
|
||||
&:not(.badge) {
|
||||
color: var(--color-icon);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
margin-right: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -132,17 +132,24 @@
|
||||
:loader="'Vanilla'"
|
||||
class="size-5 flex-none"
|
||||
/>
|
||||
<svg
|
||||
v-else
|
||||
<component
|
||||
:is="getLoaderIcon(filtersRef.selectedPlatforms[0])"
|
||||
v-else-if="
|
||||
filtersRef?.selectedPlatforms[0] && getLoaderIcon(filtersRef.selectedPlatforms[0])
|
||||
"
|
||||
class="size-5 flex-none"
|
||||
v-html="tags.loaders.find((x) => x.name === filtersRef?.selectedPlatforms[0])?.icon"
|
||||
></svg>
|
||||
/>
|
||||
|
||||
<div class="w-full truncate text-left">
|
||||
{{
|
||||
filtersRef?.selectedPlatforms.length === 0
|
||||
? 'All platforms'
|
||||
: filtersRef?.selectedPlatforms.map((x) => formatCategory(x)).join(', ')
|
||||
: filtersRef?.selectedPlatforms
|
||||
.map((x) => {
|
||||
const message = getTagMessageOrDefault(x, 'loader')
|
||||
return typeof message === 'string' ? message : formatMessage(message)
|
||||
})
|
||||
.join(', ')
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -242,6 +249,7 @@ import {
|
||||
DropdownIcon,
|
||||
ExternalIcon,
|
||||
GameIcon,
|
||||
getLoaderIcon,
|
||||
LockOpenIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
@@ -252,10 +260,12 @@ import {
|
||||
Checkbox,
|
||||
Combobox,
|
||||
CopyCode,
|
||||
getTagMessageOrDefault,
|
||||
NewModal,
|
||||
TagItem,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import TagItem from '@modrinth/ui/src/components/base/TagItem.vue'
|
||||
import { formatCategory, formatVersionsForDisplay, type Mod, type Version } from '@modrinth/utils'
|
||||
import { formatVersionsForDisplay, type Mod, type Version } from '@modrinth/utils'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import Accordion from '~/components/ui/Accordion.vue'
|
||||
@@ -265,6 +275,8 @@ import ContentVersionFilter, {
|
||||
} from '~/components/ui/servers/ContentVersionFilter.vue'
|
||||
import LoaderIcon from '~/components/ui/servers/icons/LoaderIcon.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const props = defineProps<{
|
||||
type: 'Mod' | 'Plugin'
|
||||
loader: string
|
||||
@@ -424,7 +436,10 @@ const formattedVersions = computed(() => {
|
||||
if (secondLoaderPosition === -1) return -1
|
||||
return firstLoaderPosition - secondLoaderPosition
|
||||
})
|
||||
.map((loader: string) => formatCategory(loader)),
|
||||
.map((loader: string) => {
|
||||
const message = getTagMessageOrDefault(loader, 'loader')
|
||||
return typeof message === 'string' ? message : formatMessage(message)
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<template>
|
||||
<div v-if="loaderData?.icon" v-html="loaderData.icon" />
|
||||
<component :is="icon" v-if="icon" />
|
||||
<LoaderIcon v-else />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LoaderIcon } from '@modrinth/assets'
|
||||
import { getLoaderIcon, LoaderIcon } from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useGeneratedState } from '~/composables/generated'
|
||||
|
||||
const props = defineProps<{
|
||||
loader: string
|
||||
}>()
|
||||
|
||||
const tags = useGeneratedState()
|
||||
|
||||
// Find the loader by name (case-insensitive comparison)
|
||||
const loaderData = computed(() =>
|
||||
tags.value.loaders.find((l) => l.name.toLowerCase() === props.loader.toLowerCase()),
|
||||
)
|
||||
const icon = computed(() => getLoaderIcon(props.loader))
|
||||
</script>
|
||||
|
||||
@@ -977,7 +977,7 @@ import {
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
||||
import { formatCategory, formatPrice, formatProjectType, renderString } from '@modrinth/utils'
|
||||
import { formatPrice, formatProjectType, renderString } from '@modrinth/utils'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
@@ -66,18 +66,24 @@
|
||||
v-for="category in categoryLists[header]"
|
||||
:key="`category-${header}-${category.name}`"
|
||||
:model-value="current.selectedTags.includes(category)"
|
||||
:description="formatCategory(category.name)"
|
||||
:description="
|
||||
typeof getTagMessageOrDefault(category.name, 'category') === 'string'
|
||||
? getTagMessageOrDefault(category.name, 'category')
|
||||
: formatMessage(getTagMessageOrDefault(category.name, 'category'))
|
||||
"
|
||||
class="category-selector"
|
||||
@update:model-value="toggleCategory(category)"
|
||||
>
|
||||
<div class="category-selector__label">
|
||||
<div
|
||||
v-if="header !== 'resolutions' && category.icon"
|
||||
<component
|
||||
:is="getCategoryIcon(category.name)"
|
||||
v-if="header !== 'resolutions' && getCategoryIcon(category.name)"
|
||||
aria-hidden="true"
|
||||
class="icon"
|
||||
v-html="category.icon"
|
||||
/>
|
||||
<span aria-hidden="true"> {{ formatCategory(category.name) }}</span>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
@@ -100,18 +106,24 @@
|
||||
:key="`featured-category-${category.name}`"
|
||||
class="category-selector"
|
||||
:model-value="current.featuredTags.includes(category)"
|
||||
:description="formatCategory(category.name)"
|
||||
:description="
|
||||
typeof getTagMessageOrDefault(category.name, 'category') === 'string'
|
||||
? getTagMessageOrDefault(category.name, 'category')
|
||||
: formatMessage(getTagMessageOrDefault(category.name, 'category'))
|
||||
"
|
||||
:disabled="current.featuredTags.length >= 3 && !current.featuredTags.includes(category)"
|
||||
@update:model-value="toggleFeaturedCategory(category)"
|
||||
>
|
||||
<div class="category-selector__label">
|
||||
<div
|
||||
v-if="category.header !== 'resolutions' && category.icon"
|
||||
<component
|
||||
:is="getCategoryIcon(category.name)"
|
||||
v-if="category.header !== 'resolutions' && getCategoryIcon(category.name)"
|
||||
aria-hidden="true"
|
||||
class="icon"
|
||||
v-html="category.icon"
|
||||
/>
|
||||
<span aria-hidden="true"> {{ formatCategory(category.name) }}</span>
|
||||
<span aria-hidden="true">
|
||||
<FormattedTag :tag="category.name" enforce-type="category" />
|
||||
</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
@@ -128,14 +140,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { StarIcon, TriangleAlertIcon } from '@modrinth/assets'
|
||||
import { Checkbox, injectProjectPageContext, UnsavedChangesPopup, useSavable } from '@modrinth/ui'
|
||||
import { getCategoryIcon, StarIcon, TriangleAlertIcon } from '@modrinth/assets'
|
||||
import {
|
||||
formatCategory,
|
||||
formatCategoryHeader,
|
||||
formatProjectType,
|
||||
sortedCategories,
|
||||
} from '@modrinth/utils'
|
||||
Checkbox,
|
||||
FormattedTag,
|
||||
getTagMessageOrDefault,
|
||||
injectProjectPageContext,
|
||||
UnsavedChangesPopup,
|
||||
useSavable,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { formatCategoryHeader, formatProjectType, sortedCategories } from '@modrinth/utils'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Category {
|
||||
@@ -146,6 +161,7 @@ interface Category {
|
||||
}
|
||||
|
||||
const tags = useGeneratedState()
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const { projectV2: project, patchProject } = injectProjectPageContext()
|
||||
|
||||
@@ -308,9 +324,12 @@ const toggleFeaturedCategory = (category: Category) => {
|
||||
|
||||
.icon {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
margin-right: 0.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
margin-right: 0.25rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
@@ -428,6 +428,7 @@ import {
|
||||
Avatar,
|
||||
Badge,
|
||||
ButtonStyled,
|
||||
Categories,
|
||||
ConfirmModal,
|
||||
CopyCode,
|
||||
ENVIRONMENTS_COPY,
|
||||
@@ -440,7 +441,6 @@ import { Multiselect } from 'vue-multiselect'
|
||||
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
|
||||
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
|
||||
import Modal from '~/components/ui/Modal.vue'
|
||||
import Categories from '~/components/ui/search/Categories.vue'
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { inferVersionInfo } from '~/helpers/infer'
|
||||
import { createDataPackVersion } from '~/helpers/package.js'
|
||||
|
||||
Reference in New Issue
Block a user