mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34:50 +00:00
feat: download modal changes (#6652)
* update download with modrinth app button * feat: implement compatible version card and update download dependency buttons * fix: download multiple files immediately triggers blocking * refactor: implement download modal provider * fix: duplicate dependencies and style update for dependecy section * feat: improve loading flow when there are dependencies, to not show compatible version until all data is loaded * fix: fallback to download compatible version if dependency lookup fails * feat: implement selecting compatible versions when there are multiple * fix: update dependencies version table to any compatible version * fix: version showing before depedencies loaded * feat: add platform options sort order * pnpm prepr * feat: keep previous result showing until new one loads in when change compatibility options * feat: implement better handling for showing required/recommended resource packs with additional files * pnpm prepr * fix: required resource pack not showing footer actions * fix: download with deps is downloading all files * feat: add joined button for downloading with recommended * feat: add admontion for dependency that has resource pack to download * pnpm prepr * fix: lint * feat: append "-EXTRACT_ME.zip" to zipped project with dependencies * fix: date published capitalization * pnpm prepr --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -80,107 +80,73 @@
|
||||
</Combobox>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedVersion" class="flex flex-col gap-1">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h3 class="relative top-0.5 m-0 text-base font-semibold text-contrast">
|
||||
{{ formatMessage(messages.compatibleVersionTitle) }}
|
||||
</h3>
|
||||
<ButtonStyled v-if="downloadAllFiles.length > 1" type="transparent">
|
||||
<button :disabled="downloadingSelectedVersion" @click="downloadSelectedVersionFiles">
|
||||
<SpinnerIcon v-if="downloadingSelectedVersion" aria-hidden="true" class="animate-spin" />
|
||||
<DownloadIcon v-else aria-hidden="true" />
|
||||
{{
|
||||
formatMessage(
|
||||
downloadingSelectedVersion
|
||||
? messages.downloadingSelectedVersion
|
||||
: messages.downloadAllSelectedVersion,
|
||||
{
|
||||
current: selectedVersionDownloadProgress.current,
|
||||
total: selectedVersionDownloadProgress.total,
|
||||
},
|
||||
)
|
||||
}}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div
|
||||
class="grid grid-cols-[1fr_min-content] items-center gap-3 rounded-2xl bg-surface-2 px-3 py-3"
|
||||
<div
|
||||
v-if="selectedVersion && downloadDataLoaded"
|
||||
:role="compatibleVersions.length > 1 ? 'radiogroup' : undefined"
|
||||
:aria-label="
|
||||
compatibleVersions.length > 1 ? formatMessage(messages.compatibleVersionTitle) : undefined
|
||||
"
|
||||
class="flex flex-col gap-2.5"
|
||||
>
|
||||
<h3
|
||||
v-if="compatibleVersions.length > 1"
|
||||
class="relative top-0.5 m-0 text-base font-semibold text-contrast"
|
||||
>
|
||||
<div class="flex min-w-0 flex-col gap-1">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<nuxt-link
|
||||
v-tooltip="truncatedTooltip(versionNumberRef, selectedVersion.version_number)"
|
||||
:to="`/${project.project_type}/${project.slug || project.id}/version/${selectedVersion.id}`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block min-w-0 text-contrast no-underline hover:underline"
|
||||
>
|
||||
<span ref="versionNumberRef" class="block truncate font-semibold">
|
||||
{{ selectedVersion.version_number }}
|
||||
</span>
|
||||
</nuxt-link>
|
||||
<VersionChannelTag
|
||||
:channel="selectedVersion.version_type"
|
||||
class="relative -top-px !py-0.5"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
ref="versionNameRef"
|
||||
v-tooltip="truncatedTooltip(versionNameRef, selectedVersion.name)"
|
||||
class="m-0 w-fit max-w-full truncate text-sm text-secondary"
|
||||
>
|
||||
{{ selectedVersion.name }}
|
||||
</p>
|
||||
</div>
|
||||
<ButtonStyled v-if="selectedPrimaryFile" color="brand" circular>
|
||||
<a
|
||||
v-tooltip="'Download'"
|
||||
:href="selectedPrimaryFileDownloadUrl"
|
||||
:download="selectedPrimaryFile.filename"
|
||||
:aria-label="
|
||||
formatMessage(messages.downloadVersion, {
|
||||
version: selectedVersion.version_number,
|
||||
})
|
||||
"
|
||||
@click="emit('download')"
|
||||
>
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
{{ formatMessage(messages.compatibleVersionTitle) }}
|
||||
</h3>
|
||||
<CompatibleVersionCard
|
||||
v-for="compatibleVersion in compatibleVersions"
|
||||
:key="compatibleVersion.id"
|
||||
:project="project"
|
||||
:version="compatibleVersion"
|
||||
:download-reason="downloadReason"
|
||||
:current-game-version="currentGameVersion"
|
||||
:current-platform="currentPlatform"
|
||||
:selectable="compatibleVersions.length > 1"
|
||||
:selected="compatibleVersion.id === selectedVersion.id"
|
||||
:show-download="
|
||||
compatibleVersions.length === 1 || compatibleVersion.id === selectedVersion.id
|
||||
"
|
||||
:color="
|
||||
compatibleVersion.id === selectedVersion.id &&
|
||||
compatibleVersions.length === 1 &&
|
||||
!hasAdditionalDownloads
|
||||
? 'brand'
|
||||
: 'standard'
|
||||
"
|
||||
:type="
|
||||
compatibleVersion.id === selectedVersion.id &&
|
||||
compatibleVersions.length === 1 &&
|
||||
!hasAdditionalDownloads
|
||||
? 'standard'
|
||||
: 'transparent'
|
||||
"
|
||||
:circular="hasAdditionalDownloads || compatibleVersions.length > 1"
|
||||
@select="selectCompatibleVersion(compatibleVersion)"
|
||||
@download="emit('download')"
|
||||
/>
|
||||
</div>
|
||||
<p v-else-if="currentPlatform && currentGameVersion && versions.length > 0">
|
||||
{{
|
||||
formatMessage(messages.noVersionsAvailable, {
|
||||
gameVersion: currentGameVersion,
|
||||
platform: currentPlatformText,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { DownloadIcon, SpinnerIcon, TriangleAlertIcon } from '@modrinth/assets'
|
||||
import { TriangleAlertIcon } from '@modrinth/assets'
|
||||
import {
|
||||
ButtonStyled,
|
||||
type CdnDownloadReason,
|
||||
Checkbox,
|
||||
Combobox,
|
||||
type ComboboxOption,
|
||||
defineMessages,
|
||||
getTagMessage,
|
||||
injectNotificationManager,
|
||||
truncatedTooltip,
|
||||
useDebugLogger,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import VersionChannelTag from '@modrinth/ui/src/components/version/VersionChannelTag.vue'
|
||||
import type { DisplayProjectType } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import JSZip from 'jszip'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import CompatibleVersionCard from './CompatibleVersionCard.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'DownloadProject',
|
||||
})
|
||||
@@ -207,6 +173,7 @@ const props = withDefaults(
|
||||
project: DownloadModalProject
|
||||
versions?: Labrinth.Versions.v3.Version[]
|
||||
dependencyDownloadFiles?: DownloadableFile[]
|
||||
downloadDataLoaded?: boolean
|
||||
downloadReason?: CdnDownloadReason
|
||||
initialGameVersion?: string | null
|
||||
initialPlatform?: string | null
|
||||
@@ -217,6 +184,7 @@ const props = withDefaults(
|
||||
{
|
||||
versions: () => [],
|
||||
dependencyDownloadFiles: () => [],
|
||||
downloadDataLoaded: false,
|
||||
downloadReason: 'standalone',
|
||||
initialGameVersion: null,
|
||||
initialPlatform: null,
|
||||
@@ -233,22 +201,19 @@ const emit = defineEmits<{
|
||||
'update:selection': [selection: ProjectDownloadSelection]
|
||||
}>()
|
||||
const { formatMessage } = useVIntl()
|
||||
const { createProjectDownloadUrl } = useCdnDownloadContext()
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const debug = useDebugLogger('DownloadProject')
|
||||
const tags = useGeneratedState()
|
||||
|
||||
const userSelectedGameVersion = ref<string | null>(props.initialGameVersion)
|
||||
const userSelectedPlatform = ref<string | null>(props.initialPlatform)
|
||||
const userSelectedCompatibleVersionId = ref<string | null>(null)
|
||||
const showAllVersions = ref(defaultShowAllVersions())
|
||||
const versionFilter = ref('')
|
||||
const versionNumberRef = ref<HTMLElement | null>(null)
|
||||
const versionNameRef = ref<HTMLElement | null>(null)
|
||||
const downloadingSelectedVersion = ref(false)
|
||||
const selectedVersionDownloadProgress = ref({
|
||||
current: 0,
|
||||
total: 0,
|
||||
})
|
||||
const preferredPlatformRanks = new Map([
|
||||
['fabric', 0],
|
||||
['forge', 1],
|
||||
['neoforge', 2],
|
||||
])
|
||||
|
||||
const incompatibleGameVersionsSet = computed(() => new Set(props.incompatibleGameVersions))
|
||||
const incompatibleLoadersSet = computed(() => new Set(props.incompatibleLoaders))
|
||||
@@ -376,13 +341,12 @@ const gameVersionOptions = computed<ComboboxOption<string>[]>(() => {
|
||||
|
||||
const platformOptions = computed<ComboboxOption<string>[]>(() => {
|
||||
return props.project.loaders
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((platform) => ({
|
||||
value: platform,
|
||||
label: loaderLabel(platform),
|
||||
class: '!px-0 !py-1',
|
||||
}))
|
||||
.sort(comparePlatformOptions)
|
||||
})
|
||||
|
||||
const filteredVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
|
||||
@@ -407,31 +371,52 @@ const filteredVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
|
||||
})
|
||||
|
||||
const filteredRelease = computed<Labrinth.Versions.v3.Version | undefined>(() => {
|
||||
return filteredVersions.value.find((x) => x.version_type === 'release')
|
||||
return latestVersionByType('release')
|
||||
})
|
||||
|
||||
const filteredBeta = computed<Labrinth.Versions.v3.Version | undefined>(() => {
|
||||
return filteredVersions.value.find(
|
||||
(x) =>
|
||||
x.version_type === 'beta' &&
|
||||
(!filteredRelease.value ||
|
||||
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))),
|
||||
)
|
||||
return latestVersionByType('beta')
|
||||
})
|
||||
|
||||
const filteredAlpha = computed<Labrinth.Versions.v3.Version | undefined>(() => {
|
||||
return filteredVersions.value.find(
|
||||
(x) =>
|
||||
x.version_type === 'alpha' &&
|
||||
(!filteredRelease.value ||
|
||||
dayjs(x.date_published).isAfter(dayjs(filteredRelease.value.date_published))) &&
|
||||
(!filteredBeta.value ||
|
||||
dayjs(x.date_published).isAfter(dayjs(filteredBeta.value.date_published))),
|
||||
)
|
||||
return latestVersionByType('alpha')
|
||||
})
|
||||
|
||||
const defaultSelectedVersion = computed<Labrinth.Versions.v3.Version | null>(() => {
|
||||
return filteredRelease.value || filteredBeta.value || filteredAlpha.value || null
|
||||
})
|
||||
|
||||
const suggestedPreReleaseVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
|
||||
if (!defaultSelectedVersion.value || defaultSelectedVersion.value.version_type !== 'release')
|
||||
return []
|
||||
|
||||
const versions: Labrinth.Versions.v3.Version[] = []
|
||||
const beta = filteredBeta.value
|
||||
if (beta && isNewerThan(beta, defaultSelectedVersion.value)) {
|
||||
versions.push(beta)
|
||||
}
|
||||
|
||||
const alpha = filteredAlpha.value
|
||||
if (alpha && isNewerThan(alpha, defaultSelectedVersion.value)) {
|
||||
versions.push(alpha)
|
||||
}
|
||||
|
||||
return versions
|
||||
})
|
||||
|
||||
const compatibleVersions = computed<Labrinth.Versions.v3.Version[]>(() => {
|
||||
if (!defaultSelectedVersion.value) return []
|
||||
return [defaultSelectedVersion.value, ...suggestedPreReleaseVersions.value]
|
||||
})
|
||||
|
||||
const selectedVersion = computed<Labrinth.Versions.v3.Version | null>(() => {
|
||||
return filteredRelease.value || filteredBeta.value || filteredAlpha.value || null
|
||||
return (
|
||||
compatibleVersions.value.find(
|
||||
(version) => version.id === userSelectedCompatibleVersionId.value,
|
||||
) ||
|
||||
defaultSelectedVersion.value ||
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
const selectedPrimaryFile = computed<Labrinth.Versions.v3.VersionFile | null>(() => {
|
||||
@@ -442,39 +427,47 @@ const selectedPrimaryFile = computed<Labrinth.Versions.v3.VersionFile | null>(()
|
||||
)
|
||||
})
|
||||
|
||||
const selectedPrimaryFileDownloadUrl = computed(() => {
|
||||
if (!selectedPrimaryFile.value) return '#'
|
||||
return getDownloadUrl(selectedPrimaryFile.value.url)
|
||||
const requiredResourcePackFile = computed<Labrinth.Versions.v3.VersionFile | null>(() => {
|
||||
if (props.project.project_type !== 'datapack') return null
|
||||
|
||||
return (
|
||||
selectedVersion.value?.files?.find(
|
||||
(file) => file !== selectedPrimaryFile.value && file.file_type === 'required-resource-pack',
|
||||
) || null
|
||||
)
|
||||
})
|
||||
|
||||
const selectedVersionDownloadFiles = computed(() => {
|
||||
if (!selectedVersion.value) return []
|
||||
const recommendedResourcePackFiles = computed<Labrinth.Versions.v3.VersionFile[]>(() => {
|
||||
if (props.project.project_type !== 'datapack') return []
|
||||
|
||||
return selectedVersion.value.files.map((file) => ({
|
||||
href: getDownloadUrl(file.url),
|
||||
filename: file.filename,
|
||||
}))
|
||||
return (
|
||||
selectedVersion.value?.files?.filter(
|
||||
(file) => file !== selectedPrimaryFile.value && file.file_type === 'optional-resource-pack',
|
||||
) || []
|
||||
)
|
||||
})
|
||||
|
||||
const downloadAllFiles = computed(() => {
|
||||
const files: DownloadableFile[] = []
|
||||
const hasAdditionalDownloads = computed(() => {
|
||||
const hrefs = new Set<string>()
|
||||
|
||||
for (const file of [...selectedVersionDownloadFiles.value, ...props.dependencyDownloadFiles]) {
|
||||
if (hrefs.has(file.href)) continue
|
||||
hrefs.add(file.href)
|
||||
files.push(file)
|
||||
if (selectedPrimaryFile.value) {
|
||||
hrefs.add(selectedPrimaryFile.value.url)
|
||||
}
|
||||
|
||||
return files
|
||||
})
|
||||
if (requiredResourcePackFile.value) {
|
||||
hrefs.add(requiredResourcePackFile.value.url)
|
||||
}
|
||||
|
||||
const selectedVersionZipFilename = computed(() => {
|
||||
if (!selectedVersion.value) return `${sanitizeFilename(props.project.title)}.zip`
|
||||
for (const file of recommendedResourcePackFiles.value) {
|
||||
hrefs.add(file.url)
|
||||
}
|
||||
|
||||
return `${sanitizeFilename(props.project.title)} ${sanitizeFilename(
|
||||
selectedVersion.value.version_number,
|
||||
)}.zip`
|
||||
for (const file of props.dependencyDownloadFiles) {
|
||||
if (hrefs.has(file.href)) continue
|
||||
hrefs.add(file.href)
|
||||
}
|
||||
|
||||
return hrefs.size > 1
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -490,11 +483,16 @@ watch(
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch([currentGameVersion, currentPlatform], () => {
|
||||
userSelectedCompatibleVersionId.value = null
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.resetKey,
|
||||
() => {
|
||||
userSelectedGameVersion.value = props.initialGameVersion
|
||||
userSelectedPlatform.value = props.initialPlatform
|
||||
userSelectedCompatibleVersionId.value = null
|
||||
showAllVersions.value = defaultShowAllVersions()
|
||||
versionFilter.value = ''
|
||||
},
|
||||
@@ -513,115 +511,39 @@ function selectPlatform(platform?: string) {
|
||||
emit('selectPlatform', platform)
|
||||
}
|
||||
|
||||
function getDownloadUrl(url: string) {
|
||||
return createProjectDownloadUrl(url, {
|
||||
reason: props.downloadReason,
|
||||
gameVersion: currentGameVersion.value ?? undefined,
|
||||
loader: currentPlatform.value ?? undefined,
|
||||
})
|
||||
function selectCompatibleVersion(version: Labrinth.Versions.v3.Version) {
|
||||
userSelectedCompatibleVersionId.value = version.id
|
||||
}
|
||||
|
||||
async function downloadSelectedVersionFiles() {
|
||||
if (downloadingSelectedVersion.value || downloadAllFiles.value.length <= 1) return
|
||||
|
||||
downloadingSelectedVersion.value = true
|
||||
const files = [...downloadAllFiles.value]
|
||||
selectedVersionDownloadProgress.value = {
|
||||
current: 0,
|
||||
total: files.length,
|
||||
}
|
||||
|
||||
try {
|
||||
const zip = new JSZip()
|
||||
const usedFilenames = new Set<string>()
|
||||
|
||||
for (const [index, file] of files.entries()) {
|
||||
selectedVersionDownloadProgress.value = {
|
||||
current: index + 1,
|
||||
total: files.length,
|
||||
}
|
||||
const response = await fetch(file.href)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${file.filename}`)
|
||||
}
|
||||
|
||||
zip.file(uniqueFilename(file.filename, usedFilenames), await response.blob())
|
||||
}
|
||||
|
||||
downloadBlob(
|
||||
await zip.generateAsync({
|
||||
type: 'blob',
|
||||
mimeType: 'application/zip',
|
||||
}),
|
||||
selectedVersionZipFilename.value,
|
||||
)
|
||||
emit('download')
|
||||
} catch (error) {
|
||||
console.error('Failed to download selected version files:', error)
|
||||
addNotification({
|
||||
title: formatMessage(messages.downloadSelectedVersionFailedTitle),
|
||||
text: formatMessage(messages.downloadSelectedVersionFailedText),
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
downloadingSelectedVersion.value = false
|
||||
selectedVersionDownloadProgress.value = {
|
||||
current: 0,
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
function latestVersionByType(type: Labrinth.Versions.v3.VersionChannel) {
|
||||
return filteredVersions.value
|
||||
.filter((version) => version.version_type === type)
|
||||
.reduce<Labrinth.Versions.v3.Version | undefined>((latest, version) => {
|
||||
if (!latest || isNewerThan(version, latest)) return version
|
||||
return latest
|
||||
}, undefined)
|
||||
}
|
||||
|
||||
function downloadBlob(blob: Blob, filename: string) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
|
||||
link.href = url
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 0)
|
||||
}
|
||||
|
||||
function sanitizeFilename(value: string) {
|
||||
const sanitized = value
|
||||
.replace(/[<>:"/\\|?*]/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
|
||||
return sanitized || 'download'
|
||||
}
|
||||
|
||||
function uniqueFilename(filename: string, usedFilenames: Set<string>) {
|
||||
const sanitizedFilename = sanitizeFilename(filename)
|
||||
|
||||
if (!usedFilenames.has(sanitizedFilename)) {
|
||||
usedFilenames.add(sanitizedFilename)
|
||||
return sanitizedFilename
|
||||
}
|
||||
|
||||
const extensionIndex = sanitizedFilename.lastIndexOf('.')
|
||||
const basename =
|
||||
extensionIndex > 0 ? sanitizedFilename.slice(0, extensionIndex) : sanitizedFilename
|
||||
const extension = extensionIndex > 0 ? sanitizedFilename.slice(extensionIndex) : ''
|
||||
let index = 2
|
||||
let candidate = `${basename} (${index})${extension}`
|
||||
|
||||
while (usedFilenames.has(candidate)) {
|
||||
index += 1
|
||||
candidate = `${basename} (${index})${extension}`
|
||||
}
|
||||
|
||||
usedFilenames.add(candidate)
|
||||
return candidate
|
||||
function isNewerThan(
|
||||
version: Labrinth.Versions.v3.Version,
|
||||
comparison: Labrinth.Versions.v3.Version,
|
||||
) {
|
||||
return dayjs(version.date_published).isAfter(dayjs(comparison.date_published))
|
||||
}
|
||||
|
||||
function loaderLabel(loader: string) {
|
||||
return formatMessage(getTagMessage(loader, 'loader') ?? messages.unknownLoader)
|
||||
}
|
||||
|
||||
function comparePlatformOptions(a: ComboboxOption<string>, b: ComboboxOption<string>) {
|
||||
const aRank = preferredPlatformRanks.get(a.value) ?? Number.MAX_SAFE_INTEGER
|
||||
const bRank = preferredPlatformRanks.get(b.value) ?? Number.MAX_SAFE_INTEGER
|
||||
|
||||
if (aRank !== bRank) return aRank - bRank
|
||||
|
||||
return a.label.localeCompare(b.label)
|
||||
}
|
||||
|
||||
function isReleaseGameVersion(version: string) {
|
||||
if (releaseVersions.value.has(version)) return true
|
||||
if (nonReleaseVersions.value.has(version)) return false
|
||||
@@ -706,38 +628,14 @@ const messages = defineMessages({
|
||||
id: 'project.download.game-version-unsupported-tooltip',
|
||||
defaultMessage: '{title} does not support {gameVersion} for {platform}',
|
||||
},
|
||||
downloadVersion: {
|
||||
id: 'project.download.download-version',
|
||||
defaultMessage: 'Download {version}',
|
||||
},
|
||||
compatibleVersionTitle: {
|
||||
id: 'project.download.compatible-version-title',
|
||||
defaultMessage: 'Compatible version',
|
||||
},
|
||||
downloadAllSelectedVersion: {
|
||||
id: 'project.download.selected-version-download-all',
|
||||
defaultMessage: 'Download all (.zip)',
|
||||
},
|
||||
downloadingSelectedVersion: {
|
||||
id: 'project.download.selected-version-downloading',
|
||||
defaultMessage: 'Downloading... ({current}/{total})',
|
||||
},
|
||||
downloadSelectedVersionFailedTitle: {
|
||||
id: 'project.download.selected-version-failed-title',
|
||||
defaultMessage: 'Could not download version',
|
||||
},
|
||||
downloadSelectedVersionFailedText: {
|
||||
id: 'project.download.selected-version-failed-text',
|
||||
defaultMessage: 'One or more version files could not be downloaded. Please try again.',
|
||||
defaultMessage: 'Compatible versions',
|
||||
},
|
||||
noGameVersionsFound: {
|
||||
id: 'project.download.no-game-versions-found',
|
||||
defaultMessage: 'No game versions found',
|
||||
},
|
||||
noVersionsAvailable: {
|
||||
id: 'project.download.no-versions-available',
|
||||
defaultMessage: 'No versions available for {gameVersion} and {platform}.',
|
||||
},
|
||||
platformUnsupportedTooltip: {
|
||||
id: 'project.download.platform-unsupported-tooltip',
|
||||
defaultMessage: '{title} does not support {platform} for {gameVersion}',
|
||||
|
||||
Reference in New Issue
Block a user