diff --git a/apps/app-frontend/package.json b/apps/app-frontend/package.json
index 44439c7976..a0468e289a 100644
--- a/apps/app-frontend/package.json
+++ b/apps/app-frontend/package.json
@@ -28,7 +28,8 @@
"@tauri-apps/plugin-updater": "^2.7.1",
"@tauri-apps/plugin-window-state": "^2.2.2",
"@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",
"dayjs": "^1.11.10",
"floating-vue": "^5.2.2",
diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue
index 26f041fd68..4a56f35eed 100644
--- a/apps/app-frontend/src/App.vue
+++ b/apps/app-frontend/src/App.vue
@@ -48,7 +48,6 @@ import { getCurrentWindow } from '@tauri-apps/api/window'
import { openUrl } from '@tauri-apps/plugin-opener'
import { type } from '@tauri-apps/plugin-os'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
-import { defineMessages, useVIntl } from '@vintl/vintl'
import { $fetch } from 'ofetch'
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
@@ -98,6 +97,7 @@ import {
import { useError } from '@/store/error.js'
import { useInstall } from '@/store/install.js'
import { useLoading, useTheming } from '@/store/state'
+import { defineMessages, useVIntl } from '@/utils/i18n-vintl'
import { create_profile_and_install_from_file } from './helpers/pack'
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
diff --git a/apps/app-frontend/src/components/ui/IntlFormatted.vue b/apps/app-frontend/src/components/ui/IntlFormatted.vue
new file mode 100644
index 0000000000..77a4d29c36
--- /dev/null
+++ b/apps/app-frontend/src/components/ui/IntlFormatted.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+ {{ part }}
+
+
diff --git a/apps/app-frontend/src/components/ui/UpdateToast.vue b/apps/app-frontend/src/components/ui/UpdateToast.vue
index af9676080d..95e4f5957b 100644
--- a/apps/app-frontend/src/components/ui/UpdateToast.vue
+++ b/apps/app-frontend/src/components/ui/UpdateToast.vue
@@ -2,10 +2,10 @@
import { DownloadIcon, ExternalIcon, RefreshCwIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages, ProgressBar } from '@modrinth/ui'
import { formatBytes } from '@modrinth/utils'
-import { defineMessages, useVIntl } from '@vintl/vintl'
import { ref } from 'vue'
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
+import { defineMessages, useVIntl } from '@/utils/i18n-vintl'
const { formatMessage } = useVIntl()
diff --git a/apps/app-frontend/src/components/ui/friends/FriendsList.vue b/apps/app-frontend/src/components/ui/friends/FriendsList.vue
index 991e7e7d69..293e6828e5 100644
--- a/apps/app-frontend/src/components/ui/friends/FriendsList.vue
+++ b/apps/app-frontend/src/components/ui/friends/FriendsList.vue
@@ -7,11 +7,10 @@ import {
injectNotificationManager,
useRelativeTime,
} from '@modrinth/ui'
-import { defineMessages, useVIntl } from '@vintl/vintl'
-import { IntlFormatted } from '@vintl/vintl/components'
import { computed, onUnmounted, ref, watch } from 'vue'
import FriendsSection from '@/components/ui/friends/FriendsSection.vue'
+import IntlFormatted from '@/components/ui/IntlFormatted.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { friend_listener } from '@/helpers/events'
import {
@@ -22,6 +21,7 @@ import {
transformFriends,
} from '@/helpers/friends.ts'
import type { ModrinthCredentials } from '@/helpers/mr_auth'
+import { defineMessages, useVIntl } from '@/utils/i18n-vintl'
const { formatMessage } = useVIntl()
diff --git a/apps/app-frontend/src/components/ui/friends/FriendsSection.vue b/apps/app-frontend/src/components/ui/friends/FriendsSection.vue
index 83e673f7c0..da927fbe9d 100644
--- a/apps/app-frontend/src/components/ui/friends/FriendsSection.vue
+++ b/apps/app-frontend/src/components/ui/friends/FriendsSection.vue
@@ -2,11 +2,11 @@
import { MoreVerticalIcon, TrashIcon, UserIcon, XIcon } from '@modrinth/assets'
import { Accordion, Avatar, ButtonStyled, OverflowMenu } from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
-import { defineMessages, useVIntl } from '@vintl/vintl'
import { useTemplateRef } from 'vue'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import type { FriendWithUserData } from '@/helpers/friends.ts'
+import { defineMessages, useVIntl } from '@/utils/i18n-vintl'
const { formatMessage } = useVIntl()
diff --git a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue
index b98d00cf2b..8cc209d8c6 100644
--- a/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue
+++ b/apps/app-frontend/src/components/ui/instance_settings/GeneralSettings.vue
@@ -9,13 +9,13 @@ import {
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-dialog'
-import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, type Ref, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
+import { defineMessages, useVIntl } from '@/utils/i18n-vintl'
import type { GameInstance, InstanceSettingsTabProps } from '../../../helpers/types'
diff --git a/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue
index 9f1d589beb..9443d5b4d4 100644
--- a/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue
+++ b/apps/app-frontend/src/components/ui/instance_settings/HooksSettings.vue
@@ -1,10 +1,10 @@
+
+
+
+
+ {{ part }}
+
+
diff --git a/apps/frontend/src/components/ui/ProjectMemberHeader.vue b/apps/frontend/src/components/ui/ProjectMemberHeader.vue
index be287995fb..a3c2e13c5d 100644
--- a/apps/frontend/src/components/ui/ProjectMemberHeader.vue
+++ b/apps/frontend/src/components/ui/ProjectMemberHeader.vue
@@ -26,10 +26,10 @@
import { CheckIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import type { Project, User, Version } from '@modrinth/utils'
-import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
import { computed } from 'vue'
import { acceptTeamInvite, removeTeamMember } from '~/helpers/teams.js'
+import { defineMessages, type MessageDescriptor, useVIntl } from '~/utils/i18n-vintl'
const { addNotification } = injectNotificationManager()
diff --git a/apps/frontend/src/components/ui/create/CollectionCreateModal.vue b/apps/frontend/src/components/ui/create/CollectionCreateModal.vue
index 9eada58306..f822fd0ffc 100644
--- a/apps/frontend/src/components/ui/create/CollectionCreateModal.vue
+++ b/apps/frontend/src/components/ui/create/CollectionCreateModal.vue
@@ -59,7 +59,8 @@
@@ -298,7 +183,7 @@ function getItemLabel(locale: Locale) {
-
+
@@ -306,7 +191,7 @@ function getItemLabel(locale: Locale) {
-
+
-
- {{ formatMessage(messages.searchFieldDescription) }}
-
-
{{
isQueryEmpty()
@@ -335,59 +215,46 @@ function getItemLabel(locale: Locale) {
-
+
{{ formatMessage(categoryNames[category]) }}
{{ formatMessage(messages.noResults) }}
-
+
onItemClick(e, locale)"
- @keydown="(e) => onItemKeydown(e, locale)"
+ :aria-label="getItemLabel(loc)"
+ @click="(e) => onItemClick(e, loc)"
+ @keydown="(e) => onItemKeydown(e, loc)"
>
-
+
- {{ locale.auto ? formatMessage(messages.automaticLocale) : locale.displayName }}
+ {{ loc.displayName }}
-
- {{ locale.translatedName }}
+
+ {{ loc.nativeName }}
-
-
-
- {{ formatMessage(messages.loadFailed) }}
-
@@ -423,14 +290,6 @@ function getItemLabel(locale: Locale) {
outline: 2px solid var(--color-brand);
}
- &.errored {
- border-color: var(--color-red);
-
- &:hover {
- border-color: var(--color-red);
- }
- }
-
&.pending::after {
content: '';
position: absolute;
@@ -482,15 +341,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 {
width: 24px;
height: 24px;
@@ -534,4 +384,9 @@ function getItemLabel(locale: Locale) {
.category-name {
margin-top: var(--spacing-card-md);
}
+
+.no-results {
+ padding: var(--spacing-card-md);
+ color: var(--color-text-secondary);
+}
diff --git a/apps/frontend/src/pages/settings/pats.vue b/apps/frontend/src/pages/settings/pats.vue
index bf55bb70e6..9224dac69a 100644
--- a/apps/frontend/src/pages/settings/pats.vue
+++ b/apps/frontend/src/pages/settings/pats.vue
@@ -212,8 +212,8 @@ import {
injectNotificationManager,
useRelativeTime,
} from '@modrinth/ui'
-import { IntlFormatted } from '@vintl/vintl/components'
+import IntlFormatted from '~/components/ui/IntlFormatted.vue'
import Modal from '~/components/ui/Modal.vue'
import {
getScopeValue,
diff --git a/apps/frontend/src/pages/settings/profile.vue b/apps/frontend/src/pages/settings/profile.vue
index 12be87ddba..db407c3150 100644
--- a/apps/frontend/src/pages/settings/profile.vue
+++ b/apps/frontend/src/pages/settings/profile.vue
@@ -92,7 +92,8 @@
+
+
+
+
+ {{ part }}
+
+
diff --git a/packages/ui/src/components/base/ServerNotice.vue b/packages/ui/src/components/base/ServerNotice.vue
index ba1daadd81..caf56567f6 100644
--- a/packages/ui/src/components/base/ServerNotice.vue
+++ b/packages/ui/src/components/base/ServerNotice.vue
@@ -33,8 +33,8 @@