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:
Prospector
2026-01-28 11:01:56 -08:00
committed by GitHub
parent 6d68d50699
commit 16ac2aae6b
121 changed files with 1532 additions and 229 deletions

View File

@@ -6,4 +6,5 @@ export * from './game-modes'
export * from './notices'
export * from './savable'
export * from './search'
export * from './tag-messages'
export * from './vue-children'

View File

@@ -1,10 +1,11 @@
import type { Labrinth } from '@modrinth/api-client'
import { ClientIcon, ServerIcon } from '@modrinth/assets'
import { formatCategory, formatCategoryHeader, sortByNameOrNumber } from '@modrinth/utils'
import { ClientIcon, getCategoryIcon, getLoaderIcon, ServerIcon } from '@modrinth/assets'
import { formatCategoryHeader, sortByNameOrNumber } from '@modrinth/utils'
import { type Component, computed, readonly, type Ref, ref } from 'vue'
import { type LocationQueryRaw, type LocationQueryValue, useRoute } from 'vue-router'
import { defineMessage, useVIntl } from '../composables/i18n'
import { getTagMessageOrDefault } from './tag-messages.ts'
type BaseOption = {
id: string
@@ -111,7 +112,22 @@ export function useSearch(
const filters = computed(() => {
const categoryFilters: Record<string, FilterType> = {}
for (const category of sortByNameOrNumber(tags.value.categories.slice(), ['header', 'name'])) {
const sortedCategories = sortByNameOrNumber(tags.value.categories.slice(), ['header', 'name'])
sortedCategories.sort((a, b) => {
if (a.header === 'performance impact' && b.header === 'performance impact') {
const qualityOrder = ['potato', 'low', 'medium', 'high', 'screenshot']
const aIndex = qualityOrder.indexOf(a.name)
const bIndex = qualityOrder.indexOf(b.name)
if (aIndex !== -1 && bIndex !== -1) {
return aIndex - bIndex
}
if (aIndex !== -1) return -1
if (bIndex !== -1) return 1
}
return 0
})
for (const category of sortedCategories) {
const filterTypeId = `category_${category.project_type}_${category.header}`
if (!categoryFilters[filterTypeId]) {
categoryFilters[filterTypeId] = {
@@ -120,7 +136,7 @@ export function useSearch(
supported_project_types:
category.project_type === 'mod'
? ['mod', 'plugin', 'datapack']
: [category.project_type],
: ([category.project_type] as ProjectType[]),
display: 'all',
query_param: category.header === 'resolutions' ? 'g' : 'f',
supports_negative_filter: true,
@@ -128,10 +144,11 @@ export function useSearch(
options: [],
}
}
const message = getTagMessageOrDefault(category.name, 'category')
categoryFilters[filterTypeId].options.push({
id: category.name,
formatted_name: formatCategory(category.name),
icon: category.icon,
formatted_name: typeof message === 'string' ? message : formatMessage(message),
icon: getCategoryIcon(category.name),
value: `categories:${category.name}`,
method: category.header === 'resolutions' ? 'or' : 'and',
})
@@ -225,7 +242,7 @@ export function useSearch(
display: 'expandable',
query_param: 'g',
supports_negative_filter: true,
default_values: ['fabric', 'forge', 'neoforge', 'quilt'],
default_values: ['fabric', 'forge', 'neoforge'],
searchable: false,
options: tags.value.loaders
.filter(
@@ -235,10 +252,11 @@ export function useSearch(
!loader.supported_project_types.includes('datapack'),
)
.map((loader) => {
const message = getTagMessageOrDefault(loader.name, 'loader')
return {
id: loader.name,
formatted_name: formatCategory(loader.name),
icon: loader.icon,
formatted_name: typeof message === 'string' ? message : formatMessage(message),
icon: getLoaderIcon(loader.name),
method: 'or',
value: `categories:${loader.name}`,
}
@@ -261,10 +279,11 @@ export function useSearch(
options: tags.value.loaders
.filter((loader) => loader.supported_project_types.includes('modpack'))
.map((loader) => {
const message = getTagMessageOrDefault(loader.name, 'loader')
return {
id: loader.name,
formatted_name: formatCategory(loader.name),
icon: loader.icon,
formatted_name: typeof message === 'string' ? message : formatMessage(message),
icon: getLoaderIcon(loader.name),
method: 'or',
value: `categories:${loader.name}`,
}
@@ -290,10 +309,11 @@ export function useSearch(
!PLUGIN_PLATFORMS.includes(loader.name),
)
.map((loader) => {
const message = getTagMessageOrDefault(loader.name, 'loader')
return {
id: loader.name,
formatted_name: formatCategory(loader.name),
icon: loader.icon,
formatted_name: typeof message === 'string' ? message : formatMessage(message),
icon: getLoaderIcon(loader.name),
method: 'or',
value: `categories:${loader.name}`,
}
@@ -315,10 +335,11 @@ export function useSearch(
options: tags.value.loaders
.filter((loader) => PLUGIN_PLATFORMS.includes(loader.name))
.map((loader) => {
const message = getTagMessageOrDefault(loader.name, 'loader')
return {
id: loader.name,
formatted_name: formatCategory(loader.name),
icon: loader.icon,
formatted_name: typeof message === 'string' ? message : formatMessage(message),
icon: getLoaderIcon(loader.name),
method: 'or',
value: `categories:${loader.name}`,
}
@@ -333,17 +354,19 @@ export function useSearch(
}),
),
supported_project_types: ['shader'],
display: 'all',
query_param: 'g',
supports_negative_filter: true,
searchable: false,
display: 'expandable',
default_values: ['iris', 'optifine', 'vanilla'],
options: tags.value.loaders
.filter((loader) => loader.supported_project_types.includes('shader'))
.map((loader) => {
const message = getTagMessageOrDefault(loader.name, 'loader')
return {
id: loader.name,
formatted_name: formatCategory(loader.name),
icon: loader.icon,
formatted_name: typeof message === 'string' ? message : formatMessage(message),
icon: getLoaderIcon(loader.name),
method: 'or',
value: `categories:${loader.name}`,
}

View File

@@ -0,0 +1,409 @@
import { capitalizeString } from '@modrinth/utils'
import { defineMessages, type MessageDescriptor } from '../composables/i18n'
export const loaderMessages = defineMessages({
babric: {
id: 'tag.loader.babric',
defaultMessage: 'Babric',
},
'bta-babric': {
id: 'tag.loader.bta-babric',
defaultMessage: 'BTA (Babric)',
},
bukkit: {
id: 'tag.loader.bukkit',
defaultMessage: 'Bukkit',
},
bungeecord: {
id: 'tag.loader.bungeecord',
defaultMessage: 'BungeeCord',
},
canvas: {
id: 'tag.loader.canvas',
defaultMessage: 'Canvas',
},
datapack: {
id: 'tag.loader.datapack',
defaultMessage: 'Data Pack',
},
fabric: {
id: 'tag.loader.fabric',
defaultMessage: 'Fabric',
},
folia: {
id: 'tag.loader.folia',
defaultMessage: 'Folia',
},
forge: {
id: 'tag.loader.forge',
defaultMessage: 'Forge',
},
geyser: {
id: 'tag.loader.geyser',
defaultMessage: 'Geyser Extension',
},
iris: {
id: 'tag.loader.iris',
defaultMessage: 'Iris',
},
'java-agent': {
id: 'tag.loader.java-agent',
defaultMessage: 'Java Agent',
},
'legacy-fabric': {
id: 'tag.loader.legacy-fabric',
defaultMessage: 'Legacy Fabric',
},
liteloader: {
id: 'tag.loader.liteloader',
defaultMessage: 'LiteLoader',
},
minecraft: {
id: 'tag.loader.minecraft',
defaultMessage: 'Resource Pack',
},
modloader: {
id: 'tag.loader.modloader',
defaultMessage: "Risugami's ModLoader",
},
mrpack: {
id: 'tag.loader.mrpack',
defaultMessage: 'Modpack',
},
neoforge: {
id: 'tag.loader.neoforge',
defaultMessage: 'NeoForge',
},
nilloader: {
id: 'tag.loader.nilloader',
defaultMessage: 'NilLoader',
},
optifine: {
id: 'tag.loader.optifine',
defaultMessage: 'OptiFine',
},
ornithe: {
id: 'tag.loader.ornithe',
defaultMessage: 'Ornithe',
},
paper: {
id: 'tag.loader.paper',
defaultMessage: 'Paper',
},
purpur: {
id: 'tag.loader.purpur',
defaultMessage: 'Purpur',
},
quilt: {
id: 'tag.loader.quilt',
defaultMessage: 'Quilt',
},
rift: {
id: 'tag.loader.rift',
defaultMessage: 'Rift',
},
spigot: {
id: 'tag.loader.spigot',
defaultMessage: 'Spigot',
},
sponge: {
id: 'tag.loader.sponge',
defaultMessage: 'Sponge',
},
vanilla: {
id: 'tag.loader.vanilla',
defaultMessage: 'Vanilla Shader',
},
velocity: {
id: 'tag.loader.velocity',
defaultMessage: 'Velocity',
},
waterfall: {
id: 'tag.loader.waterfall',
defaultMessage: 'Waterfall',
},
})
export const categoryMessages = defineMessages({
'128x': {
id: 'tag.category.128x',
defaultMessage: '128x',
},
'16x': {
id: 'tag.category.16x',
defaultMessage: '16x',
},
'256x': {
id: 'tag.category.256x',
defaultMessage: '256x',
},
'32x': {
id: 'tag.category.32x',
defaultMessage: '32x',
},
'48x': {
id: 'tag.category.48x',
defaultMessage: '48x',
},
'512x+': {
id: 'tag.category.512x+',
defaultMessage: '512x or higher',
},
'64x': {
id: 'tag.category.64x',
defaultMessage: '64x',
},
'8x-': {
id: 'tag.category.8x-',
defaultMessage: '8x or lower',
},
adventure: {
id: 'tag.category.adventure',
defaultMessage: 'Adventure',
},
atmosphere: {
id: 'tag.category.atmosphere',
defaultMessage: 'Atmosphere',
},
audio: {
id: 'tag.category.audio',
defaultMessage: 'Audio',
},
blocks: {
id: 'tag.category.blocks',
defaultMessage: 'Blocks',
},
bloom: {
id: 'tag.category.bloom',
defaultMessage: 'Bloom',
},
cartoon: {
id: 'tag.category.cartoon',
defaultMessage: 'Cartoon',
},
challenging: {
id: 'tag.category.challenging',
defaultMessage: 'Challenging',
},
'colored-lighting': {
id: 'tag.category.colored-lighting',
defaultMessage: 'Colored Lighting',
},
combat: {
id: 'tag.category.combat',
defaultMessage: 'Combat',
},
'core-shaders': {
id: 'tag.category.core-shaders',
defaultMessage: 'Core Shaders',
},
cursed: {
id: 'tag.category.cursed',
defaultMessage: 'Cursed',
},
decoration: {
id: 'tag.category.decoration',
defaultMessage: 'Decoration',
},
economy: {
id: 'tag.category.economy',
defaultMessage: 'Economy',
},
entities: {
id: 'tag.category.entities',
defaultMessage: 'Entities',
},
environment: {
id: 'tag.category.environment',
defaultMessage: 'Environment',
},
equipment: {
id: 'tag.category.equipment',
defaultMessage: 'Equipment',
},
fantasy: {
id: 'tag.category.fantasy',
defaultMessage: 'Fantasy',
},
foliage: {
id: 'tag.category.foliage',
defaultMessage: 'Foliage',
},
fonts: {
id: 'tag.category.fonts',
defaultMessage: 'Fonts',
},
food: {
id: 'tag.category.food',
defaultMessage: 'Food',
},
'game-mechanics': {
id: 'tag.category.game-mechanics',
defaultMessage: 'Game Mechanics',
},
gui: {
id: 'tag.category.gui',
defaultMessage: 'GUI',
},
high: {
id: 'tag.category.high',
defaultMessage: 'High',
},
items: {
id: 'tag.category.items',
defaultMessage: 'Items',
},
'kitchen-sink': {
id: 'tag.category.kitchen-sink',
defaultMessage: 'Kitchen Sink',
},
library: {
id: 'tag.category.library',
defaultMessage: 'Library',
},
lightweight: {
id: 'tag.category.lightweight',
defaultMessage: 'Lightweight',
},
locale: {
id: 'tag.category.locale',
defaultMessage: 'Locale',
},
low: {
id: 'tag.category.low',
defaultMessage: 'Low',
},
magic: {
id: 'tag.category.magic',
defaultMessage: 'Magic',
},
management: {
id: 'tag.category.management',
defaultMessage: 'Management',
},
medium: {
id: 'tag.category.medium',
defaultMessage: 'Medium',
},
minigame: {
id: 'tag.category.minigame',
defaultMessage: 'Minigame',
},
mobs: {
id: 'tag.category.mobs',
defaultMessage: 'Mobs',
},
modded: {
id: 'tag.category.modded',
defaultMessage: 'Modded',
},
models: {
id: 'tag.category.models',
defaultMessage: 'Models',
},
multiplayer: {
id: 'tag.category.multiplayer',
defaultMessage: 'Multiplayer',
},
optimization: {
id: 'tag.category.optimization',
defaultMessage: 'Optimization',
},
'path-tracing': {
id: 'tag.category.path-tracing',
defaultMessage: 'Path Tracing',
},
pbr: {
id: 'tag.category.pbr',
defaultMessage: 'PBR',
},
potato: {
id: 'tag.category.potato',
defaultMessage: 'Potato',
},
quests: {
id: 'tag.category.quests',
defaultMessage: 'Quests',
},
realistic: {
id: 'tag.category.realistic',
defaultMessage: 'Realistic',
},
reflections: {
id: 'tag.category.reflections',
defaultMessage: 'Reflections',
},
screenshot: {
id: 'tag.category.screenshot',
defaultMessage: 'Screenshot',
},
'semi-realistic': {
id: 'tag.category.semi-realistic',
defaultMessage: 'Semi Realistic',
},
shadows: {
id: 'tag.category.shadows',
defaultMessage: 'Shadows',
},
simplistic: {
id: 'tag.category.simplistic',
defaultMessage: 'Simplistic',
},
social: {
id: 'tag.category.social',
defaultMessage: 'Social',
},
storage: {
id: 'tag.category.storage',
defaultMessage: 'Storage',
},
technology: {
id: 'tag.category.technology',
defaultMessage: 'Technology',
},
themed: {
id: 'tag.category.themed',
defaultMessage: 'Themed',
},
transportation: {
id: 'tag.category.transportation',
defaultMessage: 'Transportation',
},
tweaks: {
id: 'tag.category.tweaks',
defaultMessage: 'Tweaks',
},
utility: {
id: 'tag.category.utility',
defaultMessage: 'Utility',
},
'vanilla-like': {
id: 'tag.category.vanilla-like',
defaultMessage: 'Vanilla Like',
},
worldgen: {
id: 'tag.category.worldgen',
defaultMessage: 'World Generation',
},
})
export function getTagMessage(
tag: string,
enforceType?: 'loader' | 'category',
): MessageDescriptor | undefined {
if (enforceType === 'loader') {
return loaderMessages[tag]
} else if (enforceType === 'category') {
return categoryMessages[tag]
} else {
return loaderMessages[tag] ?? categoryMessages[tag]
}
}
export function getTagMessageOrDefault(
tag: string,
enforceType?: 'loader' | 'category',
): MessageDescriptor | string {
return getTagMessage(tag, enforceType) ?? capitalizeString(tag)
}