You've already forked AstralRinth
forked from didirus/AstralRinth
devex: migrate to vue-i18n (#4966)
* sample languages refactor * feat: consistency + dedupe impl of i18n * fix: broken imports * fix: intl formatted component * fix: use relative imports * fix: imports * fix: comment out incomplete locales + fix imports * feat: cleanup * fix: ui imports * fix: lint * fix: admonition import * make footer a component, fix language reactivity * make copyright notice untranslatable --------- Co-authored-by: Calum H. <contact@cal.engineer>
This commit is contained in:
@@ -28,7 +28,8 @@
|
|||||||
"@tauri-apps/plugin-updater": "^2.7.1",
|
"@tauri-apps/plugin-updater": "^2.7.1",
|
||||||
"@tauri-apps/plugin-window-state": "^2.2.2",
|
"@tauri-apps/plugin-window-state": "^2.2.2",
|
||||||
"@types/three": "^0.172.0",
|
"@types/three": "^0.172.0",
|
||||||
"@vintl/vintl": "^4.4.1",
|
"intl-messageformat": "^10.7.7",
|
||||||
|
"vue-i18n": "^9.14.0",
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^11.1.0",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"floating-vue": "^5.2.2",
|
"floating-vue": "^5.2.2",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
NewsArticleCard,
|
NewsArticleCard,
|
||||||
NotificationPanel,
|
NotificationPanel,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
@@ -39,6 +40,7 @@ import {
|
|||||||
provideNotificationManager,
|
provideNotificationManager,
|
||||||
providePageContext,
|
providePageContext,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { renderString } from '@modrinth/utils'
|
import { renderString } from '@modrinth/utils'
|
||||||
import { useQuery } from '@tanstack/vue-query'
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
@@ -48,7 +50,6 @@ import { getCurrentWindow } from '@tauri-apps/api/window'
|
|||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { type } from '@tauri-apps/plugin-os'
|
import { type } from '@tauri-apps/plugin-os'
|
||||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { $fetch } from 'ofetch'
|
import { $fetch } from 'ofetch'
|
||||||
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
|
||||||
import { RouterView, useRoute, useRouter } from 'vue-router'
|
import { RouterView, useRoute, useRouter } from 'vue-router'
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DownloadIcon, ExternalIcon, RefreshCwIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
import { DownloadIcon, ExternalIcon, RefreshCwIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages, ProgressBar } from '@modrinth/ui'
|
import { ButtonStyled, commonMessages, defineMessages, ProgressBar, useVIntl } from '@modrinth/ui'
|
||||||
import { formatBytes } from '@modrinth/utils'
|
import { formatBytes } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
import FriendsSection from '@/components/ui/friends/FriendsSection.vue'
|
import FriendsSection from '@/components/ui/friends/FriendsSection.vue'
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MoreVerticalIcon, TrashIcon, UserIcon, XIcon } from '@modrinth/assets'
|
import { MoreVerticalIcon, TrashIcon, UserIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Accordion, Avatar, ButtonStyled, OverflowMenu } from '@modrinth/ui'
|
import {
|
||||||
|
Accordion,
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
OverflowMenu,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { useTemplateRef } from 'vue'
|
import { useTemplateRef } from 'vue'
|
||||||
|
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, type Ref, ref, watch } from 'vue'
|
import { computed, type Ref, ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox, injectNotificationManager } from '@modrinth/ui'
|
import { Checkbox, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { edit } from '@/helpers/profile'
|
import { edit } from '@/helpers/profile'
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
Chips,
|
Chips,
|
||||||
Combobox,
|
Combobox,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
formatCategory,
|
formatCategory,
|
||||||
@@ -25,7 +27,6 @@ import {
|
|||||||
type Project,
|
type Project,
|
||||||
type Version,
|
type Version,
|
||||||
} from '@modrinth/utils'
|
} from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
|
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
|
||||||
import { Checkbox, injectNotificationManager, Slider } from '@modrinth/ui'
|
import { Checkbox, defineMessages, injectNotificationManager, Slider, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, readonly, ref, watch } from 'vue'
|
import { computed, readonly, ref, watch } from 'vue'
|
||||||
|
|
||||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox, injectNotificationManager, Toggle } from '@modrinth/ui'
|
import { Checkbox, defineMessages, injectNotificationManager, Toggle, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, type Ref, ref, watch } from 'vue'
|
import { computed, type Ref, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { edit } from '@/helpers/profile'
|
import { edit } from '@/helpers/profile'
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
ShieldIcon,
|
ShieldIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { ProgressBar, TabbedModal } from '@modrinth/ui'
|
import { defineMessage, defineMessages, ProgressBar, TabbedModal, useVIntl } from '@modrinth/ui'
|
||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
import { platform as getOsPlatform, version as getOsVersion } from '@tauri-apps/plugin-os'
|
import { platform as getOsPlatform, version as getOsVersion } from '@tauri-apps/plugin-os'
|
||||||
import { defineMessage, defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import {
|
|||||||
MonitorIcon,
|
MonitorIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui'
|
import { Avatar, defineMessage, TabbedModal, type TabbedModalTab, useVIntl } from '@modrinth/ui'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
|
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import {
|
|||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
SmartClickable,
|
SmartClickable,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { capitalizeString } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|||||||
@@ -17,18 +17,19 @@ import {
|
|||||||
UserIcon,
|
UserIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
|
import type { MessageDescriptor } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
SmartClickable,
|
SmartClickable,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import type { MessageDescriptor } from '@vintl/vintl'
|
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
|
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SaveIcon, XIcon } from '@modrinth/assets'
|
import { SaveIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessage,
|
||||||
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
|
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Avatar, ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Checkbox } from '@modrinth/ui'
|
import { Checkbox, defineMessage, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Combobox } from '@modrinth/ui'
|
import { Combobox, defineMessages, type MessageDescriptor, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
import type { ServerPackStatus } from '@/helpers/worlds.ts'
|
import type { ServerPackStatus } from '@/helpers/worlds.ts'
|
||||||
|
|
||||||
|
|||||||
18
apps/app-frontend/src/i18n.config.ts
Normal file
18
apps/app-frontend/src/i18n.config.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { buildLocaleMessages, createMessageCompiler, type CrowdinMessages } from '@modrinth/ui'
|
||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const localeModules = import.meta.glob<{ default: CrowdinMessages }>('./locales/*/index.json', {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: 'en-US',
|
||||||
|
fallbackLocale: 'en-US',
|
||||||
|
messageCompiler: createMessageCompiler(),
|
||||||
|
missingWarn: false,
|
||||||
|
fallbackWarn: false,
|
||||||
|
messages: buildLocaleMessages(localeModules),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@@ -3,31 +3,14 @@ import 'floating-vue/dist/style.css'
|
|||||||
import * as Sentry from '@sentry/vue'
|
import * as Sentry from '@sentry/vue'
|
||||||
import { VueScanPlugin } from '@taijased/vue-render-tracker'
|
import { VueScanPlugin } from '@taijased/vue-render-tracker'
|
||||||
import { VueQueryPlugin } from '@tanstack/vue-query'
|
import { VueQueryPlugin } from '@tanstack/vue-query'
|
||||||
import { createPlugin } from '@vintl/vintl/plugin'
|
|
||||||
import FloatingVue from 'floating-vue'
|
import FloatingVue from 'floating-vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
import App from '@/App.vue'
|
import App from '@/App.vue'
|
||||||
|
import i18n from '@/i18n.config'
|
||||||
import router from '@/routes'
|
import router from '@/routes'
|
||||||
|
|
||||||
const VIntlPlugin = createPlugin({
|
|
||||||
controllerOpts: {
|
|
||||||
defaultLocale: 'en-US',
|
|
||||||
locale: 'en-US',
|
|
||||||
locales: [
|
|
||||||
{
|
|
||||||
tag: 'en-US',
|
|
||||||
meta: {
|
|
||||||
displayName: 'American English',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
globalMixin: true,
|
|
||||||
injectInto: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const vueScan = new VueScanPlugin({
|
const vueScan = new VueScanPlugin({
|
||||||
enabled: false, // Enable or disable the tracker
|
enabled: false, // Enable or disable the tracker
|
||||||
showOverlay: true, // Show overlay to visualize renders
|
showOverlay: true, // Show overlay to visualize renders
|
||||||
@@ -60,6 +43,6 @@ app.use(FloatingVue, {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.use(VIntlPlugin)
|
app.use(i18n)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { Category, GameVersion, Platform, ProjectType, SortType, Tags } fro
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
defineMessages,
|
||||||
DropdownSelect,
|
DropdownSelect,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
@@ -11,9 +12,9 @@ import {
|
|||||||
SearchFilterControl,
|
SearchFilterControl,
|
||||||
SearchSidebarFilter,
|
SearchSidebarFilter,
|
||||||
useSearch,
|
useSearch,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
|
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
|
||||||
import type { LocationQuery } from 'vue-router'
|
import type { LocationQuery } from 'vue-router'
|
||||||
|
|||||||
@@ -274,17 +274,18 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
ContentListPanel,
|
ContentListPanel,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
Pagination,
|
Pagination,
|
||||||
RadialHeader,
|
RadialHeader,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { ContentItem } from '@modrinth/ui/src/components/content/ContentListItem.vue'
|
import type { ContentItem } from '@modrinth/ui/src/components/content/ContentListItem.vue'
|
||||||
import type { Organization, Project, TeamMember, Version } from '@modrinth/utils'
|
import type { Organization, Project, TeamMember, Version } from '@modrinth/utils'
|
||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ import { PlusIcon, SearchIcon, SpinnerIcon, UpdatedIcon, XIcon } from '@modrinth
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
FilterBar,
|
FilterBar,
|
||||||
type FilterBarOption,
|
type FilterBarOption,
|
||||||
GAME_MODES,
|
GAME_MODES,
|
||||||
@@ -133,7 +134,6 @@ import {
|
|||||||
RadialHeader,
|
RadialHeader,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { Version } from '@modrinth/utils'
|
import type { Version } from '@modrinth/utils'
|
||||||
import { defineMessages } from '@vintl/vintl'
|
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { pathToFileURL } from 'node:url'
|
|
||||||
|
|
||||||
import { match as matchLocale } from '@formatjs/intl-localematcher'
|
|
||||||
import { GenericModrinthClient, type Labrinth } from '@modrinth/api-client'
|
import { GenericModrinthClient, type Labrinth } from '@modrinth/api-client'
|
||||||
|
// Import directly from utils to avoid loading .vue files at config time
|
||||||
|
import { LOCALES } from '@modrinth/ui/src/composables/i18n.ts'
|
||||||
import serverSidedVue from '@vitejs/plugin-vue'
|
import serverSidedVue from '@vitejs/plugin-vue'
|
||||||
import { consola } from 'consola'
|
|
||||||
import { promises as fs } from 'fs'
|
import { promises as fs } from 'fs'
|
||||||
import { globIterate } from 'glob'
|
|
||||||
import { defineNuxtConfig } from 'nuxt/config'
|
import { defineNuxtConfig } from 'nuxt/config'
|
||||||
import { basename, relative } from 'pathe'
|
|
||||||
import svgLoader from 'vite-svg-loader'
|
import svgLoader from 'vite-svg-loader'
|
||||||
|
|
||||||
const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
|
const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
|
||||||
@@ -25,28 +21,6 @@ const favicons = {
|
|||||||
'(prefers-color-scheme:dark)': '/favicon.ico',
|
'(prefers-color-scheme:dark)': '/favicon.ico',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tags of locales that are auto-discovered besides the default locale.
|
|
||||||
*
|
|
||||||
* Preferably only the locales that reach a certain threshold of complete
|
|
||||||
* translations would be included in this array.
|
|
||||||
*/
|
|
||||||
// const enabledLocales: string[] = []
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overrides for the categories of the certain locales.
|
|
||||||
*/
|
|
||||||
const localesCategoriesOverrides: Partial<Record<string, 'fun' | 'experimental'>> = {
|
|
||||||
'en-x-pirate': 'fun',
|
|
||||||
'en-x-updown': 'fun',
|
|
||||||
'en-x-lolcat': 'fun',
|
|
||||||
'en-x-uwu': 'fun',
|
|
||||||
'ru-x-bandit': 'fun',
|
|
||||||
ar: 'experimental',
|
|
||||||
he: 'experimental',
|
|
||||||
pes: 'experimental',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
srcDir: 'src/',
|
srcDir: 'src/',
|
||||||
app: {
|
app: {
|
||||||
@@ -176,115 +150,6 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
console.log('Tags generated!')
|
console.log('Tags generated!')
|
||||||
},
|
},
|
||||||
async 'vintl:extendOptions'(opts) {
|
|
||||||
opts.locales ??= []
|
|
||||||
|
|
||||||
// const isProduction = getDomain() === 'https://modrinth.com'
|
|
||||||
|
|
||||||
const resolveCompactNumberDataImport = await (async () => {
|
|
||||||
const compactNumberLocales: string[] = []
|
|
||||||
|
|
||||||
for await (const localeFile of globIterate(
|
|
||||||
'node_modules/@vintl/compact-number/dist/locale-data/*.mjs',
|
|
||||||
{ ignore: '**/*.data.mjs' },
|
|
||||||
)) {
|
|
||||||
const tag = basename(localeFile, '.mjs')
|
|
||||||
compactNumberLocales.push(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveImport(tag: string) {
|
|
||||||
const matchedTag = matchLocale([tag], compactNumberLocales, 'en-x-placeholder')
|
|
||||||
return matchedTag === 'en-x-placeholder'
|
|
||||||
? undefined
|
|
||||||
: `@vintl/compact-number/locale-data/${matchedTag}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolveImport
|
|
||||||
})()
|
|
||||||
|
|
||||||
const resolveOmorphiaLocaleImport = await (async () => {
|
|
||||||
const omorphiaLocales: string[] = []
|
|
||||||
const omorphiaLocaleSets = new Map<string, { files: { from: string; format?: string }[] }>()
|
|
||||||
|
|
||||||
for (const pkgLocales of [`node_modules/@modrinth/**/src/locales/*`]) {
|
|
||||||
for await (const localeDir of globIterate(pkgLocales, {
|
|
||||||
posix: true,
|
|
||||||
})) {
|
|
||||||
const tag = basename(localeDir)
|
|
||||||
if (!omorphiaLocales.includes(tag)) {
|
|
||||||
omorphiaLocales.push(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = omorphiaLocaleSets.get(tag) ?? { files: [] }
|
|
||||||
omorphiaLocaleSets.set(tag, entry)
|
|
||||||
|
|
||||||
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
|
||||||
entry.files.push({
|
|
||||||
from: pathToFileURL(localeFile).toString(),
|
|
||||||
format: 'default',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return function resolveLocaleImport(tag: string) {
|
|
||||||
return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, 'en-x-placeholder'))
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
for await (const localeDir of globIterate('src/locales/*/', { posix: true })) {
|
|
||||||
const tag = basename(localeDir)
|
|
||||||
|
|
||||||
// NOTICE: temporarily disabled all locales except en-US
|
|
||||||
if (opts.defaultLocale !== tag) continue
|
|
||||||
|
|
||||||
const locale =
|
|
||||||
opts.locales.find((locale) => locale.tag === tag) ??
|
|
||||||
opts.locales[opts.locales.push({ tag }) - 1]!
|
|
||||||
|
|
||||||
const localeFiles = (locale.files ??= [])
|
|
||||||
|
|
||||||
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
|
||||||
const fileName = basename(localeFile)
|
|
||||||
if (fileName === 'index.json') {
|
|
||||||
localeFiles.push({
|
|
||||||
from: `./${relative('./src', localeFile)}`,
|
|
||||||
format: 'crowdin',
|
|
||||||
})
|
|
||||||
} else if (fileName === 'meta.json') {
|
|
||||||
const meta: Record<string, { message: string }> = await fs
|
|
||||||
.readFile(localeFile, 'utf8')
|
|
||||||
.then((date) => JSON.parse(date))
|
|
||||||
const localeMeta = (locale.meta ??= {})
|
|
||||||
for (const key in meta) {
|
|
||||||
const value = meta[key]
|
|
||||||
if (value === undefined) continue
|
|
||||||
localeMeta[key] = value.message
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
;(locale.resources ??= {})[fileName] = `./${relative('./src', localeFile)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const categoryOverride = localesCategoriesOverrides[tag]
|
|
||||||
if (categoryOverride != null) {
|
|
||||||
;(locale.meta ??= {}).category = categoryOverride
|
|
||||||
}
|
|
||||||
|
|
||||||
const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag)
|
|
||||||
if (omorphiaLocaleData != null) {
|
|
||||||
localeFiles.push(...omorphiaLocaleData.files)
|
|
||||||
}
|
|
||||||
|
|
||||||
const cnDataImport = resolveCompactNumberDataImport(tag)
|
|
||||||
if (cnDataImport != null) {
|
|
||||||
;(locale.additionalImports ??= []).push({
|
|
||||||
from: cnDataImport,
|
|
||||||
resolve: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -331,52 +196,19 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modules: ['@vintl/nuxt', '@pinia/nuxt'],
|
modules: ['@nuxtjs/i18n', '@pinia/nuxt'],
|
||||||
vintl: {
|
i18n: {
|
||||||
defaultLocale: 'en-US',
|
defaultLocale: 'en-US',
|
||||||
locales: [
|
locales: LOCALES,
|
||||||
{
|
strategy: 'no_prefix',
|
||||||
tag: 'en-US',
|
detectBrowserLanguage: {
|
||||||
meta: {
|
useCookie: true,
|
||||||
static: {
|
cookieKey: 'locale',
|
||||||
iso: 'en',
|
fallbackLocale: 'en-US',
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
storage: 'cookie',
|
|
||||||
parserless: 'only-prod',
|
|
||||||
seo: {
|
|
||||||
defaultLocaleHasParameter: false,
|
|
||||||
},
|
|
||||||
onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) {
|
|
||||||
const errorMessage = String(error)
|
|
||||||
const modulePath = relative(__dirname, moduleId)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true })
|
|
||||||
|
|
||||||
consola.warn(
|
|
||||||
`[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`,
|
|
||||||
)
|
|
||||||
|
|
||||||
return fallback
|
|
||||||
} catch (err) {
|
|
||||||
const secondaryErrorMessage = String(err)
|
|
||||||
|
|
||||||
const reason =
|
|
||||||
errorMessage === secondaryErrorMessage
|
|
||||||
? errorMessage
|
|
||||||
: `${errorMessage} and ${secondaryErrorMessage}`
|
|
||||||
|
|
||||||
consola.warn(
|
|
||||||
`[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
vueI18n: './src/i18n.config.ts',
|
||||||
},
|
},
|
||||||
nitro: {
|
nitro: {
|
||||||
moduleSideEffects: ['@vintl/compact-number/locale-data'],
|
|
||||||
rollupConfig: {
|
rollupConfig: {
|
||||||
// @ts-expect-error it's not infinite.
|
// @ts-expect-error it's not infinite.
|
||||||
plugins: [serverSidedVue()],
|
plugins: [serverSidedVue()],
|
||||||
|
|||||||
@@ -16,12 +16,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^6.2.12",
|
"@formatjs/cli": "^6.2.12",
|
||||||
"@nuxt/devtools": "^1.3.3",
|
"@nuxt/devtools": "^1.3.3",
|
||||||
|
"@nuxtjs/i18n": "^8.5.5",
|
||||||
"@types/dompurify": "^3.0.5",
|
"@types/dompurify": "^3.0.5",
|
||||||
"@types/iso-3166-2": "^1.0.4",
|
"@types/iso-3166-2": "^1.0.4",
|
||||||
"@types/node": "^20.1.0",
|
"@types/node": "^20.1.0",
|
||||||
"@vintl/compact-number": "^2.0.5",
|
|
||||||
"@vintl/how-ago": "^3.0.1",
|
|
||||||
"@vintl/nuxt": "^1.9.2",
|
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"glob": "^10.2.7",
|
"glob": "^10.2.7",
|
||||||
@@ -48,7 +46,7 @@
|
|||||||
"@pinia/nuxt": "^0.5.1",
|
"@pinia/nuxt": "^0.5.1",
|
||||||
"@tanstack/vue-query": "^5.90.7",
|
"@tanstack/vue-query": "^5.90.7",
|
||||||
"@types/three": "^0.172.0",
|
"@types/three": "^0.172.0",
|
||||||
"@vintl/vintl": "^4.4.1",
|
"intl-messageformat": "^10.7.7",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"@vue-email/components": "^0.0.21",
|
"@vue-email/components": "^0.0.21",
|
||||||
"@vue-email/render": "^0.0.9",
|
"@vue-email/render": "^0.0.9",
|
||||||
|
|||||||
316
apps/frontend/src/components/ui/ModrinthFooter.vue
Normal file
316
apps/frontend/src/components/ui/ModrinthFooter.vue
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { BlueskyIcon, DiscordIcon, GithubIcon, MastodonIcon, TwitterIcon } from '@modrinth/assets'
|
||||||
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
defineMessage,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
|
type MessageDescriptor,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
|
import TextLogo from '~/components/brand/TextLogo.vue'
|
||||||
|
|
||||||
|
const flags = useFeatureFlags()
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
const { addNotification } = injectNotificationManager()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
modrinthInformation: {
|
||||||
|
id: 'layout.footer.modrinth-information',
|
||||||
|
defaultMessage: 'Modrinth information',
|
||||||
|
},
|
||||||
|
openSource: {
|
||||||
|
id: 'layout.footer.open-source',
|
||||||
|
defaultMessage: 'Modrinth is <github-link>open source</github-link>.',
|
||||||
|
},
|
||||||
|
legalDisclaimer: {
|
||||||
|
id: 'layout.footer.legal-disclaimer',
|
||||||
|
defaultMessage:
|
||||||
|
'NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const socialLinks: {
|
||||||
|
label: MessageDescriptor
|
||||||
|
href: string
|
||||||
|
icon: Component
|
||||||
|
rel?: string
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.social.discord', defaultMessage: 'Discord' }),
|
||||||
|
href: 'https://discord.modrinth.com',
|
||||||
|
icon: DiscordIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.social.bluesky', defaultMessage: 'Bluesky' }),
|
||||||
|
href: 'https://bsky.app/profile/modrinth.com',
|
||||||
|
icon: BlueskyIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.social.mastodon', defaultMessage: 'Mastodon' }),
|
||||||
|
href: 'https://floss.social/@modrinth',
|
||||||
|
icon: MastodonIcon,
|
||||||
|
rel: 'me',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.social.x', defaultMessage: 'X' }),
|
||||||
|
href: 'https://x.com/modrinth',
|
||||||
|
icon: TwitterIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.social.github', defaultMessage: 'GitHub' }),
|
||||||
|
href: 'https://github.com/modrinth',
|
||||||
|
icon: GithubIcon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const footerLinks: {
|
||||||
|
label: MessageDescriptor
|
||||||
|
links: {
|
||||||
|
href: string
|
||||||
|
label: MessageDescriptor
|
||||||
|
}[]
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.about', defaultMessage: 'About' }),
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
href: '/news',
|
||||||
|
label: defineMessage({ id: 'layout.footer.about.news', defaultMessage: 'News' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/news/changelog',
|
||||||
|
label: defineMessage({ id: 'layout.footer.about.changelog', defaultMessage: 'Changelog' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://status.modrinth.com',
|
||||||
|
label: defineMessage({ id: 'layout.footer.about.status', defaultMessage: 'Status' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://careers.modrinth.com',
|
||||||
|
label: defineMessage({ id: 'layout.footer.about.careers', defaultMessage: 'Careers' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/legal/cmp-info',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.about.rewards-program',
|
||||||
|
defaultMessage: 'Rewards Program',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.products', defaultMessage: 'Products' }),
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
href: '/plus',
|
||||||
|
label: defineMessage({ id: 'layout.footer.products.plus', defaultMessage: 'Modrinth+' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/app',
|
||||||
|
label: defineMessage({ id: 'layout.footer.products.app', defaultMessage: 'Modrinth App' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/hosting',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.products.servers',
|
||||||
|
defaultMessage: 'Modrinth Hosting',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.resources', defaultMessage: 'Resources' }),
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
href: 'https://support.modrinth.com',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.resources.help-center',
|
||||||
|
defaultMessage: 'Help Center',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://translate.modrinth.com',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.resources.translate',
|
||||||
|
defaultMessage: 'Translate',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://github.com/modrinth/code/issues',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.resources.report-issues',
|
||||||
|
defaultMessage: 'Report issues',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: 'https://docs.modrinth.com/api/',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.resources.api-docs',
|
||||||
|
defaultMessage: 'API documentation',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: defineMessage({ id: 'layout.footer.legal', defaultMessage: 'Legal' }),
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
href: '/legal/rules',
|
||||||
|
label: defineMessage({ id: 'layout.footer.legal.rules', defaultMessage: 'Content Rules' }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/legal/terms',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.legal.terms-of-use',
|
||||||
|
defaultMessage: 'Terms of Use',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/legal/privacy',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.legal.privacy-policy',
|
||||||
|
defaultMessage: 'Privacy Policy',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/legal/security',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.legal.security-notice',
|
||||||
|
defaultMessage: 'Security Notice',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: '/legal/copyright',
|
||||||
|
label: defineMessage({
|
||||||
|
id: 'layout.footer.legal.copyright-policy',
|
||||||
|
defaultMessage: 'Copyright Policy and DMCA',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const developerModeCounter = ref(0)
|
||||||
|
|
||||||
|
const state = useGeneratedState()
|
||||||
|
|
||||||
|
function developerModeIncrement() {
|
||||||
|
if (developerModeCounter.value >= 5) {
|
||||||
|
flags.value.developerMode = !flags.value.developerMode
|
||||||
|
developerModeCounter.value = 0
|
||||||
|
saveFeatureFlags()
|
||||||
|
if (flags.value.developerMode) {
|
||||||
|
addNotification({
|
||||||
|
title: 'Developer mode activated',
|
||||||
|
text: 'Developer mode has been enabled',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addNotification({
|
||||||
|
title: 'Developer mode deactivated',
|
||||||
|
text: 'Developer mode has been disabled',
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
developerModeCounter.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<footer
|
||||||
|
class="footer-brand-background experimental-styles-within border-0 border-t-[1px] border-solid"
|
||||||
|
>
|
||||||
|
<div class="mx-auto flex max-w-screen-xl flex-col gap-6 p-6 pb-20 sm:px-12 md:py-12">
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-1 gap-4 text-primary md:grid-cols-[1fr_2fr] lg:grid-cols-[auto_auto_auto_auto_auto]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex flex-col items-center gap-3 md:items-start"
|
||||||
|
role="region"
|
||||||
|
:aria-label="formatMessage(messages.modrinthInformation)"
|
||||||
|
>
|
||||||
|
<TextLogo
|
||||||
|
aria-hidden="true"
|
||||||
|
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
|
||||||
|
@click="developerModeIncrement()"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-wrap justify-center gap-px sm:-mx-2">
|
||||||
|
<ButtonStyled
|
||||||
|
v-for="(social, index) in socialLinks"
|
||||||
|
:key="`footer-social-${index}`"
|
||||||
|
circular
|
||||||
|
type="transparent"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
v-tooltip="formatMessage(social.label)"
|
||||||
|
:href="social.href"
|
||||||
|
target="_blank"
|
||||||
|
:rel="`noopener${social.rel ? ` ${social.rel}` : ''}`"
|
||||||
|
>
|
||||||
|
<component :is="social.icon" class="h-5 w-5" />
|
||||||
|
</a>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex flex-wrap justify-center gap-3 md:flex-col">
|
||||||
|
<p class="m-0">
|
||||||
|
<IntlFormatted :message-id="messages.openSource">
|
||||||
|
<template #github-link="{ children }">
|
||||||
|
<a
|
||||||
|
href="https://github.com/modrinth/code"
|
||||||
|
class="text-brand hover:underline"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
<component :is="() => children" />
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</p>
|
||||||
|
<p class="m-0">© {{ state.buildYear ?? '2025' }} Rinth, Inc.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:contents">
|
||||||
|
<div
|
||||||
|
v-for="group in footerLinks"
|
||||||
|
:key="group.label.id"
|
||||||
|
class="flex flex-col items-center gap-3 sm:items-start"
|
||||||
|
>
|
||||||
|
<h3 class="m-0 text-base text-contrast">{{ formatMessage(group.label) }}</h3>
|
||||||
|
<template v-for="item in group.links" :key="item.label">
|
||||||
|
<nuxt-link
|
||||||
|
v-if="item.href.startsWith('/')"
|
||||||
|
:to="item.href"
|
||||||
|
class="w-fit hover:underline"
|
||||||
|
>
|
||||||
|
{{ formatMessage(item.label) }}
|
||||||
|
</nuxt-link>
|
||||||
|
<a
|
||||||
|
v-else
|
||||||
|
:href="item.href"
|
||||||
|
class="w-fit hover:underline"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>
|
||||||
|
{{ formatMessage(item.label) }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center text-center text-xs font-medium text-secondary opacity-50">
|
||||||
|
{{ formatMessage(messages.legalDisclaimer) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.footer-brand-background {
|
||||||
|
background: var(--brand-gradient-strong-bg);
|
||||||
|
border-color: var(--brand-gradient-border);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -24,9 +24,14 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CheckIcon, XIcon } from '@modrinth/assets'
|
import { CheckIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
type MessageDescriptor,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import type { Project, User, Version } from '@modrinth/utils'
|
import type { Project, User, Version } from '@modrinth/utils'
|
||||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { acceptTeamInvite, removeTeamMember } from '~/helpers/teams.js'
|
import { acceptTeamInvite, removeTeamMember } from '~/helpers/teams.js'
|
||||||
|
|||||||
@@ -87,7 +87,13 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Labrinth } from '@modrinth/api-client'
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
import { Admonition, DropzoneFileInput, injectProjectPageContext } from '@modrinth/ui'
|
import {
|
||||||
|
Admonition,
|
||||||
|
defineMessages,
|
||||||
|
DropzoneFileInput,
|
||||||
|
injectProjectPageContext,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { acceptFileFromProjectType } from '@modrinth/utils'
|
import { acceptFileFromProjectType } from '@modrinth/utils'
|
||||||
|
|
||||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||||
|
|||||||
@@ -58,8 +58,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlusIcon, XIcon } from '@modrinth/assets'
|
import { PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, injectNotificationManager, NewModal } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages } from '@vintl/vintl'
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
NewModal,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import CreateLimitAlert from './CreateLimitAlert.vue'
|
import CreateLimitAlert from './CreateLimitAlert.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MessageIcon } from '@modrinth/assets'
|
import { MessageIcon } from '@modrinth/assets'
|
||||||
import { Admonition, ButtonStyled } from '@modrinth/ui'
|
import { Admonition, ButtonStyled, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { capitalizeString } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import { defineMessages } from '@vintl/vintl'
|
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -81,8 +81,13 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PlusIcon, XIcon } from '@modrinth/assets'
|
import { PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, injectNotificationManager, NewModal } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages } from '@vintl/vintl'
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
NewModal,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import CreateLimitAlert from './CreateLimitAlert.vue'
|
import CreateLimitAlert from './CreateLimitAlert.vue'
|
||||||
|
|||||||
@@ -95,8 +95,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlusIcon, XIcon } from '@modrinth/assets'
|
import { PlusIcon, XIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, Chips, injectNotificationManager, NewModal } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages } from '@vintl/vintl'
|
ButtonStyled,
|
||||||
|
Chips,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
NewModal,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import CreateLimitAlert from './CreateLimitAlert.vue'
|
import CreateLimitAlert from './CreateLimitAlert.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -162,12 +162,13 @@ import {
|
|||||||
Admonition,
|
Admonition,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
Chips,
|
Chips,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
NewModal,
|
NewModal,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import { type FormRequestResponse, useAvalara1099 } from '@/composables/avalara1099'
|
import { type FormRequestResponse, useAvalara1099 } from '@/composables/avalara1099'
|
||||||
|
|
||||||
|
|||||||
@@ -116,8 +116,14 @@ import {
|
|||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { ButtonStyled, commonMessages, injectNotificationManager, NewModal } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
NewModal,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'
|
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -49,9 +49,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, Combobox, commonMessages, formFieldPlaceholders } from '@modrinth/ui'
|
import {
|
||||||
|
ButtonStyled,
|
||||||
|
Combobox,
|
||||||
|
commonMessages,
|
||||||
|
formFieldPlaceholders,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, nextTick, ref, watch } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
|||||||
@@ -57,8 +57,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { LoaderCircleIcon } from '@modrinth/assets'
|
import { LoaderCircleIcon } from '@modrinth/assets'
|
||||||
|
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
|||||||
@@ -124,10 +124,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { normalizeChildren } from '@modrinth/ui'
|
import { defineMessages, IntlFormatted, normalizeChildren, useVIntl } from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import ConfettiExplosion from 'vue-confetti-explosion'
|
import ConfettiExplosion from 'vue-confetti-explosion'
|
||||||
|
|||||||
@@ -107,12 +107,13 @@ import { CheckIcon, PayPalColorIcon, SaveIcon, XIcon } from '@modrinth/assets'
|
|||||||
import {
|
import {
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
defineMessages,
|
||||||
financialMessages,
|
financialMessages,
|
||||||
formFieldLabels,
|
formFieldLabels,
|
||||||
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -83,13 +83,14 @@ import {
|
|||||||
Admonition,
|
Admonition,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
Combobox,
|
Combobox,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { useGeolocation } from '@vueuse/core'
|
import { useGeolocation } from '@vueuse/core'
|
||||||
|
|
||||||
import { useCountries, useFormattedCountries, useUserCountry } from '@/composables/country.ts'
|
import { useCountries, useFormattedCountries, useUserCountry } from '@/composables/country.ts'
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ import {
|
|||||||
Admonition,
|
Admonition,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Combobox,
|
Combobox,
|
||||||
|
defineMessages,
|
||||||
financialMessages,
|
financialMessages,
|
||||||
formFieldLabels,
|
formFieldLabels,
|
||||||
formFieldPlaceholders,
|
formFieldPlaceholders,
|
||||||
@@ -211,10 +212,10 @@ import {
|
|||||||
getBlockchainIcon,
|
getBlockchainIcon,
|
||||||
getCurrencyColor,
|
getCurrencyColor,
|
||||||
getCurrencyIcon,
|
getCurrencyIcon,
|
||||||
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -218,8 +218,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Chips, Combobox, formFieldLabels, formFieldPlaceholders } from '@modrinth/ui'
|
import {
|
||||||
import { useVIntl } from '@vintl/vintl'
|
Chips,
|
||||||
|
Combobox,
|
||||||
|
defineMessages,
|
||||||
|
formFieldLabels,
|
||||||
|
formFieldPlaceholders,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
// TODO: Switch to using Muralpay's improved endpoint when it's available.
|
// TODO: Switch to using Muralpay's improved endpoint when it's available.
|
||||||
import iso3166 from 'iso-3166-2'
|
import iso3166 from 'iso-3166-2'
|
||||||
|
|
||||||
|
|||||||
@@ -74,10 +74,15 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FileTextIcon } from '@modrinth/assets'
|
import { FileTextIcon } from '@modrinth/assets'
|
||||||
import { Admonition, ButtonStyled, normalizeChildren } from '@modrinth/ui'
|
import {
|
||||||
|
Admonition,
|
||||||
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
IntlFormatted,
|
||||||
|
normalizeChildren,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { TAX_THRESHOLD_ACTUAL } from '@/providers/creator-withdraw.ts'
|
import { TAX_THRESHOLD_ACTUAL } from '@/providers/creator-withdraw.ts'
|
||||||
|
|||||||
@@ -356,16 +356,17 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
Chips,
|
Chips,
|
||||||
Combobox,
|
Combobox,
|
||||||
|
defineMessages,
|
||||||
financialMessages,
|
financialMessages,
|
||||||
formFieldLabels,
|
formFieldLabels,
|
||||||
formFieldPlaceholders,
|
formFieldPlaceholders,
|
||||||
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
paymentMethodMessages,
|
paymentMethodMessages,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -89,9 +89,8 @@ import {
|
|||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import type { Nag, NagContext, NagStatus } from '@modrinth/moderation'
|
import type { Nag, NagContext, NagStatus } from '@modrinth/moderation'
|
||||||
import { nags } from '@modrinth/moderation'
|
import { nags } from '@modrinth/moderation'
|
||||||
import { ButtonStyled } from '@modrinth/ui'
|
import { ButtonStyled, defineMessages, type MessageDescriptor, useVIntl } from '@modrinth/ui'
|
||||||
import type { Project, User, Version } from '@modrinth/utils'
|
import type { Project, User, Version } from '@modrinth/utils'
|
||||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NewspaperIcon } from '@modrinth/assets'
|
import { NewspaperIcon } from '@modrinth/assets'
|
||||||
import { articles as rawArticles } from '@modrinth/blog'
|
import { articles as rawArticles } from '@modrinth/blog'
|
||||||
import { ButtonStyled, NewsArticleCard } from '@modrinth/ui'
|
import { ButtonStyled, defineMessages, NewsArticleCard, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ import {
|
|||||||
UpdatedIcon,
|
UpdatedIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { ButtonStyled, Checkbox, NewModal, ServerInfoLabels } from '@modrinth/ui'
|
import { ButtonStyled, Checkbox, NewModal, ServerInfoLabels, useVIntl } from '@modrinth/ui'
|
||||||
import type { PowerAction as ServerPowerAction, ServerState } from '@modrinth/utils'
|
import type { PowerAction as ServerPowerAction, ServerState } from '@modrinth/utils'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
NewModal,
|
NewModal,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { type Loaders, ModrinthServersFetchError } from '@modrinth/utils'
|
import { type Loaders, ModrinthServersFetchError } from '@modrinth/utils'
|
||||||
import { $fetch } from 'ofetch'
|
import { $fetch } from 'ofetch'
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CompassIcon, InfoIcon, SettingsIcon, TransferIcon, UploadIcon } from '@modrinth/assets'
|
import { CompassIcon, InfoIcon, SettingsIcon, TransferIcon, UploadIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, NewProjectCard } from '@modrinth/ui'
|
import { ButtonStyled, NewProjectCard, useVIntl } from '@modrinth/ui'
|
||||||
import type { Loaders } from '@modrinth/utils'
|
import type { Loaders } from '@modrinth/utils'
|
||||||
|
|
||||||
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
|
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ButtonStyled, ServersSpecs } from '@modrinth/ui'
|
import type { MessageDescriptor } from '@modrinth/ui'
|
||||||
|
import { ButtonStyled, defineMessage, ServersSpecs, useVIntl } from '@modrinth/ui'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
import { formatPrice } from '@modrinth/utils'
|
||||||
import type { MessageDescriptor } from '@vintl/vintl'
|
|
||||||
|
|
||||||
const { formatMessage, locale } = useVIntl()
|
const { formatMessage, locale } = useVIntl()
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
ServerNotice,
|
ServerNotice,
|
||||||
TagItem,
|
TagItem,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface GeneratedState extends Labrinth.State.GeneratedState {
|
|||||||
// Metadata
|
// Metadata
|
||||||
lastGenerated?: string
|
lastGenerated?: string
|
||||||
apiUrl?: string
|
apiUrl?: string
|
||||||
|
buildYear: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,4 +122,6 @@ export const useGeneratedState = () =>
|
|||||||
lastGenerated: generatedState.lastGenerated,
|
lastGenerated: generatedState.lastGenerated,
|
||||||
apiUrl: generatedState.apiUrl,
|
apiUrl: generatedState.apiUrl,
|
||||||
errors: generatedState.errors,
|
errors: generatedState.errors,
|
||||||
|
|
||||||
|
buildYear: new Date().getFullYear(),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -53,13 +53,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { SadRinthbot } from '@modrinth/assets'
|
import { SadRinthbot } from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
|
defineMessage,
|
||||||
|
IntlFormatted,
|
||||||
NotificationPanel,
|
NotificationPanel,
|
||||||
provideModrinthClient,
|
provideModrinthClient,
|
||||||
provideNotificationManager,
|
provideNotificationManager,
|
||||||
providePageContext,
|
providePageContext,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessage, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import Logo404 from '~/assets/images/404.svg'
|
import Logo404 from '~/assets/images/404.svg'
|
||||||
|
|
||||||
|
|||||||
15
apps/frontend/src/i18n.config.ts
Normal file
15
apps/frontend/src/i18n.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { buildLocaleMessages, createMessageCompiler, type CrowdinMessages } from '@modrinth/ui'
|
||||||
|
|
||||||
|
const localeModules = import.meta.glob<{ default: CrowdinMessages }>('./locales/*/index.json', {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineI18nConfig(() => ({
|
||||||
|
legacy: false,
|
||||||
|
locale: 'en-US',
|
||||||
|
fallbackLocale: 'en-US',
|
||||||
|
messageCompiler: createMessageCompiler(),
|
||||||
|
missingWarn: false,
|
||||||
|
fallbackWarn: false,
|
||||||
|
messages: buildLocaleMessages(localeModules),
|
||||||
|
}))
|
||||||
@@ -821,93 +821,7 @@
|
|||||||
<BatchCreditModal v-if="auth.user && isAdmin(auth.user)" ref="modal_batch_credit" />
|
<BatchCreditModal v-if="auth.user && isAdmin(auth.user)" ref="modal_batch_credit" />
|
||||||
<slot id="main" />
|
<slot id="main" />
|
||||||
</main>
|
</main>
|
||||||
<footer
|
<ModrinthFooter />
|
||||||
class="footer-brand-background experimental-styles-within border-0 border-t-[1px] border-solid"
|
|
||||||
>
|
|
||||||
<div class="mx-auto flex max-w-screen-xl flex-col gap-6 p-6 pb-20 sm:px-12 md:py-12">
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-1 gap-4 text-primary md:grid-cols-[1fr_2fr] lg:grid-cols-[auto_auto_auto_auto_auto]"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex flex-col items-center gap-3 md:items-start"
|
|
||||||
role="region"
|
|
||||||
:aria-label="formatMessage(messages.modrinthInformation)"
|
|
||||||
>
|
|
||||||
<TextLogo
|
|
||||||
aria-hidden="true"
|
|
||||||
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
|
|
||||||
@click="developerModeIncrement()"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-wrap justify-center gap-px sm:-mx-2">
|
|
||||||
<ButtonStyled
|
|
||||||
v-for="(social, index) in socialLinks"
|
|
||||||
:key="`footer-social-${index}`"
|
|
||||||
circular
|
|
||||||
type="transparent"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
v-tooltip="social.label"
|
|
||||||
:href="social.href"
|
|
||||||
target="_blank"
|
|
||||||
:rel="`noopener${social.rel ? ` ${social.rel}` : ''}`"
|
|
||||||
>
|
|
||||||
<component :is="social.icon" class="h-5 w-5" />
|
|
||||||
</a>
|
|
||||||
</ButtonStyled>
|
|
||||||
</div>
|
|
||||||
<div class="mt-auto flex flex-wrap justify-center gap-3 md:flex-col">
|
|
||||||
<p class="m-0">
|
|
||||||
<IntlFormatted :message-id="footerMessages.openSource">
|
|
||||||
<template #github-link="{ children }">
|
|
||||||
<a
|
|
||||||
href="https://github.com/modrinth/code"
|
|
||||||
class="text-brand hover:underline"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
<component :is="() => children" />
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</IntlFormatted>
|
|
||||||
</p>
|
|
||||||
<p class="m-0">
|
|
||||||
{{ formatMessage(footerMessages.copyright, { year: currentYear }) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-4 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:contents">
|
|
||||||
<div
|
|
||||||
v-for="group in footerLinks"
|
|
||||||
:key="group.label"
|
|
||||||
class="flex flex-col items-center gap-3 sm:items-start"
|
|
||||||
>
|
|
||||||
<h3 class="m-0 text-base text-contrast">{{ group.label }}</h3>
|
|
||||||
<template v-for="item in group.links" :key="item.label">
|
|
||||||
<nuxt-link
|
|
||||||
v-if="item.href.startsWith('/')"
|
|
||||||
:to="item.href"
|
|
||||||
class="w-fit hover:underline"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</nuxt-link>
|
|
||||||
<a
|
|
||||||
v-else
|
|
||||||
:href="item.href"
|
|
||||||
class="w-fit hover:underline"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
>
|
|
||||||
{{ item.label }}
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center text-center text-xs font-medium text-secondary opacity-50">
|
|
||||||
{{ formatMessage(footerMessages.legalDisclaimer) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -915,7 +829,6 @@ import {
|
|||||||
AffiliateIcon,
|
AffiliateIcon,
|
||||||
ArrowBigUpDashIcon,
|
ArrowBigUpDashIcon,
|
||||||
BellIcon,
|
BellIcon,
|
||||||
BlueskyIcon,
|
|
||||||
BookTextIcon,
|
BookTextIcon,
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
BracesIcon,
|
BracesIcon,
|
||||||
@@ -923,12 +836,10 @@ import {
|
|||||||
CollectionIcon,
|
CollectionIcon,
|
||||||
CompassIcon,
|
CompassIcon,
|
||||||
CurrencyIcon,
|
CurrencyIcon,
|
||||||
DiscordIcon,
|
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
DropdownIcon,
|
DropdownIcon,
|
||||||
FileIcon,
|
FileIcon,
|
||||||
FileTextIcon,
|
FileTextIcon,
|
||||||
GithubIcon,
|
|
||||||
GlassesIcon,
|
GlassesIcon,
|
||||||
HamburgerIcon,
|
HamburgerIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
@@ -936,7 +847,6 @@ import {
|
|||||||
LibraryIcon,
|
LibraryIcon,
|
||||||
LogInIcon,
|
LogInIcon,
|
||||||
LogOutIcon,
|
LogOutIcon,
|
||||||
MastodonIcon,
|
|
||||||
MessageIcon,
|
MessageIcon,
|
||||||
ModrinthIcon,
|
ModrinthIcon,
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
@@ -952,7 +862,6 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
ShieldAlertIcon,
|
ShieldAlertIcon,
|
||||||
SunIcon,
|
SunIcon,
|
||||||
TwitterIcon,
|
|
||||||
UserIcon,
|
UserIcon,
|
||||||
UserSearchIcon,
|
UserSearchIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
@@ -963,12 +872,13 @@ import {
|
|||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
commonProjectTypeCategoryMessages,
|
commonProjectTypeCategoryMessages,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
PagewideBanner,
|
PagewideBanner,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
|
import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import TextLogo from '~/components/brand/TextLogo.vue'
|
import TextLogo from '~/components/brand/TextLogo.vue'
|
||||||
import BatchCreditModal from '~/components/ui/admin/BatchCreditModal.vue'
|
import BatchCreditModal from '~/components/ui/admin/BatchCreditModal.vue'
|
||||||
@@ -976,6 +886,7 @@ import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.
|
|||||||
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
import OrganizationCreateModal from '~/components/ui/create/OrganizationCreateModal.vue'
|
||||||
import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
|
import ProjectCreateModal from '~/components/ui/create/ProjectCreateModal.vue'
|
||||||
import CreatorTaxFormModal from '~/components/ui/dashboard/CreatorTaxFormModal.vue'
|
import CreatorTaxFormModal from '~/components/ui/dashboard/CreatorTaxFormModal.vue'
|
||||||
|
import ModrinthFooter from '~/components/ui/ModrinthFooter.vue'
|
||||||
import TeleportOverflowMenu from '~/components/ui/servers/TeleportOverflowMenu.vue'
|
import TeleportOverflowMenu from '~/components/ui/servers/TeleportOverflowMenu.vue'
|
||||||
import { errors as generatedStateErrors } from '~/generated/state.json'
|
import { errors as generatedStateErrors } from '~/generated/state.json'
|
||||||
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
||||||
@@ -1196,10 +1107,6 @@ const messages = defineMessages({
|
|||||||
id: 'layout.nav.modrinth-home-page',
|
id: 'layout.nav.modrinth-home-page',
|
||||||
defaultMessage: 'Modrinth home page',
|
defaultMessage: 'Modrinth home page',
|
||||||
},
|
},
|
||||||
modrinthInformation: {
|
|
||||||
id: 'layout.footer.modrinth-information',
|
|
||||||
defaultMessage: 'Modrinth information',
|
|
||||||
},
|
|
||||||
createNew: {
|
createNew: {
|
||||||
id: 'layout.action.create-new',
|
id: 'layout.action.create-new',
|
||||||
defaultMessage: 'Create new...',
|
defaultMessage: 'Create new...',
|
||||||
@@ -1294,22 +1201,6 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const footerMessages = defineMessages({
|
|
||||||
openSource: {
|
|
||||||
id: 'layout.footer.open-source',
|
|
||||||
defaultMessage: 'Modrinth is <github-link>open source</github-link>.',
|
|
||||||
},
|
|
||||||
legalDisclaimer: {
|
|
||||||
id: 'layout.footer.legal-disclaimer',
|
|
||||||
defaultMessage:
|
|
||||||
'NOT AN OFFICIAL MINECRAFT SERVICE. NOT APPROVED BY OR ASSOCIATED WITH MOJANG OR MICROSOFT.',
|
|
||||||
},
|
|
||||||
copyright: {
|
|
||||||
id: 'layout.footer.copyright',
|
|
||||||
defaultMessage: '© {year} Rinth, Inc.',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
link: [
|
link: [
|
||||||
{
|
{
|
||||||
@@ -1348,10 +1239,6 @@ useSeoMeta({
|
|||||||
twitterSite: '@modrinth',
|
twitterSite: '@modrinth',
|
||||||
})
|
})
|
||||||
|
|
||||||
const developerModeCounter = ref(0)
|
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear()
|
|
||||||
|
|
||||||
const isMobileMenuOpen = ref(false)
|
const isMobileMenuOpen = ref(false)
|
||||||
const isBrowseMenuOpen = ref(false)
|
const isBrowseMenuOpen = ref(false)
|
||||||
const navRoutes = computed(() => [
|
const navRoutes = computed(() => [
|
||||||
@@ -1547,29 +1434,6 @@ watch(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
function developerModeIncrement() {
|
|
||||||
if (developerModeCounter.value >= 5) {
|
|
||||||
flags.value.developerMode = !flags.value.developerMode
|
|
||||||
developerModeCounter.value = 0
|
|
||||||
saveFeatureFlags()
|
|
||||||
if (flags.value.developerMode) {
|
|
||||||
addNotification({
|
|
||||||
title: 'Developer mode activated',
|
|
||||||
text: 'Developer mode has been enabled',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
addNotification({
|
|
||||||
title: 'Developer mode deactivated',
|
|
||||||
text: 'Developer mode has been disabled',
|
|
||||||
type: 'success',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
developerModeCounter.value++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logoutUser() {
|
async function logoutUser() {
|
||||||
await logout()
|
await logout()
|
||||||
}
|
}
|
||||||
@@ -1620,196 +1484,6 @@ function hideRussiaCensorshipBanner() {
|
|||||||
flags.value.hideRussiaCensorshipBanner = true
|
flags.value.hideRussiaCensorshipBanner = true
|
||||||
saveFeatureFlags()
|
saveFeatureFlags()
|
||||||
}
|
}
|
||||||
|
|
||||||
const socialLinks = [
|
|
||||||
{
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.social.discord', defaultMessage: 'Discord' }),
|
|
||||||
),
|
|
||||||
href: 'https://discord.modrinth.com',
|
|
||||||
icon: DiscordIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.social.bluesky', defaultMessage: 'Bluesky' }),
|
|
||||||
),
|
|
||||||
href: 'https://bsky.app/profile/modrinth.com',
|
|
||||||
icon: BlueskyIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.social.mastodon', defaultMessage: 'Mastodon' }),
|
|
||||||
),
|
|
||||||
href: 'https://floss.social/@modrinth',
|
|
||||||
icon: MastodonIcon,
|
|
||||||
rel: 'me',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(defineMessage({ id: 'layout.footer.social.x', defaultMessage: 'X' })),
|
|
||||||
href: 'https://x.com/modrinth',
|
|
||||||
icon: TwitterIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.social.github', defaultMessage: 'GitHub' }),
|
|
||||||
),
|
|
||||||
href: 'https://github.com/modrinth',
|
|
||||||
icon: GithubIcon,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const footerLinks = [
|
|
||||||
{
|
|
||||||
label: formatMessage(defineMessage({ id: 'layout.footer.about', defaultMessage: 'About' })),
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
href: '/news',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.about.news', defaultMessage: 'News' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/news/changelog',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.about.changelog', defaultMessage: 'Changelog' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://status.modrinth.com',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.about.status', defaultMessage: 'Status' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://careers.modrinth.com',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.about.careers', defaultMessage: 'Careers' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/legal/cmp-info',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.about.rewards-program',
|
|
||||||
defaultMessage: 'Rewards Program',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.products', defaultMessage: 'Products' }),
|
|
||||||
),
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
href: '/plus',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.products.plus', defaultMessage: 'Modrinth+' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/app',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.products.app', defaultMessage: 'Modrinth App' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/hosting',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.products.servers',
|
|
||||||
defaultMessage: 'Modrinth Hosting',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.resources', defaultMessage: 'Resources' }),
|
|
||||||
),
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
href: 'https://support.modrinth.com',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.resources.help-center',
|
|
||||||
defaultMessage: 'Help Center',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://translate.modrinth.com',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.resources.translate', defaultMessage: 'Translate' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://github.com/modrinth/code/issues',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.resources.report-issues',
|
|
||||||
defaultMessage: 'Report issues',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: 'https://docs.modrinth.com/api/',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.resources.api-docs',
|
|
||||||
defaultMessage: 'API documentation',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: formatMessage(defineMessage({ id: 'layout.footer.legal', defaultMessage: 'Legal' })),
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
href: '/legal/rules',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.legal.rules', defaultMessage: 'Content Rules' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/legal/terms',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({ id: 'layout.footer.legal.terms-of-use', defaultMessage: 'Terms of Use' }),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/legal/privacy',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.legal.privacy-policy',
|
|
||||||
defaultMessage: 'Privacy Policy',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/legal/security',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.legal.security-notice',
|
|
||||||
defaultMessage: 'Security Notice',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
href: '/legal/copyright',
|
|
||||||
label: formatMessage(
|
|
||||||
defineMessage({
|
|
||||||
id: 'layout.footer.legal.copyright-policy',
|
|
||||||
defaultMessage: 'Copyright Policy and DMCA',
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -2050,11 +1724,6 @@ const footerLinks = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-brand-background {
|
|
||||||
background: var(--brand-gradient-strong-bg);
|
|
||||||
border-color: var(--brand-gradient-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.over-the-top-random-animation {
|
.over-the-top-random-animation {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
@@ -1364,9 +1364,6 @@
|
|||||||
"layout.footer.about.status": {
|
"layout.footer.about.status": {
|
||||||
"message": "Status"
|
"message": "Status"
|
||||||
},
|
},
|
||||||
"layout.footer.copyright": {
|
|
||||||
"message": "© {year} Rinth, Inc."
|
|
||||||
},
|
|
||||||
"layout.footer.legal": {
|
"layout.footer.legal": {
|
||||||
"message": "Legal"
|
"message": "Legal"
|
||||||
},
|
},
|
||||||
@@ -2813,18 +2810,9 @@
|
|||||||
"settings.display.theme.title": {
|
"settings.display.theme.title": {
|
||||||
"message": "Color theme"
|
"message": "Color theme"
|
||||||
},
|
},
|
||||||
"settings.language.categories.auto": {
|
|
||||||
"message": "Automatic"
|
|
||||||
},
|
|
||||||
"settings.language.categories.default": {
|
"settings.language.categories.default": {
|
||||||
"message": "Standard languages"
|
"message": "Standard languages"
|
||||||
},
|
},
|
||||||
"settings.language.categories.experimental": {
|
|
||||||
"message": "Experimental languages"
|
|
||||||
},
|
|
||||||
"settings.language.categories.fun": {
|
|
||||||
"message": "Fun languages"
|
|
||||||
},
|
|
||||||
"settings.language.categories.search-result": {
|
"settings.language.categories.search-result": {
|
||||||
"message": "Search results"
|
"message": "Search results"
|
||||||
},
|
},
|
||||||
@@ -2834,18 +2822,6 @@
|
|||||||
"settings.language.languages.automatic": {
|
"settings.language.languages.automatic": {
|
||||||
"message": "Sync with the system language"
|
"message": "Sync with the system language"
|
||||||
},
|
},
|
||||||
"settings.language.languages.language-label-applying": {
|
|
||||||
"message": "{label}. Applying..."
|
|
||||||
},
|
|
||||||
"settings.language.languages.language-label-error": {
|
|
||||||
"message": "{label}. Error"
|
|
||||||
},
|
|
||||||
"settings.language.languages.load-failed": {
|
|
||||||
"message": "Cannot load this language. Try again in a bit."
|
|
||||||
},
|
|
||||||
"settings.language.languages.search-field.description": {
|
|
||||||
"message": "Submit to focus the first search result"
|
|
||||||
},
|
|
||||||
"settings.language.languages.search-field.placeholder": {
|
"settings.language.languages.search-field.placeholder": {
|
||||||
"message": "Search for a language..."
|
"message": "Search for a language..."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -970,7 +970,9 @@ import {
|
|||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
NewModal,
|
NewModal,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
PopoutMenu,
|
PopoutMenu,
|
||||||
@@ -985,10 +987,10 @@ import {
|
|||||||
ServersPromo,
|
ServersPromo,
|
||||||
TagItem,
|
TagItem,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
||||||
import { formatCategory, formatPrice, formatProjectType, renderString } from '@modrinth/utils'
|
import { formatCategory, formatPrice, formatProjectType, renderString } from '@modrinth/utils'
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import { useLocalStorage } from '@vueuse/core'
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
commonMessages,
|
commonMessages,
|
||||||
commonProjectSettingsMessages,
|
commonProjectSettingsMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { Project, ProjectV3Partial } from '@modrinth/utils'
|
import type { Project, ProjectV3Partial } from '@modrinth/utils'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import { useLocalStorage, useScroll } from '@vueuse/core'
|
import { useLocalStorage, useScroll } from '@vueuse/core'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import { CheckIcon } from '@modrinth/assets'
|
|||||||
import {
|
import {
|
||||||
Admonition,
|
Admonition,
|
||||||
commonProjectSettingsMessages,
|
commonProjectSettingsMessages,
|
||||||
|
defineMessages,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
ProjectSettingsEnvSelector,
|
ProjectSettingsEnvSelector,
|
||||||
UnsavedChangesPopup,
|
UnsavedChangesPopup,
|
||||||
useSavable,
|
useSavable,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
|
defineMessages,
|
||||||
IconSelect,
|
IconSelect,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
|
type MessageDescriptor,
|
||||||
SettingsLabel,
|
SettingsLabel,
|
||||||
UnsavedChangesPopup,
|
UnsavedChangesPopup,
|
||||||
useSavable,
|
useSavable,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
|
|||||||
@@ -326,11 +326,13 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
|
defineMessages,
|
||||||
DropdownSelect,
|
DropdownSelect,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
NewModal,
|
NewModal,
|
||||||
Toggle,
|
Toggle,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatCategory, formatPrice } from '@modrinth/utils'
|
import { formatCategory, formatPrice } from '@modrinth/utils'
|
||||||
import { DEFAULT_CREDIT_EMAIL_MESSAGE } from '@modrinth/utils/utils.ts'
|
import { DEFAULT_CREDIT_EMAIL_MESSAGE } from '@modrinth/utils/utils.ts'
|
||||||
|
|||||||
@@ -267,16 +267,17 @@ import {
|
|||||||
Combobox,
|
Combobox,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
NewModal,
|
NewModal,
|
||||||
ServerNotice,
|
ServerNotice,
|
||||||
TagItem,
|
TagItem,
|
||||||
Toggle,
|
Toggle,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { NOTICE_LEVELS } from '@modrinth/ui/src/utils/notices.ts'
|
import { NOTICE_LEVELS } from '@modrinth/ui/src/utils/notices.ts'
|
||||||
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,15 @@ import {
|
|||||||
SendIcon,
|
SendIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Badge, Checkbox, commonMessages } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
Avatar,
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
Badge,
|
||||||
|
Checkbox,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
IntlFormatted,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import ATLauncher from '~/assets/images/external/atlauncher.svg?component'
|
import ATLauncher from '~/assets/images/external/atlauncher.svg?component'
|
||||||
import CurseForge from '~/assets/images/external/curseforge.svg?component'
|
import CurseForge from '~/assets/images/external/curseforge.svg?component'
|
||||||
|
|||||||
@@ -85,10 +85,12 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
commonMessages,
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import { useAuth } from '@/composables/auth.js'
|
import { useAuth } from '@/composables/auth.js'
|
||||||
import { useScopes } from '@/composables/auth/scopes.ts'
|
import { useScopes } from '@/composables/auth/scopes.ts'
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
|
import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
|
||||||
import { commonMessages, injectNotificationManager } from '@modrinth/ui'
|
import { commonMessages, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
import HCaptcha from '@/components/ui/HCaptcha.vue'
|
import HCaptcha from '@/components/ui/HCaptcha.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -140,8 +140,13 @@ import {
|
|||||||
RightArrowIcon,
|
RightArrowIcon,
|
||||||
SteamColorIcon,
|
SteamColorIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { commonMessages, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import HCaptcha from '@/components/ui/HCaptcha.vue'
|
import HCaptcha from '@/components/ui/HCaptcha.vue'
|
||||||
import { getAuthUrl, getLauncherRedirectUrl } from '@/composables/auth.js'
|
import { getAuthUrl, getLauncherRedirectUrl } from '@/composables/auth.js'
|
||||||
|
|||||||
@@ -145,8 +145,14 @@ import {
|
|||||||
SteamColorIcon,
|
SteamColorIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Checkbox, commonMessages, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
Checkbox,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import HCaptcha from '@/components/ui/HCaptcha.vue'
|
import HCaptcha from '@/components/ui/HCaptcha.vue'
|
||||||
import { getAuthUrl } from '@/composables/auth.js'
|
import { getAuthUrl } from '@/composables/auth.js'
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { RightArrowIcon, SettingsIcon } from '@modrinth/assets'
|
import { RightArrowIcon, SettingsIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
|
import { ButtonStyled, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -49,8 +49,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets'
|
import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets'
|
||||||
import { Checkbox, commonMessages, normalizeChildren } from '@modrinth/ui'
|
import {
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
Checkbox,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
IntlFormatted,
|
||||||
|
normalizeChildren,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|||||||
@@ -381,10 +381,13 @@ import {
|
|||||||
commonProjectTypeCategoryMessages,
|
commonProjectTypeCategoryMessages,
|
||||||
commonProjectTypeSentenceMessages,
|
commonProjectTypeSentenceMessages,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
|
defineMessage,
|
||||||
|
defineMessages,
|
||||||
FileInput,
|
FileInput,
|
||||||
HorizontalRule,
|
HorizontalRule,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
NewModal,
|
NewModal,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
NormalPage,
|
NormalPage,
|
||||||
@@ -393,10 +396,9 @@ import {
|
|||||||
SidebarCard,
|
SidebarCard,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useSavable,
|
useSavable,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { isAdmin } from '@modrinth/utils'
|
import { isAdmin } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import {
|
|||||||
OrganizationIcon,
|
OrganizationIcon,
|
||||||
ReportIcon,
|
ReportIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { commonMessages } from '@modrinth/ui'
|
import { commonMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { type User, UserBadge } from '@modrinth/utils'
|
import { type User, UserBadge } from '@modrinth/utils'
|
||||||
|
|
||||||
import NavStack from '~/components/ui/NavStack.vue'
|
import NavStack from '~/components/ui/NavStack.vue'
|
||||||
|
|||||||
@@ -71,10 +71,11 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { AffiliateLink } from '@modrinth/utils'
|
import type { AffiliateLink } from '@modrinth/utils'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
const createModal = useTemplateRef<typeof AffiliateLinkCreateModal>('createModal')
|
const createModal = useTemplateRef<typeof AffiliateLinkCreateModal>('createModal')
|
||||||
const revokeModal = useTemplateRef<typeof ConfirmModal>('revokeModal')
|
const revokeModal = useTemplateRef<typeof ConfirmModal>('revokeModal')
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ import {
|
|||||||
SearchIcon,
|
SearchIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Button, commonMessages } from '@modrinth/ui'
|
import { Avatar, Button, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
NewModal,
|
NewModal,
|
||||||
ProjectStatusBadge,
|
ProjectStatusBadge,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
|
|||||||
@@ -260,8 +260,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowUpRightIcon, InProgressIcon, UnknownIcon } from '@modrinth/assets'
|
import { ArrowUpRightIcon, InProgressIcon, UnknownIcon } from '@modrinth/assets'
|
||||||
|
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
|
|
||||||
|
|||||||
@@ -90,9 +90,8 @@ import {
|
|||||||
GenericListIcon,
|
GenericListIcon,
|
||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { ButtonStyled, Combobox } from '@modrinth/ui'
|
import { ButtonStyled, Combobox, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
import { formatMoney } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
|
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { commonProjectTypeCategoryMessages } from '@modrinth/ui'
|
import { commonProjectTypeCategoryMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
import NavTabs from '~/components/ui/NavTabs.vue'
|
import NavTabs from '~/components/ui/NavTabs.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const flags = useFeatureFlags()
|
const flags = useFeatureFlags()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
SearchIcon,
|
SearchIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
|
import { defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRelativeTime } from '@modrinth/ui'
|
import { defineMessages, useRelativeTime, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
const vintl = useVIntl()
|
const vintl = useVIntl()
|
||||||
const { formatMessage } = vintl
|
const { formatMessage } = vintl
|
||||||
|
|||||||
@@ -651,10 +651,10 @@ import {
|
|||||||
commonMessages,
|
commonMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
ModrinthServersPurchaseModal,
|
ModrinthServersPurchaseModal,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { monthsInInterval } from '@modrinth/ui/src/utils/billing.ts'
|
import { monthsInInterval } from '@modrinth/ui/src/utils/billing.ts'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
import { formatPrice } from '@modrinth/utils'
|
||||||
import { useVIntl } from '@vintl/vintl'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { useBaseFetch } from '@/composables/fetch.js'
|
import { useBaseFetch } from '@/composables/fetch.js'
|
||||||
|
|||||||
@@ -385,8 +385,10 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
|
import type { MessageDescriptor } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
|
defineMessage,
|
||||||
ErrorInformationCard,
|
ErrorInformationCard,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
@@ -397,7 +399,6 @@ import {
|
|||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { PowerAction, Stats } from '@modrinth/utils'
|
import type { PowerAction, Stats } from '@modrinth/utils'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
import type { MessageDescriptor } from '@vintl/vintl'
|
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
import { computed, onMounted, onUnmounted, type Reactive, reactive, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, type Reactive, reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
|||||||
@@ -439,9 +439,15 @@ import {
|
|||||||
ModrinthIcon,
|
ModrinthIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, ButtonStyled, commonMessages, useRelativeTime } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
Avatar,
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
IntlFormatted,
|
||||||
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FolderIcon, ReportIcon, ShieldCheckIcon } from '@modrinth/assets'
|
import { FolderIcon, ReportIcon, ShieldCheckIcon } from '@modrinth/assets'
|
||||||
import { Chips } from '@modrinth/ui'
|
import { Chips, defineMessages, useVIntl } from '@modrinth/ui'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
|
|
||||||
import NavTabs from '@/components/ui/NavTabs.vue'
|
import NavTabs from '@/components/ui/NavTabs.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -105,8 +105,15 @@ import {
|
|||||||
SortDescIcon,
|
SortDescIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, ButtonStyled, Combobox, type ComboboxOption, Pagination } from '@modrinth/ui'
|
import {
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
Button,
|
||||||
|
ButtonStyled,
|
||||||
|
Combobox,
|
||||||
|
type ComboboxOption,
|
||||||
|
defineMessages,
|
||||||
|
Pagination,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import ConfettiExplosion from 'vue-confetti-explosion'
|
import ConfettiExplosion from 'vue-confetti-explosion'
|
||||||
|
|
||||||
|
|||||||
@@ -78,9 +78,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ListFilterIcon, SearchIcon, SortAscIcon, SortDescIcon, XIcon } from '@modrinth/assets'
|
import { ListFilterIcon, SearchIcon, SortAscIcon, SortDescIcon, XIcon } from '@modrinth/assets'
|
||||||
import type { ExtendedReport } from '@modrinth/moderation'
|
import type { ExtendedReport } from '@modrinth/moderation'
|
||||||
import { Button, Combobox, type ComboboxOption, Pagination } from '@modrinth/ui'
|
import {
|
||||||
|
Button,
|
||||||
|
Combobox,
|
||||||
|
type ComboboxOption,
|
||||||
|
defineMessages,
|
||||||
|
Pagination,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import type { Report } from '@modrinth/utils'
|
import type { Report } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
|
|
||||||
import ReportCard from '~/components/ui/moderation/ModerationReportCard.vue'
|
import ReportCard from '~/components/ui/moderation/ModerationReportCard.vue'
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Combobox,
|
Combobox,
|
||||||
type ComboboxOption,
|
type ComboboxOption,
|
||||||
|
defineMessages,
|
||||||
injectModrinthClient,
|
injectModrinthClient,
|
||||||
Pagination,
|
Pagination,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { useInfiniteQuery, useQueryClient } from '@tanstack/vue-query'
|
import { useInfiniteQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
|
|
||||||
import MaliciousSummaryModal, {
|
import MaliciousSummaryModal, {
|
||||||
|
|||||||
@@ -261,7 +261,14 @@ import {
|
|||||||
UsersIcon,
|
UsersIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, ButtonStyled, commonMessages, ContentPageHeader, OverflowMenu } from '@modrinth/ui'
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
|
ContentPageHeader,
|
||||||
|
OverflowMenu,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import type { Organization, ProjectStatus, ProjectType, ProjectV3 } from '@modrinth/utils'
|
import type { Organization, ProjectStatus, ProjectType, ProjectV3 } from '@modrinth/utils'
|
||||||
import { formatNumber } from '@modrinth/utils'
|
import { formatNumber } from '@modrinth/utils'
|
||||||
|
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
NewModal,
|
NewModal,
|
||||||
ProjectStatusBadge,
|
ProjectStatusBadge,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets'
|
import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets'
|
||||||
import { injectNotificationManager, PurchaseModal } from '@modrinth/ui'
|
import { injectNotificationManager, PurchaseModal, useVIntl } from '@modrinth/ui'
|
||||||
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
|
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
|
||||||
|
|
||||||
import { useBaseFetch } from '@/composables/fetch.js'
|
import { useBaseFetch } from '@/composables/fetch.js'
|
||||||
|
|||||||
@@ -281,18 +281,21 @@ import {
|
|||||||
VersionIcon,
|
VersionIcon,
|
||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
|
import { defineMessage } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
AutoLink,
|
AutoLink,
|
||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
MarkdownEditor,
|
MarkdownEditor,
|
||||||
|
type MessageDescriptor,
|
||||||
RadialHeader,
|
RadialHeader,
|
||||||
RadioButtons,
|
RadioButtons,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { Project, Report, User, Version } from '@modrinth/utils'
|
import type { Project, Report, User, Version } from '@modrinth/utils'
|
||||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ import {
|
|||||||
ShieldIcon,
|
ShieldIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { commonMessages, commonSettingsMessages } from '@modrinth/ui'
|
import { commonMessages, commonSettingsMessages, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
import NavStack from '~/components/ui/NavStack.vue'
|
import NavStack from '~/components/ui/NavStack.vue'
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ import {
|
|||||||
CopyCode,
|
CopyCode,
|
||||||
FileInput,
|
FileInput,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ import {
|
|||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import { useScopes } from '~/composables/auth/scopes.ts'
|
import { useScopes } from '~/composables/auth/scopes.ts'
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Badge, Breadcrumbs } from '@modrinth/ui'
|
import { Badge, Breadcrumbs, useVIntl } from '@modrinth/ui'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
import { formatPrice } from '@modrinth/utils'
|
||||||
|
|
||||||
import { products } from '~/generated/state.json'
|
import { products } from '~/generated/state.json'
|
||||||
|
|||||||
@@ -617,11 +617,13 @@ import {
|
|||||||
commonMessages,
|
commonMessages,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
|
defineMessages,
|
||||||
getPaymentMethodIcon,
|
getPaymentMethodIcon,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
PurchaseModal,
|
PurchaseModal,
|
||||||
ServerListing,
|
ServerListing,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
|
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|||||||
@@ -205,10 +205,16 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
|
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
|
||||||
import { Button, injectNotificationManager, normalizeChildren, ThemeSelector } from '@modrinth/ui'
|
import {
|
||||||
|
Button,
|
||||||
|
defineMessages,
|
||||||
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
|
normalizeChildren,
|
||||||
|
ThemeSelector,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
import MessageBanner from '~/components/ui/MessageBanner.vue'
|
||||||
import type { DisplayLocation } from '~/plugins/cosmetics'
|
import type { DisplayLocation } from '~/plugins/cosmetics'
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { IssuesIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
|
import { RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
|
||||||
import { Admonition, commonSettingsMessages } from '@modrinth/ui'
|
import {
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
Admonition,
|
||||||
|
commonSettingsMessages,
|
||||||
|
defineMessages,
|
||||||
|
IntlFormatted,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import Fuse from 'fuse.js/dist/fuse.basic'
|
import Fuse from 'fuse.js/dist/fuse.basic'
|
||||||
|
|
||||||
import { isModifierKeyDown } from '~/helpers/events.ts'
|
import { isModifierKeyDown } from '~/helpers/events.ts'
|
||||||
|
|
||||||
const vintl = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const { formatMessage } = vintl
|
const { locale, setLocale, locales } = useI18n()
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
languagesDescription: {
|
languagesDescription: {
|
||||||
@@ -23,10 +28,6 @@ const messages = defineMessages({
|
|||||||
id: 'settings.language.languages.search.no-results',
|
id: 'settings.language.languages.search.no-results',
|
||||||
defaultMessage: 'No languages match your search.',
|
defaultMessage: 'No languages match your search.',
|
||||||
},
|
},
|
||||||
searchFieldDescription: {
|
|
||||||
id: 'settings.language.languages.search-field.description',
|
|
||||||
defaultMessage: 'Submit to focus the first search result',
|
|
||||||
},
|
|
||||||
searchFieldPlaceholder: {
|
searchFieldPlaceholder: {
|
||||||
id: 'settings.language.languages.search-field.placeholder',
|
id: 'settings.language.languages.search-field.placeholder',
|
||||||
defaultMessage: 'Search for a language...',
|
defaultMessage: 'Search for a language...',
|
||||||
@@ -36,18 +37,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage:
|
defaultMessage:
|
||||||
'{matches, plural, =0 {No languages match} one {# language matches} other {# languages match}} your search.',
|
'{matches, plural, =0 {No languages match} one {# language matches} other {# languages match}} your search.',
|
||||||
},
|
},
|
||||||
loadFailed: {
|
|
||||||
id: 'settings.language.languages.load-failed',
|
|
||||||
defaultMessage: 'Cannot load this language. Try again in a bit.',
|
|
||||||
},
|
|
||||||
languageLabelApplying: {
|
|
||||||
id: 'settings.language.languages.language-label-applying',
|
|
||||||
defaultMessage: '{label}. Applying...',
|
|
||||||
},
|
|
||||||
languageLabelError: {
|
|
||||||
id: 'settings.language.languages.language-label-error',
|
|
||||||
defaultMessage: '{label}. Error',
|
|
||||||
},
|
|
||||||
languageWarning: {
|
languageWarning: {
|
||||||
id: 'settings.language.warning',
|
id: 'settings.language.warning',
|
||||||
defaultMessage:
|
defaultMessage:
|
||||||
@@ -56,22 +45,10 @@ const messages = defineMessages({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const categoryNames = defineMessages({
|
const categoryNames = defineMessages({
|
||||||
auto: {
|
|
||||||
id: 'settings.language.categories.auto',
|
|
||||||
defaultMessage: 'Automatic',
|
|
||||||
},
|
|
||||||
default: {
|
default: {
|
||||||
id: 'settings.language.categories.default',
|
id: 'settings.language.categories.default',
|
||||||
defaultMessage: 'Standard languages',
|
defaultMessage: 'Standard languages',
|
||||||
},
|
},
|
||||||
fun: {
|
|
||||||
id: 'settings.language.categories.fun',
|
|
||||||
defaultMessage: 'Fun languages',
|
|
||||||
},
|
|
||||||
experimental: {
|
|
||||||
id: 'settings.language.categories.experimental',
|
|
||||||
defaultMessage: 'Experimental languages',
|
|
||||||
},
|
|
||||||
searchResult: {
|
searchResult: {
|
||||||
id: 'settings.language.categories.search-result',
|
id: 'settings.language.categories.search-result',
|
||||||
defaultMessage: 'Search results',
|
defaultMessage: 'Search results',
|
||||||
@@ -80,97 +57,46 @@ const categoryNames = defineMessages({
|
|||||||
|
|
||||||
type Category = keyof typeof categoryNames
|
type Category = keyof typeof categoryNames
|
||||||
|
|
||||||
const categoryOrder: Category[] = ['auto', 'default', 'fun', 'experimental']
|
type LocaleInfo = {
|
||||||
|
|
||||||
function normalizeCategoryName(name?: string): keyof typeof categoryNames {
|
|
||||||
switch (name) {
|
|
||||||
case 'auto':
|
|
||||||
case 'fun':
|
|
||||||
case 'experimental':
|
|
||||||
return name
|
|
||||||
default:
|
|
||||||
return 'default'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type LocaleBase = {
|
|
||||||
category: Category
|
category: Category
|
||||||
tag: string
|
tag: string
|
||||||
|
displayName: string
|
||||||
|
nativeName: string
|
||||||
searchTerms?: string[]
|
searchTerms?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type AutomaticLocale = LocaleBase & {
|
const displayNames = new Intl.DisplayNames(['en'], { type: 'language' })
|
||||||
auto: true
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommonLocale = LocaleBase & {
|
|
||||||
auto?: never
|
|
||||||
displayName: string
|
|
||||||
defaultName: string
|
|
||||||
translatedName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Locale = AutomaticLocale | CommonLocale
|
|
||||||
|
|
||||||
const $defaultNames = useDisplayNames(() => vintl.defaultLocale)
|
|
||||||
|
|
||||||
const $translatedNames = useDisplayNames(() => vintl.locale)
|
|
||||||
|
|
||||||
const $locales = computed(() => {
|
const $locales = computed(() => {
|
||||||
const locales: Locale[] = []
|
const result: LocaleInfo[] = []
|
||||||
|
|
||||||
locales.push({
|
const localeList = Array.isArray(locales.value) ? locales.value : Object.keys(locales.value)
|
||||||
auto: true,
|
|
||||||
tag: 'auto',
|
|
||||||
category: 'auto',
|
|
||||||
searchTerms: [
|
|
||||||
'automatic',
|
|
||||||
'Sync with the system language',
|
|
||||||
formatMessage(messages.automaticLocale),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const locale of vintl.availableLocales) {
|
for (const loc of localeList) {
|
||||||
let displayName = locale.meta?.displayName
|
const tag = typeof loc === 'string' ? loc : loc.code
|
||||||
|
const name = typeof loc === 'object' && loc.name ? loc.name : (displayNames.of(tag) ?? tag)
|
||||||
|
|
||||||
if (displayName == null) {
|
const nativeDisplayNames = new Intl.DisplayNames([tag], { type: 'language' })
|
||||||
displayName = createDisplayNames(locale.tag).of(locale.tag) ?? locale.tag
|
const nativeName = nativeDisplayNames.of(tag) ?? tag
|
||||||
}
|
|
||||||
|
|
||||||
let defaultName = vintl.defaultResources['languages.json']?.[locale.tag]
|
result.push({
|
||||||
|
tag,
|
||||||
if (defaultName == null) {
|
category: 'default',
|
||||||
defaultName = $defaultNames.value.of(locale.tag) ?? locale.tag
|
displayName: name,
|
||||||
}
|
nativeName,
|
||||||
|
searchTerms: [tag, name, nativeName],
|
||||||
let translatedName = vintl.resources['languages.json']?.[locale.tag]
|
|
||||||
|
|
||||||
if (translatedName == null) {
|
|
||||||
translatedName = $translatedNames.value.of(locale.tag) ?? locale.tag
|
|
||||||
}
|
|
||||||
|
|
||||||
let searchTerms = locale.meta?.searchTerms
|
|
||||||
if (searchTerms === '-') searchTerms = undefined
|
|
||||||
|
|
||||||
locales.push({
|
|
||||||
tag: locale.tag,
|
|
||||||
category: normalizeCategoryName(locale.meta?.category),
|
|
||||||
displayName,
|
|
||||||
defaultName,
|
|
||||||
translatedName,
|
|
||||||
searchTerms: searchTerms?.split('\n'),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return locales
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
const $query = ref('')
|
const $query = ref('')
|
||||||
|
|
||||||
const isQueryEmpty = () => $query.value.trim().length === 0
|
const isQueryEmpty = () => $query.value.trim().length === 0
|
||||||
|
|
||||||
const fuse = new Fuse<Locale>([], {
|
const fuse = new Fuse<LocaleInfo>([], {
|
||||||
keys: ['tag', 'displayName', 'translatedName', 'englishName', 'searchTerms'],
|
keys: ['tag', 'displayName', 'nativeName', 'searchTerms'],
|
||||||
threshold: 0.4,
|
threshold: 0.4,
|
||||||
distance: 100,
|
distance: 100,
|
||||||
})
|
})
|
||||||
@@ -178,32 +104,13 @@ const fuse = new Fuse<Locale>([], {
|
|||||||
watchSyncEffect(() => fuse.setCollection($locales.value))
|
watchSyncEffect(() => fuse.setCollection($locales.value))
|
||||||
|
|
||||||
const $categories = computed(() => {
|
const $categories = computed(() => {
|
||||||
const categories = new Map<Category, Locale[]>()
|
const categories = new Map<Category, LocaleInfo[]>()
|
||||||
|
categories.set('default', $locales.value)
|
||||||
for (const category of categoryOrder) categories.set(category, [])
|
|
||||||
|
|
||||||
for (const locale of $locales.value) {
|
|
||||||
let categoryLocales = categories.get(locale.category)
|
|
||||||
|
|
||||||
if (categoryLocales == null) {
|
|
||||||
categoryLocales = []
|
|
||||||
categories.set(locale.category, categoryLocales)
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryLocales.push(locale)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const categoryKey of [...categories.keys()]) {
|
|
||||||
if (categories.get(categoryKey)?.length === 0) {
|
|
||||||
categories.delete(categoryKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return categories
|
return categories
|
||||||
})
|
})
|
||||||
|
|
||||||
const $searchResults = computed(() => {
|
const $searchResults = computed(() => {
|
||||||
return new Map<Category, Locale[]>([
|
return new Map<Category, LocaleInfo[]>([
|
||||||
['searchResult', isQueryEmpty() ? [] : fuse.search($query.value).map(({ item }) => item)],
|
['searchResult', isQueryEmpty() ? [] : fuse.search($query.value).map(({ item }) => item)],
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@@ -216,11 +123,9 @@ const $changingTo = ref<string | undefined>()
|
|||||||
|
|
||||||
const isChanging = () => $changingTo.value != null
|
const isChanging = () => $changingTo.value != null
|
||||||
|
|
||||||
const $failedLocale = ref<string>()
|
|
||||||
|
|
||||||
const $activeLocale = computed(() => {
|
const $activeLocale = computed(() => {
|
||||||
if ($changingTo.value != null) return $changingTo.value
|
if ($changingTo.value != null) return $changingTo.value
|
||||||
return vintl.automatic ? 'auto' : vintl.locale
|
return locale.value
|
||||||
})
|
})
|
||||||
|
|
||||||
async function changeLocale(value: string) {
|
async function changeLocale(value: string) {
|
||||||
@@ -229,10 +134,7 @@ async function changeLocale(value: string) {
|
|||||||
$changingTo.value = value
|
$changingTo.value = value
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await vintl.changeLocale(value)
|
await setLocale(value)
|
||||||
$failedLocale.value = undefined
|
|
||||||
} catch {
|
|
||||||
$failedLocale.value = value
|
|
||||||
} finally {
|
} finally {
|
||||||
$changingTo.value = undefined
|
$changingTo.value = undefined
|
||||||
}
|
}
|
||||||
@@ -250,7 +152,7 @@ function onSearchKeydown(e: KeyboardEvent) {
|
|||||||
focusableTarget?.focus()
|
focusableTarget?.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
function onItemKeydown(e: KeyboardEvent, locale: Locale) {
|
function onItemKeydown(e: KeyboardEvent, loc: LocaleInfo) {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case ' ':
|
case ' ':
|
||||||
@@ -261,29 +163,17 @@ function onItemKeydown(e: KeyboardEvent, locale: Locale) {
|
|||||||
|
|
||||||
if (isModifierKeyDown(e) || isChanging()) return
|
if (isModifierKeyDown(e) || isChanging()) return
|
||||||
|
|
||||||
changeLocale(locale.tag)
|
changeLocale(loc.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onItemClick(e: MouseEvent, locale: Locale) {
|
function onItemClick(e: MouseEvent, loc: LocaleInfo) {
|
||||||
if (isModifierKeyDown(e) || isChanging()) return
|
if (isModifierKeyDown(e) || isChanging()) return
|
||||||
|
|
||||||
changeLocale(locale.tag)
|
changeLocale(loc.tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemLabel(locale: Locale) {
|
function getItemLabel(loc: LocaleInfo) {
|
||||||
const label = locale.auto
|
return `${loc.nativeName}. ${loc.displayName}`
|
||||||
? formatMessage(messages.automaticLocale)
|
|
||||||
: `${locale.translatedName}. ${locale.displayName}`
|
|
||||||
|
|
||||||
if ($changingTo.value === locale.tag) {
|
|
||||||
return formatMessage(messages.languageLabelApplying, { label })
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($failedLocale.value === locale.tag) {
|
|
||||||
return formatMessage(messages.languageLabelError, { label })
|
|
||||||
}
|
|
||||||
|
|
||||||
return label
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -298,7 +188,7 @@ function getItemLabel(locale: Locale) {
|
|||||||
|
|
||||||
<div class="card-description mt-4">
|
<div class="card-description mt-4">
|
||||||
<IntlFormatted :message-id="messages.languagesDescription">
|
<IntlFormatted :message-id="messages.languagesDescription">
|
||||||
<template #crowdin-link="{ children }">
|
<template #~crowdin-link="{ children }">
|
||||||
<a href="https://translate.modrinth.com">
|
<a href="https://translate.modrinth.com">
|
||||||
<component :is="() => children" />
|
<component :is="() => children" />
|
||||||
</a>
|
</a>
|
||||||
@@ -306,7 +196,7 @@ function getItemLabel(locale: Locale) {
|
|||||||
</IntlFormatted>
|
</IntlFormatted>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-container">
|
<div v-if="$locales.length > 1" class="search-container">
|
||||||
<input
|
<input
|
||||||
id="language-search"
|
id="language-search"
|
||||||
v-model="$query"
|
v-model="$query"
|
||||||
@@ -314,15 +204,10 @@ function getItemLabel(locale: Locale) {
|
|||||||
type="search"
|
type="search"
|
||||||
:placeholder="formatMessage(messages.searchFieldPlaceholder)"
|
:placeholder="formatMessage(messages.searchFieldPlaceholder)"
|
||||||
class="language-search"
|
class="language-search"
|
||||||
aria-describedby="language-search-description"
|
|
||||||
:disabled="isChanging()"
|
:disabled="isChanging()"
|
||||||
@keydown="onSearchKeydown"
|
@keydown="onSearchKeydown"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div id="language-search-description" class="visually-hidden">
|
|
||||||
{{ formatMessage(messages.searchFieldDescription) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="language-search-results-announcements" class="visually-hidden" aria-live="polite">
|
<div id="language-search-results-announcements" class="visually-hidden" aria-live="polite">
|
||||||
{{
|
{{
|
||||||
isQueryEmpty()
|
isQueryEmpty()
|
||||||
@@ -335,59 +220,46 @@ function getItemLabel(locale: Locale) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref="$languagesList" class="languages-list">
|
<div ref="$languagesList" class="languages-list">
|
||||||
<template v-for="[category, locales] in $displayCategories" :key="category">
|
<template v-for="[category, categoryLocales] in $displayCategories" :key="category">
|
||||||
<strong class="category-name">
|
<strong class="category-name">
|
||||||
{{ formatMessage(categoryNames[category]) }}
|
{{ formatMessage(categoryNames[category]) }}
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="category === 'searchResult' && locales.length === 0"
|
v-if="category === 'searchResult' && categoryLocales.length === 0"
|
||||||
class="no-results"
|
class="no-results"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
{{ formatMessage(messages.noResults) }}
|
{{ formatMessage(messages.noResults) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-for="locale in locales" :key="locale.tag">
|
<template v-for="loc in categoryLocales" :key="loc.tag">
|
||||||
<div
|
<div
|
||||||
role="button"
|
role="button"
|
||||||
:aria-pressed="$activeLocale === locale.tag"
|
:aria-pressed="$activeLocale === loc.tag"
|
||||||
:class="{
|
:class="{
|
||||||
'language-item': true,
|
'language-item': true,
|
||||||
pending: $changingTo == locale.tag,
|
pending: $changingTo === loc.tag,
|
||||||
errored: $failedLocale == locale.tag,
|
|
||||||
}"
|
}"
|
||||||
:aria-describedby="
|
:aria-disabled="isChanging() && $changingTo !== loc.tag"
|
||||||
$failedLocale == locale.tag ? `language__${locale.tag}__fail` : undefined
|
|
||||||
"
|
|
||||||
:aria-disabled="isChanging() && $changingTo !== locale.tag"
|
|
||||||
:tabindex="0"
|
:tabindex="0"
|
||||||
:aria-label="getItemLabel(locale)"
|
:aria-label="getItemLabel(loc)"
|
||||||
@click="(e) => onItemClick(e, locale)"
|
@click="(e) => onItemClick(e, loc)"
|
||||||
@keydown="(e) => onItemKeydown(e, locale)"
|
@keydown="(e) => onItemKeydown(e, loc)"
|
||||||
>
|
>
|
||||||
<RadioButtonCheckedIcon v-if="$activeLocale === locale.tag" class="radio" />
|
<RadioButtonCheckedIcon v-if="$activeLocale === loc.tag" class="radio" />
|
||||||
<RadioButtonIcon v-else class="radio" />
|
<RadioButtonIcon v-else class="radio" />
|
||||||
|
|
||||||
<div class="language-names">
|
<div class="language-names">
|
||||||
<div class="language-name">
|
<div class="language-name">
|
||||||
{{ locale.auto ? formatMessage(messages.automaticLocale) : locale.displayName }}
|
{{ loc.displayName }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!locale.auto" class="language-translated-name">
|
<div class="language-translated-name">
|
||||||
{{ locale.translatedName }}
|
{{ loc.nativeName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="$failedLocale === locale.tag"
|
|
||||||
:id="`language__${locale.tag}__fail`"
|
|
||||||
class="language-load-error"
|
|
||||||
>
|
|
||||||
<IssuesIcon />
|
|
||||||
{{ formatMessage(messages.loadFailed) }}
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -423,14 +295,6 @@ function getItemLabel(locale: Locale) {
|
|||||||
outline: 2px solid var(--color-brand);
|
outline: 2px solid var(--color-brand);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.errored {
|
|
||||||
border-color: var(--color-red);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--color-red);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.pending::after {
|
&.pending::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -482,15 +346,6 @@ function getItemLabel(locale: Locale) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-load-error {
|
|
||||||
color: var(--color-red);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
margin-left: 0.3rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio {
|
.radio {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -534,4 +389,9 @@ function getItemLabel(locale: Locale) {
|
|||||||
.category-name {
|
.category-name {
|
||||||
margin-top: var(--spacing-card-md);
|
margin-top: var(--spacing-card-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
padding: var(--spacing-card-md);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -209,10 +209,12 @@ import {
|
|||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
ConfirmModal,
|
ConfirmModal,
|
||||||
CopyCode,
|
CopyCode,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
|
||||||
|
|
||||||
import Modal from '~/components/ui/Modal.vue'
|
import Modal from '~/components/ui/Modal.vue'
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -91,8 +91,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from '@modrinth/assets'
|
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Avatar, Button, commonMessages, FileInput, injectNotificationManager } from '@modrinth/ui'
|
import {
|
||||||
import { IntlFormatted } from '@vintl/vintl/components'
|
Avatar,
|
||||||
|
Button,
|
||||||
|
commonMessages,
|
||||||
|
defineMessages,
|
||||||
|
FileInput,
|
||||||
|
injectNotificationManager,
|
||||||
|
IntlFormatted,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -60,8 +60,10 @@ import { XIcon } from '@modrinth/assets'
|
|||||||
import {
|
import {
|
||||||
commonMessages,
|
commonMessages,
|
||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user