Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/app-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down
70 changes: 70 additions & 0 deletions apps/app-frontend/src/components/ui/IntlFormatted.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script setup lang="ts">
import IntlMessageFormat, { type FormatXMLElementFn, type PrimitiveType } from 'intl-messageformat'
import { computed, useSlots, type VNode } from 'vue'
import { useI18n } from 'vue-i18n'

import type { MessageDescriptor } from '@/utils/i18n-vintl'

const props = defineProps<{
messageId: MessageDescriptor
values?: Record<string, PrimitiveType>
}>()

const slots = useSlots()
const { t, locale, messages } = useI18n()

const formattedParts = computed(() => {
const key = props.messageId.id
const localeMessages = messages.value[locale.value] as Record<string, string> | undefined
const translation = localeMessages?.[key]

let msg: string
if (translation && translation !== key) {
msg = translation
} else {
const translatedT = t(key, {})
if (translatedT && translatedT !== key) {
msg = translatedT
} else {
msg = props.messageId.defaultMessage ?? key
}
}

const slotHandlers: Record<string, FormatXMLElementFn<VNode>> = {}
const slotNames = Object.keys(slots)

for (const slotName of slotNames) {
slotHandlers[slotName] = (chunks) => {
const slot = slots[slotName]
if (slot) {
return slot({
children: chunks,
})
}
return chunks as VNode[]
}
}

try {
const formatter = new IntlMessageFormat(msg, locale.value)
const result = formatter.format({
...props.values,
...slotHandlers,
})

if (Array.isArray(result)) {
return result
}
return [result]
} catch {
return [msg]
}
})
</script>

<template>
<template v-for="(part, index) in formattedParts" :key="index">
<component :is="() => part" v-if="typeof part === 'object'" />
<template v-else>{{ part }}</template>
</template>
</template>
2 changes: 1 addition & 1 deletion apps/app-frontend/src/components/ui/UpdateToast.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
4 changes: 2 additions & 2 deletions apps/app-frontend/src/components/ui/friends/FriendsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { Checkbox, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, ref, watch } from 'vue'

import { edit } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

import type { AppSettings, Hooks, InstanceSettingsTabProps } from '../../../helpers/types'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
type Project,
type Version,
} from '@modrinth/utils'
import { defineMessages, useVIntl } from '@vintl/vintl'
import dayjs from 'dayjs'
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'

Expand All @@ -36,6 +35,7 @@ import { get_project, get_version_many } from '@/helpers/cache'
import { get_loader_versions } from '@/helpers/metadata'
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
import { get_game_versions, get_loaders } from '@/helpers/tags'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

import type {
InstanceSettingsTabProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script setup lang="ts">
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
import { Checkbox, injectNotificationManager, Slider } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, readonly, ref, watch } from 'vue'

import JavaSelector from '@/components/ui/JavaSelector.vue'
import useMemorySlider from '@/composables/useMemorySlider'
import { edit, get_optimal_jre_key } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { Checkbox, injectNotificationManager, Toggle } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, type Ref, ref, watch } from 'vue'

import { edit } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import { ProgressBar, TabbedModal } from '@modrinth/ui'
import { getVersion } from '@tauri-apps/api/app'
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 ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
Expand All @@ -25,6 +24,7 @@ import ResourceManagementSettings from '@/components/ui/settings/ResourceManagem
import { get, set } from '@/helpers/settings.ts'
import { injectAppUpdateDownloadProgress } from '@/providers/download-progress.ts'
import { useTheming } from '@/store/state'
import { defineMessage, defineMessages, useVIntl } from '@/utils/i18n-vintl'

const themeStore = useTheming()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
} from '@modrinth/assets'
import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { ref } from 'vue'

import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
Expand All @@ -18,6 +17,7 @@ import InstallationSettings from '@/components/ui/instance_settings/Installation
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { defineMessage, useVIntl } from '@/utils/i18n-vintl'

import type { InstanceSettingsTabProps } from '../../../helpers/types'

Expand Down
2 changes: 1 addition & 1 deletion apps/app-frontend/src/components/ui/world/InstanceItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {
} from '@modrinth/ui'
import { capitalizeString } from '@modrinth/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
import { useVIntl } from '@vintl/vintl'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
Expand All @@ -32,6 +31,7 @@ import { kill, run } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'
import { showProfileInFolder } from '@/helpers/utils'
import { handleSevereError } from '@/store/error'
import { useVIntl } from '@/utils/i18n-vintl'

const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
Expand Down
4 changes: 2 additions & 2 deletions apps/app-frontend/src/components/ui/world/WorldItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import {
} from '@modrinth/ui'
import { formatNumber, getPingLevel } from '@modrinth/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
import type { MessageDescriptor } from '@vintl/vintl'
import { defineMessages, useVIntl } from '@vintl/vintl'
import dayjs from 'dayjs'
import { Tooltip } from 'floating-vue'
import type { Component } from 'vue'
Expand All @@ -44,6 +42,8 @@ import type {
World,
} from '@/helpers/worlds.ts'
import { getWorldIdentifier, set_world_display_status } from '@/helpers/worlds.ts'
import type { MessageDescriptor } from '@/utils/i18n-vintl'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<script setup lang="ts">
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { ref } from 'vue'

import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { SaveIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { computed, ref } from 'vue'

import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
Expand All @@ -15,6 +14,7 @@ import {
type ServerWorld,
set_world_display_status,
} from '@/helpers/worlds.ts'
import { defineMessage, useVIntl } from '@/utils/i18n-vintl'

const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<script setup lang="ts">
import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, ref } from 'vue'

import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
import type { GameInstance } from '@/helpers/types'
import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts'
import { rename_world, reset_world_icon, set_world_display_status } from '@/helpers/worlds.ts'
import { defineMessages, useVIntl } from '@/utils/i18n-vintl'

const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { Checkbox } from '@modrinth/ui'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { computed } from 'vue'

import { defineMessage, useVIntl } from '@/utils/i18n-vintl'

const { formatMessage } = useVIntl()
const value = defineModel<boolean>({ required: true })

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script setup lang="ts">
import { Combobox } from '@modrinth/ui'
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'

import type { ServerPackStatus } from '@/helpers/worlds.ts'
import { defineMessages, type MessageDescriptor, useVIntl } from '@/utils/i18n-vintl'

const { formatMessage } = useVIntl()

Expand Down
37 changes: 37 additions & 0 deletions apps/app-frontend/src/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import IntlMessageFormat from 'intl-messageformat'
import { type CompileError, createI18n, type MessageCompiler, type MessageContext } from 'vue-i18n'

const messageCompiler: MessageCompiler = (msg, { locale, key, onError }) => {
if (typeof msg === 'string') {
try {
const formatter = new IntlMessageFormat(msg, locale)
return (ctx: MessageContext) => {
try {
return formatter.format(ctx.values as Record<string, unknown>) as string
} catch {
return String(msg)
}
}
} catch (e) {
onError?.(e as CompileError)
return () => key
}
} else {
onError?.(new Error('AST not supported') as CompileError)
return () => key
}
}

const i18n = createI18n({
legacy: false,
locale: 'en-US',
fallbackLocale: 'en-US',
messageCompiler,
missingWarn: false,
fallbackWarn: false,
messages: {
'en-US': {},
},
})

export default i18n
Loading
Loading