feat: malware warning modal changes (#6721)

* feat: improved warning modals

* fix: qa

* feat: install to play and update to play changes

* fix: dont warn for server projects as already reviewed

* fix: lint
This commit is contained in:
Calum H.
2026-07-14 20:47:10 +00:00
committed by GitHub
parent 8cca911775
commit 905204cc5f
58 changed files with 1244 additions and 1351 deletions
+4 -3
View File
@@ -855,12 +855,13 @@ async function handleCommand(e) {
if (e.path.endsWith('.mrpack')) {
const location = { type: 'fromFile', path: e.path }
const preview = await install_get_modpack_preview(location).catch(handleError)
if (preview?.unknownFile) {
if (preview?.unknownFile || preview?.externalFilesInModpack.length > 0) {
const splitPath = e.path.split(/[\\/]/)
const fileName = splitPath ? splitPath[splitPath.length - 1] : e.path
unknownPackWarningModal.value?.show(
() => install_create_modpack_instance(location).then(() => undefined),
fileName,
preview.externalFilesInModpack,
)
} else {
await install_create_modpack_instance(location).catch(handleError)
@@ -1736,8 +1737,8 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
@create-anyway="handleContentInstallModpackDuplicateCreateAnyway"
@go-to-instance="handleContentInstallModpackDuplicateGoToInstance"
/>
<InstallToPlayModal ref="installToPlayModal" />
<UpdateToPlayModal ref="updateToPlayModal" />
<InstallToPlayModal ref="installToPlayModal" :show-external-warnings="false" />
<UpdateToPlayModal ref="updateToPlayModal" :show-external-warnings="false" />
</template>
<style lang="scss" scoped>
@@ -1,108 +1,41 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.header)" :on-hide="reset">
<div class="max-w-[31rem] flex flex-col gap-6">
<Admonition
type="warning"
:header="formatMessage(messages.warningTitle)"
:body="formatMessage(messages.warningBody)"
/>
<div v-if="fileName" class="overflow-x-auto whitespace-nowrap text-sm text-secondary">
{{ fileName }}
</div>
<div>
<p class="mt-0 leading-tight">
{{ formatMessage(messages.body) }}
</p>
<p class="text-orange font-semibold mb-0 leading-tight">
{{ formatMessage(messages.malwareStatement) }}
</p>
</div>
<Checkbox v-model="dontShowAgain" :label="formatMessage(messages.dontShowAgain)" />
<div class="flex gap-2 justify-end">
<ButtonStyled type="outlined">
<button @click="cancel">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="orange">
<button :disabled="isProceeding" @click="proceed">
<SpinnerIcon v-if="isProceeding" class="animate-spin" />
<CircleArrowRightIcon v-else />
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
</div>
</div>
</NewModal>
<UnknownFileWarningModal
ref="modal"
:mode="externalFilesInModpack.length > 0 ? 'modpack' : 'mod'"
:file-name="fileName"
:external-files-in-modpack="externalFilesInModpack"
@cancel="reset"
@continue="proceed"
/>
</template>
<script setup lang="ts">
import { CircleArrowRightIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
ButtonStyled,
Checkbox,
commonMessages,
defineMessages,
NewModal,
useVIntl,
} from '@modrinth/ui'
import { UnknownFileWarningModal } from '@modrinth/ui'
import { ref, useTemplateRef } from 'vue'
import { get as getSettings, set as setSettings } from '@/helpers/settings'
import { useTheming } from '@/store/state'
import type { FeatureFlag } from '@/store/theme.ts'
const { formatMessage } = useVIntl()
const themeStore = useTheming()
const skipUnknownPackWarningFeatureFlag = 'skip_unknown_pack_warning' as FeatureFlag
const dontShowAgain = ref(false)
const modal = useTemplateRef('modal')
const onProceed = ref<() => Promise<void>>()
const isProceeding = ref(false)
const fileName = ref('')
const externalFilesInModpack = ref<string[]>([])
const messages = defineMessages({
header: {
id: 'unknown-pack-warning-modal.header',
defaultMessage: 'Confirm installation',
},
warningTitle: {
id: 'unknown-pack-warning-modal.warning.title',
defaultMessage: 'Unknown file warning',
},
warningBody: {
id: 'unknown-pack-warning-modal.warning.body',
defaultMessage: `We couldn't find this file on Modrinth. We strongly recommend only installing files from sources you trust.`,
},
body: {
id: 'unknown-pack-warning-modal.body',
defaultMessage: `A file is only reviewed if its uploaded to Modrinth, regardless of its file format (including .mrpack).`,
},
malwareStatement: {
id: 'unknown-pack-warning-modal.malware-statement',
defaultMessage: `Malware is often distributed through modpack files by sharing them on platforms like Discord.`,
},
dontShowAgain: {
id: 'unknown-pack-warning-modal.dont-show-again',
defaultMessage: `Don't show this warning again`,
},
installAnyway: {
id: 'unknown-pack-warning-modal.install-anyway',
defaultMessage: `Install anyway`,
},
})
function show(createInstance: () => Promise<void>, selectedFileName = '') {
function show(
createInstance: () => Promise<void>,
selectedFileName = '',
selectedExternalFiles: string[] = [],
) {
onProceed.value = createInstance
fileName.value = selectedFileName
dontShowAgain.value = false
externalFilesInModpack.value = selectedExternalFiles
if (themeStore.getFeatureFlag(skipUnknownPackWarningFeatureFlag)) {
// noinspection ES6MissingAwait
createInstance()
void createInstance()
return
}
@@ -112,18 +45,11 @@ function show(createInstance: () => Promise<void>, selectedFileName = '') {
function reset() {
onProceed.value = undefined
fileName.value = ''
externalFilesInModpack.value = []
}
function cancel() {
modal.value?.hide()
}
async function proceed() {
if (!onProceed.value) {
return
}
if (dontShowAgain.value) {
async function proceed(dontShowAgain: boolean) {
if (dontShowAgain) {
themeStore.featureFlags[skipUnknownPackWarningFeatureFlag] = true
const settings = await getSettings()
settings.feature_flags[skipUnknownPackWarningFeatureFlag] = true
@@ -131,9 +57,8 @@ async function proceed() {
}
const createInstance = onProceed.value
modal.value?.hide()
// noinspection ES6MissingAwait
createInstance()
reset()
if (createInstance) void createInstance()
}
defineExpose({ show })
@@ -1,15 +1,22 @@
<template>
<NewModal ref="modal" :header="formatMessage(messages.installToPlay)" :closable="true">
<div v-if="requiredContentProject" class="flex flex-col gap-6 max-w-[500px]">
<Admonition type="info" :header="formatMessage(messages.contentRequired)">
{{ formatMessage(messages.serverRequiresMods) }}
</Admonition>
<NewModal
ref="modal"
:header="formatMessage(messages.installToPlay)"
:closable="true"
:on-hide="show_ads_window"
max-width="640px"
width="640px"
>
<div v-if="requiredContentProject" class="flex w-full flex-col gap-6">
<p class="m-0 text-primary">
{{ formatMessage(messages.inviteWarning) }}
</p>
<div class="flex flex-col gap-1">
<div class="flex justify-between items-center">
<span class="font-semibold text-contrast">{{
formatMessage(messages.requiredModpack)
}}</span>
<div class="flex flex-col gap-2.5">
<div class="flex items-center justify-between">
<span class="font-semibold text-contrast">
{{ formatMessage(messages.sharedInstance) }}
</span>
<ButtonStyled type="transparent">
<button @click="openViewContents">
@@ -19,14 +26,16 @@
</ButtonStyled>
</div>
<div class="flex items-center gap-3 rounded-xl bg-surface-2 p-3">
<div class="flex items-center gap-3 rounded-2xl bg-surface-2 p-3">
<Avatar
:src="requiredContentProject.icon_url"
:alt="requiredContentProject.title"
size="48px"
size="56px"
no-shadow
class="!rounded-2xl"
/>
<div class="flex flex-col gap-0.5">
<span class="font-semibold text-contrast">
<div class="flex min-w-0 flex-col gap-0.5">
<span class="truncate font-semibold text-contrast">
<template v-if="usingCustomModpack && modpackVersion">
{{ modpackVersion.name }}
</template>
@@ -34,7 +43,7 @@
{{ requiredContentProject.title }}
</template>
</span>
<span class="text-sm text-secondary">
<span class="truncate text-sm font-medium text-secondary">
{{ loaderDisplay }} {{ requiredContentProject.game_versions?.[0] }}
<template v-if="modCount">
· {{ formatMessage(messages.modCount, { count: modCount }) }}
@@ -43,24 +52,105 @@
</div>
</div>
</div>
</div>
<template #actions>
<div class="flex justify-end gap-2">
<ButtonStyled>
<button @click="handleDecline">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleAccept">
<DownloadIcon />
{{ formatMessage(messages.installButton) }}
</button>
</ButtonStyled>
<Admonition
v-if="hasExternalFiles"
type="warning"
:header="formatMessage(messages.unknownFilesWarning)"
>
{{ formatMessage(messages.unknownFilesDescription) }}
</Admonition>
<div v-if="hasExternalFiles" class="relative w-full">
<div
ref="externalFileTable"
class="max-h-[242px] overflow-y-auto rounded-2xl"
@scroll="checkTableScrollState"
>
<Table
:columns="externalFileColumns"
:data="externalFileRows"
row-key="id"
virtualized
:virtual-row-height="48"
class="shadow-sm"
>
<template #cell-name="{ value }">
<span class="block truncate" :title="String(value)">{{ value }}</span>
</template>
</Table>
</div>
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 max-h-0"
enter-to-class="opacity-100 max-h-2"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 max-h-2"
leave-to-class="opacity-0 max-h-0"
>
<div
v-if="showTableTopFade"
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-2 bg-gradient-to-b from-bg-raised to-transparent"
/>
</Transition>
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 max-h-0"
enter-to-class="opacity-100 max-h-2"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 max-h-2"
leave-to-class="opacity-0 max-h-0"
>
<div
v-if="showTableBottomFade"
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-2 bg-gradient-to-t from-bg-raised to-transparent"
/>
</Transition>
</div>
</template>
<p v-if="hasExternalFiles" class="m-0 text-primary">
{{ formatMessage(messages.reviewedFiles) }}
</p>
<div class="flex w-full items-center justify-between gap-2">
<ButtonStyled type="transparent" color="red">
<button @click="handleReport">
<ReportIcon />
{{ formatMessage(commonMessages.reportButton) }}
</button>
</ButtonStyled>
<div class="flex items-center gap-2">
<template v-if="hasExternalFiles">
<ButtonStyled type="transparent" color="orange">
<button @click="handleAccept">
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleDecline">
<BanIcon />
{{ formatMessage(messages.dontInstall) }}
</button>
</ButtonStyled>
</template>
<template v-else>
<ButtonStyled>
<button @click="handleDecline">
<XIcon />
{{ formatMessage(commonMessages.cancelButton) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleAccept">
<DownloadIcon />
{{ formatMessage(messages.installButton) }}
</button>
</ButtonStyled>
</template>
</div>
</div>
</div>
</NewModal>
<ModpackContentModal
@@ -72,33 +162,56 @@
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { DownloadIcon, EyeIcon, XIcon } from '@modrinth/assets'
import type { ContentItem } from '@modrinth/ui'
import { BanIcon, DownloadIcon, EyeIcon, ReportIcon, XIcon } from '@modrinth/assets'
import {
Admonition,
Avatar,
ButtonStyled,
commonMessages,
type ContentItem,
defineMessages,
formatLoader,
ModpackContentModal,
NewModal,
Table,
type TableColumn,
useScrollIndicator,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, nextTick, ref } from 'vue'
import { hide_ads_window, show_ads_window } from '@/helpers/ads'
import { get_project, get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import { injectServerInstall } from '@/providers/server-install'
type ExternalFileColumn = 'name'
type ExternalFileRow = {
id: string
name: string
}
const modal = ref<InstanceType<typeof NewModal>>()
const modpackVersionId = ref<string | null>(null)
const modpackVersion = ref<Labrinth.Versions.v2.Version | null>(null)
const project = ref<Labrinth.Projects.v3.Project | null>(null)
const requiredContentProject = ref<Labrinth.Projects.v2.Project | null>(null)
const externalFiles = ref<string[]>([])
const externalFileTable = ref<HTMLElement | null>(null)
const onInstallComplete = ref<() => void>(() => {})
const { formatMessage } = useVIntl()
const props = defineProps<{
showExternalWarnings?: boolean
}>()
const { installServerProject, startInstallingServer, stopInstallingServer } = injectServerInstall()
const {
showTopFade: showTableTopFade,
showBottomFade: showTableBottomFade,
checkScrollState: checkTableScrollState,
forceCheck: forceCheckTableScroll,
} = useScrollIndicator(externalFileTable)
const usingCustomModpack = computed(() => {
return requiredContentProject.value?.id === project.value?.id
@@ -111,13 +224,36 @@ const loaderDisplay = computed(() => {
})
const modCount = computed(() => modpackVersion.value?.dependencies?.length)
const hasExternalFiles = computed(
() => Boolean(props.showExternalWarnings) && externalFiles.value.length > 0,
)
const externalFileRows = computed<ExternalFileRow[]>(() =>
externalFiles.value.map((name, index) => ({
id: `${index}-${name}`,
name,
})),
)
async function fetchData(versionId: string) {
// cache is making version null for some reason so bypassing for now
modpackVersion.value = await get_version(versionId, 'bypass')
const version = await get_version(versionId, 'bypass')
modpackVersion.value = version
if (modpackVersion.value?.project_id) {
requiredContentProject.value = await get_project(modpackVersion.value.project_id, 'bypass')
if (version?.project_id) {
requiredContentProject.value = await get_project(version.project_id, 'bypass')
externalFiles.value = [
...new Set(
(version.dependencies ?? [])
.filter(
(dependency) =>
dependency.dependency_type === 'embedded' &&
!dependency.project_id &&
!dependency.version_id &&
dependency.file_name,
)
.flatMap((dependency) => (dependency.file_name ? [dependency.file_name] : [])),
),
].sort((left, right) => left.localeCompare(right))
}
}
@@ -139,6 +275,12 @@ function handleDecline() {
hide()
}
function handleReport() {
if (project.value?.id) {
openUrl(`https://modrinth.com/report?item=project&itemID=${project.value.id}`)
}
}
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal>>()
async function openViewContents() {
@@ -217,17 +359,19 @@ async function show(
modpackVersionId.value = modpackVersionIdVal
modpackVersion.value = null
requiredContentProject.value = null
externalFiles.value = []
onInstallComplete.value = callback
if (modpackVersionIdVal) await fetchData(modpackVersionIdVal)
hide_ads_window()
modal.value?.show(e)
await nextTick()
forceCheckTableScroll()
}
function hide() {
modal.value?.hide()
show_ads_window()
}
const messages = defineMessages({
@@ -235,22 +379,10 @@ const messages = defineMessages({
id: 'app.modal.install-to-play.header',
defaultMessage: 'Install to play',
},
sharedServerInstance: {
id: 'app.modal.install-to-play.shared-server-instance',
defaultMessage: 'Shared server instance',
},
contentRequired: {
id: 'app.modal.install-to-play.content-required',
defaultMessage: 'Content required',
},
serverRequiresMods: {
id: 'app.modal.install-to-play.server-requires-mods',
inviteWarning: {
id: 'app.modal.install-to-play.invite-warning',
defaultMessage:
'This server requires mods to play. Click Install to set up the required files from Modrinth, then launch directly into the server.',
},
requiredModpack: {
id: 'app.modal.install-to-play.required-modpack',
defaultMessage: 'Required modpack',
'This invite was created by another Modrinth user, not Modrinth. Only accept invites from people you trust.',
},
sharedInstance: {
id: 'app.modal.install-to-play.shared-instance',
@@ -268,7 +400,41 @@ const messages = defineMessages({
id: 'app.modal.install-to-play.view-contents',
defaultMessage: 'View contents',
},
unknownFilesWarning: {
id: 'app.modal.install-to-play.unknown-files-warning',
defaultMessage: 'Unknown files warning',
},
unknownFilesDescription: {
id: 'app.modal.install-to-play.unknown-files-description',
defaultMessage:
'This server modpack contains files that arent published on Modrinth. We strongly recommend only installing files from sources you trust.',
},
unrecognizedFiles: {
id: 'app.modal.install-to-play.unrecognized-files',
defaultMessage: 'Unrecognized files',
},
reviewedFiles: {
id: 'app.modal.install-to-play.reviewed-files',
defaultMessage:
'A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack).',
},
installAnyway: {
id: 'app.modal.install-to-play.install-anyway',
defaultMessage: 'Install anyway',
},
dontInstall: {
id: 'app.modal.install-to-play.dont-install',
defaultMessage: 'Dont install',
},
})
const externalFileColumns = computed<TableColumn<ExternalFileColumn>[]>(() => [
{
key: 'name',
label: formatMessage(messages.unrecognizedFiles),
cellClass: '!h-12',
},
])
defineExpose({ show, hide })
</script>
@@ -2,17 +2,17 @@
<ContentDiffModal
ref="diffModal"
:header="formatMessage(messages.updateToPlay)"
:admonition-header="formatMessage(messages.updateRequired)"
:description="
instance ? formatMessage(messages.updateRequiredDescription, { name: instance.name }) : ''
"
:diffs="normalizedDiffs"
:version-date="versionDate"
:show-external-warnings="showExternalWarnings"
:confirm-label="formatMessage(commonMessages.updateButton)"
:confirm-icon="DownloadIcon"
:show-report-button="true"
:removed-label="formatMessage(messages.removed)"
@confirm="handleUpdate"
@cancel="handleDecline"
@report="handleReport"
/>
</template>
@@ -26,9 +26,8 @@ import {
defineMessages,
useVIntl,
} from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
import dayjs from 'dayjs'
import { computed, ref, watch } from 'vue'
import { computed, ref } from 'vue'
import { get_project_many, get_version, get_version_many } from '@/helpers/cache.js'
import { wait_for_install_job } from '@/helpers/install'
@@ -77,6 +76,10 @@ const { formatMessage } = useVIntl()
const { startInstallingServer, stopInstallingServer } = injectServerInstall()
type UpdateCompleteCallback = () => void | Promise<void>
defineProps<{
showExternalWarnings?: boolean
}>()
const diffModal = ref<InstanceType<typeof ContentDiffModal>>()
const instance = ref<GameInstance | null>(null)
const onUpdateComplete = ref<UpdateCompleteCallback>(() => {})
@@ -87,6 +90,7 @@ const modpackVersion = ref<Version | null>(null)
const normalizedDiffs = computed<ContentDiffItem[]>(() =>
diffs.value.map((diff) => ({
type: diff.type,
external: Boolean(diff.fileName && !diff.project),
projectName: diff.project?.title,
fileName: diff.fileName,
currentVersionName: diff.currentVersion?.version_number,
@@ -94,6 +98,12 @@ const normalizedDiffs = computed<ContentDiffItem[]>(() =>
})),
)
const versionDate = computed(() =>
modpackVersion.value?.date_published
? dayjs(modpackVersion.value.date_published).format('MMMM D, YYYY')
: undefined,
)
async function computeDependencyDiffs(
currentDeps: Dependency[],
latestDeps: Dependency[],
@@ -190,6 +200,10 @@ async function computeDependencyDiffs(
}
})
.sort((a, b) => {
const aExternal = Boolean(a.fileName && !a.project)
const bExternal = Boolean(b.fileName && !b.project)
if (aExternal !== bExternal) return aExternal ? -1 : 1
const typeOrder = { added: 0, updated: 1, removed: 2 }
const typeCompare = typeOrder[a.type] - typeOrder[b.type]
if (typeCompare !== 0) return typeCompare
@@ -227,16 +241,6 @@ async function checkUpdateAvailable(inst: GameInstance): Promise<DependencyDiff[
return null
}
watch(
() => instance.value,
async (newInstance) => {
if (!newInstance) return
const result = await checkUpdateAvailable(newInstance)
diffs.value = result || []
},
{ immediate: true, deep: true },
)
async function handleUpdate() {
hide()
const serverProjectId = instance.value?.link?.project_id
@@ -254,12 +258,6 @@ async function handleUpdate() {
}
}
function handleReport() {
if (instance.value?.link?.project_id) {
openUrl(`https://modrinth.com/report?item=project&itemID=${instance.value.link.project_id}`)
}
}
function handleDecline() {
hide()
}
@@ -272,8 +270,15 @@ function show(
) {
instance.value = instanceVal
modpackVersionId.value = modpackVersionIdVal
modpackVersion.value = null
diffs.value = []
onUpdateComplete.value = callback
diffModal.value?.show(e)
void checkUpdateAvailable(instanceVal).then((result) => {
if (instance.value?.id === instanceVal.id && modpackVersionId.value === modpackVersionIdVal) {
diffs.value = result || []
}
})
}
function hide() {
@@ -285,15 +290,15 @@ const messages = defineMessages({
id: 'app.modal.update-to-play.header',
defaultMessage: 'Update to play',
},
updateRequired: {
id: 'app.modal.update-to-play.update-required',
defaultMessage: 'Update required',
},
updateRequiredDescription: {
id: 'app.modal.update-to-play.update-required-description',
defaultMessage:
'An update is required to play {name}. Please update to the latest version to launch the game.',
},
removed: {
id: 'app.modal.update-to-play.removed',
defaultMessage: 'Removed',
},
})
const hasUpdate = computed(() => {
+1
View File
@@ -27,6 +27,7 @@ export interface InstallModpackPreview {
iconUrl?: string | null
link?: InstanceLink | null
unknownFile: boolean
externalFilesInModpack: string[]
}
export interface InstallCreateInstanceRequest {
@@ -242,6 +242,10 @@ export async function add_project_from_path(
})
}
export async function is_file_on_modrinth(projectPath: string): Promise<boolean> {
return await invoke('plugin:instance|instance_is_file_on_modrinth', { projectPath })
}
// Toggle disabling a project
export async function toggle_disable_project(
instanceId: string,
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "ابحث بين ال{count} عوالم..."
},
"app.modal.install-to-play.content-required": {
"message": "المحتوى مطلوب"
},
"app.modal.install-to-play.header": {
"message": "نزل للعب"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# تعديل} other {# تعديلات}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "حُزْمَة التعديل مطلوبة"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "يتطلب هذا الخادم تعديلات للعب. انقر فوق \"تثبيت\" لإعداد الملفات المطلوبة من Modrinth، ثم قم بتشغيله مباشرة إلى الخادم."
},
"app.modal.install-to-play.shared-instance": {
"message": "النماذج المشتركة"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "نماذج الخادم مشتركة"
},
"app.modal.install-to-play.view-contents": {
"message": "عرض المحتويات"
},
"app.modal.update-to-play.header": {
"message": "حدث للعب"
},
"app.modal.update-to-play.update-required": {
"message": "يلزم التحديث"
},
"app.modal.update-to-play.update-required-description": {
"message": "هناك تحديث مطلوب للعب بـ {name}. الرجاء التحديث إلى أحدث اصدار لتشغيل اللعبة."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "يتم توفير المحمّل من قبل الخادم"
},
"unknown-pack-warning-modal.body": {
"message": "لا تتم مراجعة الملف إلا إذا تم تحميله على Modrinth، بغض النظر عن تنسيق الملف (بما في ذلك .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "لا تعرض هذا التحذير مجددا"
},
"unknown-pack-warning-modal.header": {
"message": "تأكيد التثبيت"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "التثبيت على أي حال"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "غالبًا ما يتم توزيع البرامج الضارة عبر ملفات الحزم المعدلة من خلال مشاركتها على منصات مثل Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "لم نتمكن من العثور على هذا الملف على موقع Modrinth. نوصي بشدة بعدم تثبيت الملفات إلا من مصادر موثوقة."
},
"unknown-pack-warning-modal.warning.title": {
"message": "تحذير بشأن ملف مجهول"
}
}
@@ -362,9 +362,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hledat v {count} světech..."
},
"app.modal.install-to-play.content-required": {
"message": "Požadovaný obsah"
},
"app.modal.install-to-play.header": {
"message": "Nainstaluj pro hraní"
},
@@ -374,27 +371,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mód} few {# módy} other {# módů}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Požadovaný modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Tento server k hraní vyžaduje módy. Klikni na instalovat pro získání potřebných módů z Modrinth a poté se rovnou připoj na server."
},
"app.modal.install-to-play.shared-instance": {
"message": "Sdílená instance"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Sdílená serverová instance"
},
"app.modal.install-to-play.view-contents": {
"message": "Zobrazit obsah"
},
"app.modal.update-to-play.header": {
"message": "Aktualizuj pro hraní"
},
"app.modal.update-to-play.update-required": {
"message": "Je vyžadována aktualizace"
},
"app.modal.update-to-play.update-required-description": {
"message": "Pro hraní {name} je vyžadována aktualizace. Prosím aktualizuj na nejnovější verzi, abys mohl hru spustit."
},
@@ -1036,26 +1021,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader zprostředkovává server"
},
"unknown-pack-warning-modal.body": {
"message": "Soubor je zkontrolován pouze v případě, že je nahrán na Modrinth, bez ohledu na jeho formát (včetně formátu .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Toto varování znovu nezobrazovat"
},
"unknown-pack-warning-modal.header": {
"message": "Potvrdit instalaci"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Přesto nainstalovat"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Malware se často šíří prostřednictvím souborů s modpacky, které se sdílejí na platformách jako Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Tento soubor jsme na Modrinthu nenašli. Důrazně doporučujeme instalovat pouze soubory z důvěryhodných zdrojů."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Upozornění na neznámý soubor"
}
}
@@ -269,9 +269,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Søg {count} verdener..."
},
"app.modal.install-to-play.content-required": {
"message": "Indhold krævet"
},
"app.modal.install-to-play.header": {
"message": "Installer for at spille"
},
@@ -281,27 +278,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpack krævet"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Denne server kræver mods for at spille. Tryk på installer for at sætte de krævet filler fra modrinth op, så lancer direkte til serveren."
},
"app.modal.install-to-play.shared-instance": {
"message": "Delt instance"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Delt server instance"
},
"app.modal.install-to-play.view-contents": {
"message": "Vis indhold"
},
"app.modal.update-to-play.header": {
"message": "Opdater for at spille"
},
"app.modal.update-to-play.update-required": {
"message": "Opdatering krævet"
},
"app.modal.update-to-play.update-required-description": {
"message": "En opdatering er krævet for at spille {name}. Venligst opdater til den seneste version for at køre spillet."
},
@@ -883,20 +868,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader er givet af serveren"
},
"unknown-pack-warning-modal.body": {
"message": "En fil behandles kun, hvis den uploades til Modrinth, uanset filformat (herunder .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Vis ikke denne advarsel igen"
},
"unknown-pack-warning-modal.header": {
"message": "Bekræft installation"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Installer alligevel"
},
"unknown-pack-warning-modal.warning.title": {
"message": "Ukendt fil advarsel"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Durchsuche {count} Welten..."
},
"app.modal.install-to-play.content-required": {
"message": "Inhalte benötigt"
},
"app.modal.install-to-play.header": {
"message": "Installieren zum Spielen"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# Mod} other {# Mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Benötigtes Modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Dieser Server benötigt Mods zum spielen. Klicke auf Installieren um die nötigen Dateien von Modrinth herunterzuladen und dannach direkt dem Server beizutreten."
},
"app.modal.install-to-play.shared-instance": {
"message": "Geteilte Instanz"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Geteilte Server Instanz"
},
"app.modal.install-to-play.view-contents": {
"message": "Inhalte ansehen"
},
"app.modal.update-to-play.header": {
"message": "Aktualisieren zum Spielen"
},
"app.modal.update-to-play.update-required": {
"message": "Aktualisierung benötigt"
},
"app.modal.update-to-play.update-required-description": {
"message": "Eine aktualisierung zum spielen von {name} ist benötigt. Bitte aktualisiere auf die neuste Version um das Spiel zu starten."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader wird vom Server bereitgestellt"
},
"unknown-pack-warning-modal.body": {
"message": "Eine Datei wird nur geprüft, wenn sie auf Modrinth hochgeladen wird, unabhängig von ihrem Dateiformat (auch .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Diese Warnung nicht mehr anzeigen"
},
"unknown-pack-warning-modal.header": {
"message": "Installation bestätigen"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Trotzdem installieren"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Schadsoftware wird häufig über Modpack-Dateien verbreitet, indem diese auf Plattformen wie Discord geteilt werden."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Wir konnten diese Datei auf Modrinth nicht finden. Wir empfehlen dringend, nur Dateien aus vertrauenswürdigen Quellen zu installieren."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Warnung vor unbekannter Datei"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Durchsuche {count} Welten..."
},
"app.modal.install-to-play.content-required": {
"message": "Inhalte benötigt"
},
"app.modal.install-to-play.header": {
"message": "Installieren zum Spielen"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# Mod} other {# Mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Benötigtes Modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Dieser Server benötigt Mods zum Spielen. Klicke auf Installieren um die nötigen Dateien von Modrinth herunterzuladen und danach direkt dem Server beizutreten."
},
"app.modal.install-to-play.shared-instance": {
"message": "Geteilte Instanz"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Geteilte Serverinstanz"
},
"app.modal.install-to-play.view-contents": {
"message": "Inhalte ansehen"
},
"app.modal.update-to-play.header": {
"message": "Aktualisieren zum Spielen"
},
"app.modal.update-to-play.update-required": {
"message": "Aktualisierung erforderlich"
},
"app.modal.update-to-play.update-required-description": {
"message": "Zum Spielen von {name} ist eine Aktualisierung erforderlich. Bitte aktualisiere auf die neueste Version, um das Spiel zu starten."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader vom Server vorgegeben"
},
"unknown-pack-warning-modal.body": {
"message": "Eine Datei wird nur geprüft, wenn sie auf Modrinth hochgeladen wird, unabhängig von ihrem Dateiformat (auch .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Diese Warnung nicht mehr anzeigen"
},
"unknown-pack-warning-modal.header": {
"message": "Installation bestätigen"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Trotzdem installieren"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Schadsoftware wird häufig über Modpack-Dateien verbreitet, indem diese auf Plattformen wie Discord geteilt werden."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Wir konnten diese Datei auf Modrinth nicht finden. Wir empfehlen dringend, nur Dateien aus vertrauenswürdigen Quellen zu installieren."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Warnung vor unbekannter Datei"
}
}
+20 -32
View File
@@ -425,29 +425,38 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Search {count} worlds..."
},
"app.modal.install-to-play.content-required": {
"message": "Content required"
"app.modal.install-to-play.dont-install": {
"message": "Dont install"
},
"app.modal.install-to-play.header": {
"message": "Install to play"
},
"app.modal.install-to-play.install-anyway": {
"message": "Install anyway"
},
"app.modal.install-to-play.install-button": {
"message": "Install"
},
"app.modal.install-to-play.invite-warning": {
"message": "This invite was created by another Modrinth user, not Modrinth. Only accept invites from people you trust."
},
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Required modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "This server requires mods to play. Click Install to set up the required files from Modrinth, then launch directly into the server."
"app.modal.install-to-play.reviewed-files": {
"message": "A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack)."
},
"app.modal.install-to-play.shared-instance": {
"message": "Shared instance"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Shared server instance"
"app.modal.install-to-play.unknown-files-description": {
"message": "This server modpack contains files that arent published on Modrinth. We strongly recommend only installing files from sources you trust."
},
"app.modal.install-to-play.unknown-files-warning": {
"message": "Unknown files warning"
},
"app.modal.install-to-play.unrecognized-files": {
"message": "Unrecognized files"
},
"app.modal.install-to-play.view-contents": {
"message": "View contents"
@@ -455,8 +464,8 @@
"app.modal.update-to-play.header": {
"message": "Update to play"
},
"app.modal.update-to-play.update-required": {
"message": "Update required"
"app.modal.update-to-play.removed": {
"message": "Removed"
},
"app.modal.update-to-play.update-required-description": {
"message": "An update is required to play {name}. Please update to the latest version to launch the game."
@@ -1102,26 +1111,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader is provided by the server"
},
"unknown-pack-warning-modal.body": {
"message": "A file is only reviewed if its uploaded to Modrinth, regardless of its file format (including .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Don't show this warning again"
},
"unknown-pack-warning-modal.header": {
"message": "Confirm installation"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Install anyway"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Malware is often distributed through modpack files by sharing them on platforms like Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "We couldn't find this file on Modrinth. We strongly recommend only installing files from sources you trust."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Unknown file warning"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar en {count} mundos..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenido requerido"
},
"app.modal.install-to-play.header": {
"message": "Instalar para jugar"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpack requerido"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Este servidor requiere mods para poder jugar. Haz clic en Instalar para configurar los archivos requeridos desde Modrinth, después se ejecutará para entrar directamente al servidor."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instancia compartida"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instancia de servidor compartida"
},
"app.modal.install-to-play.view-contents": {
"message": "Ver contenidos"
},
"app.modal.update-to-play.header": {
"message": "Actualizar para jugar"
},
"app.modal.update-to-play.update-required": {
"message": "Actualización requerida"
},
"app.modal.update-to-play.update-required-description": {
"message": "Se requiere una actualización para jugar {name}. Por favor, actualiza a la versión más reciente para iniciar el juego."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "El loader es proporcionado por el servidor"
},
"unknown-pack-warning-modal.body": {
"message": "Un archivo solo es revisado si es subido a Modrinth, independientemente de su formato de archivo (incluyendo .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "No volver a mostrar esta advertencia"
},
"unknown-pack-warning-modal.header": {
"message": "Confirmar instalación"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Instalar de todos modos"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "El malware a menudo es distribuido mediante modpacks compartidos en aplicaciones como Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "No pudimos encontrar este archivo en Modrinth. Recomendamos solo instalar archivos de sitios de confianza."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Advertencia de archivo desconocido"
}
}
@@ -269,9 +269,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar {count} mundos..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenido obligatorio"
},
"app.modal.install-to-play.header": {
"message": "Instala para jugar"
},
@@ -281,27 +278,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpack requerido"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Este servidor requiere ciertos mods. Pulsa Instalar para instalar los archivos requeridos de Modrinth y luego el launcher te enviara directo al servidor."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instancia compartida"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instancia de servidor compartida"
},
"app.modal.install-to-play.view-contents": {
"message": "Ver contenido"
},
"app.modal.update-to-play.header": {
"message": "Actualiza para jugar"
},
"app.modal.update-to-play.update-required": {
"message": "Actualización requerida"
},
"app.modal.update-to-play.update-required-description": {
"message": "Una actualización es requerida para jugar {name}. Por favor actualízala a la versión más reciente para ejecutar el juego."
},
@@ -925,26 +910,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader proporcionado por el servidor"
},
"unknown-pack-warning-modal.body": {
"message": "Un archivo solo es revisado si es subido a Modrinth, independientemente de su formato de archivo (incluyendo .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "No mostrar esta advertencia otra vez"
},
"unknown-pack-warning-modal.header": {
"message": "Confirmar instalación"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Instalar de todos modos"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "El malware es normalmente distribuido por archivos de modpack que son normalmente compartidas en aplicaciones como Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "No pudimos encontrar este archivo en Modrinth. Recomendamos solo instalar archivos de sitios de confianza."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Advertencia de archivo desconocido"
}
}
@@ -350,9 +350,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hae {count} maailmasta..."
},
"app.modal.install-to-play.content-required": {
"message": "Sisältö vaaditaan"
},
"app.modal.install-to-play.header": {
"message": "Asenna pelataksesi"
},
@@ -362,27 +359,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# modia}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Vaadittu modipaketti"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Tämä palvelin vaatii modeja toimiakseen. Klikkaa Asenna ladataksesi vaaditut tiedostot Modrinthista, ja käynnistä peli suoraan palvelimelle."
},
"app.modal.install-to-play.shared-instance": {
"message": "Jaettu instanssi"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Jaettu palvelininstanssi"
},
"app.modal.install-to-play.view-contents": {
"message": "Tarkastele sisältöä"
},
"app.modal.update-to-play.header": {
"message": "Päivitä pelataksesi"
},
"app.modal.update-to-play.update-required": {
"message": "Päivitys vaaditaan"
},
"app.modal.update-to-play.update-required-description": {
"message": "Pävitys vaaditaan pelataksesi {name}. Päivitä viimeisimpään versioon pelataksesi."
},
@@ -1009,26 +994,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Modialusta on palvelimen tarjoama"
},
"unknown-pack-warning-modal.body": {
"message": "Tiedosto tarkistetaan vain jos se on ladattu Modrinthiin, riippumatta tiedoston muodosta (mukaanlukien .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Älä näytä tätä varoitusta uudestaan"
},
"unknown-pack-warning-modal.header": {
"message": "Vahvista asennus"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Asenna jokatapauksessa"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Haittaohjelmia levitetään usein modipaketti tiedostojen kautta jakamalla niitä alustoilla kuten Discordissa."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Emme löytäneet tätä tiedostoa Modrinthista. Suosittelemme vahvasti että asennat tiedostoja vain lähteistä joihin luotat."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Tuntematon tiedosto varoitus"
}
}
@@ -242,9 +242,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Hanapin sa {count} mundo..."
},
"app.modal.install-to-play.content-required": {
"message": "Nangangailangan ng kontento"
},
"app.modal.install-to-play.header": {
"message": "Mag-install upang malaro"
},
@@ -254,27 +251,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# na mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Kinailangan na modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Ang server rna ito ay nangangailangan ng mga mod upang makalaro. Pindutin ang install upang maihanda ang mga kinakailangang file galing sa Modrinth, matapos ay ilunsad nang diretso sa server."
},
"app.modal.install-to-play.shared-instance": {
"message": "Binahaging instansiya"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Binahaging instansiyang pang-server"
},
"app.modal.install-to-play.view-contents": {
"message": "Tingnan ang mga kontento"
},
"app.modal.update-to-play.header": {
"message": "Mag-update upang malaro"
},
"app.modal.update-to-play.update-required": {
"message": "Kailangang mag-update"
},
"app.modal.update-to-play.update-required-description": {
"message": "Kailangang mag-update upang malaro ang {name}. Mangyaring mag-update sa pinakabagong bersiyon upang ma-launch ang laro."
},
@@ -736,26 +721,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Ang loader ay handog na ng server"
},
"unknown-pack-warning-modal.body": {
"message": "Ang file ay nasusuri lamang kapag ito ay na-upload sa Modrinth, walang pili sa file format nito (kabilang na ang .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Huwag ipakita ang babalang ito ulit"
},
"unknown-pack-warning-modal.header": {
"message": "Kumpirmahin ang installation"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "I-install pa rin"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Ang malware ay madalas naidadala sa mga modpack files sa pamamagitan ng pag-bigay ng mga ito sa mga platforms kagaya ng Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Hindi namin mahanap ang file na ito sa Modrinth. Mahalagang mag-install ka lamang ng files galing sa mga mapagkakatiwalang sources."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Hindi kilalang file"
}
}
@@ -401,9 +401,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Rechercher {count} mondes..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenu requis"
},
"app.modal.install-to-play.header": {
"message": "Installer pour jouer"
},
@@ -413,27 +410,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpack requis"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Ce serveur a besoin de mods pour jouer. Cliquez sur Installer pour mettre en place les fichiers requis depuis Modrinth, puis lancez directement dans le serveur."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instance partagée"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instance serveur partagée"
},
"app.modal.install-to-play.view-contents": {
"message": "Voir le contenu"
},
"app.modal.update-to-play.header": {
"message": "Mettre à jour pour jouer"
},
"app.modal.update-to-play.update-required": {
"message": "Mise à jour requise"
},
"app.modal.update-to-play.update-required-description": {
"message": "Une mise à jour est requise pour jouer à {name}. Veuillez mettre à jour à la dernière version pour lancer le jeu."
},
@@ -1075,26 +1060,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Le loader est procuré par le serveur"
},
"unknown-pack-warning-modal.body": {
"message": "Un fichier nest révisé que sil est téléchargé sur Modrinth, quel que soit son format de fichier (y compris .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Ne plus m'avertir à ce sujet"
},
"unknown-pack-warning-modal.header": {
"message": "Confirmer l'installation"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Installer tout de même"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Les logiciels malveillants sont souvent distribués via des fichiers modpack en les partageant sur des plateformes comme Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Nous ne pouvions pas trouver ce fichier sur Modrinth. Nous vous recommandons fortement de n'installer que des fichiers de sources de confiance."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Avertissement fichier inconnu"
}
}
@@ -137,9 +137,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "חיפוש ב-{count} עולמות..."
},
"app.modal.install-to-play.content-required": {
"message": "תוכן נדרש"
},
"app.modal.install-to-play.header": {
"message": "צריך להתקין כדי לשחק"
},
@@ -149,27 +146,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {מוד אחד} other {# מודים}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "חבילת מודים נדרשת"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "שרת זה דורש מודים כדי לשחק. לחץ על \"התקן\" כדי להגדיר את הקבצים הנדרשים מ-Modrinth, ולאחר מכן הפעל ישירות לשרת."
},
"app.modal.install-to-play.shared-instance": {
"message": "התקנה משותפת"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "התקנת שרת משותפת"
},
"app.modal.install-to-play.view-contents": {
"message": "הצג תוכן"
},
"app.modal.update-to-play.header": {
"message": "צריך לעדכן כדי לשחק"
},
"app.modal.update-to-play.update-required": {
"message": "עדכון נדרש"
},
"app.modal.update-to-play.update-required-description": {
"message": "עדכון נדרש כדי לשחק ב{name}. ניתן להתחיל את המשחק רק לאחר עדכון לגרסה החדשה."
},
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Keresés {count} világ között..."
},
"app.modal.install-to-play.content-required": {
"message": "Szükséges tartalom"
},
"app.modal.install-to-play.header": {
"message": "Telepítés a játékhoz"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count} mod"
},
"app.modal.install-to-play.required-modpack": {
"message": "Szükséges modcsomag"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Ehhez a szerverhez modok szükségesek a játékhoz. Kattints a Telepítés gombra, hogy telepítsd a szükséges fájlokat a Modrinth-ról, majd indítsd el közvetlenül a szervert."
},
"app.modal.install-to-play.shared-instance": {
"message": "Megosztott profil"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Megosztott szerverpéldány"
},
"app.modal.install-to-play.view-contents": {
"message": "Tartalom megtekintése"
},
"app.modal.update-to-play.header": {
"message": "Frissítsd a játékhoz"
},
"app.modal.update-to-play.update-required": {
"message": "Frissítés szükséges"
},
"app.modal.update-to-play.update-required-description": {
"message": "Frissítés szükséges ehhez: {name}. Kérlek, frissíts a legújabb verzióra a játék elindításához."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "A betöltő a szerver által van megadva"
},
"unknown-pack-warning-modal.body": {
"message": "Egy fájlt csak akkor vizsgálunk meg, ha azt feltöltik a Modrinthra, függetlenül a fájlformátumtól (beleértve a .mrpack formátumot is)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Ne mutasd újra ezt a figyelmeztetést"
},
"unknown-pack-warning-modal.header": {
"message": "Telepítés megerősítése"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Letöltés mindenképpen"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "A rosszindulatú programokat gyakran modcsomag-fájlokon keresztül terjesztik, például a Discordhoz hasonló platformokon történő megosztás révén."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Ezt a fájlt nem találtuk meg a Modrinthon. Határozottan javasoljuk, hogy kizárólag megbízható forrásokból származó fájlokat telepíts."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Figyelmeztetés ismeretlen fájlról"
}
}
@@ -245,9 +245,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cari {count} dunia..."
},
"app.modal.install-to-play.content-required": {
"message": "Konten diperlukan"
},
"app.modal.install-to-play.header": {
"message": "Pasang untuk memainkan"
},
@@ -257,27 +254,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Paket mod yang diperlukan"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Mod diperlukan untuk bermain di server ini. Klik pasang untuk menyiapkan berkas-berkas yang diperlukan dari Modrinth, kemudian luncurkan langsung sari server."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instans terbagi"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instans server terbagi"
},
"app.modal.install-to-play.view-contents": {
"message": "Lihat konten"
},
"app.modal.update-to-play.header": {
"message": "Perbarui untuk memainkan"
},
"app.modal.update-to-play.update-required": {
"message": "Perlu diperbarui"
},
"app.modal.update-to-play.update-required-description": {
"message": "{name} perlu diperbarui sebelum dimainkan. Mohon perbarui ke versi terkini untuk meluncurkan permainan."
},
@@ -901,26 +886,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Pemuat disediakan oleh server"
},
"unknown-pack-warning-modal.body": {
"message": "Berkas hanya akan ditinjau bila ia diunggah ke Modrinth, tak peduli format berkasnya (termasuk .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Jangan tampilkan peringatan ini lagi"
},
"unknown-pack-warning-modal.header": {
"message": "Konfirmasi pemasangan"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Tetap pasang"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Program jahat (malware) dibagikan melalui berkas paket mod dengan membagikannya melalui platform seperti Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Kami tidak dapat menemukan berkas ini di Modrinth. Kami sangat menyarankan Anda untuk hanya memasang berkas dari sumber-sumber terpercaya."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Peringatan berkas tidak dikenal"
}
}
@@ -422,9 +422,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cerca tra {count} mondi..."
},
"app.modal.install-to-play.content-required": {
"message": "Contenuto richiesto"
},
"app.modal.install-to-play.header": {
"message": "Installa per continuare"
},
@@ -434,27 +431,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count} mod"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pacchetto di mod richiesto"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Questo server richiede alcune mod. Clicca Installa per scaricarle direttamente da Modrinth, poi sarai pronto a giocare."
},
"app.modal.install-to-play.shared-instance": {
"message": "Istanza condivisa"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Istanza del server condivisa"
},
"app.modal.install-to-play.view-contents": {
"message": "Mostra contenuti"
},
"app.modal.update-to-play.header": {
"message": "Aggiorna per continuare"
},
"app.modal.update-to-play.update-required": {
"message": "Aggiornamento richiesto"
},
"app.modal.update-to-play.update-required-description": {
"message": "{name} richiede degli aggiornamenti. Installa l'ultima versione per poter giocare."
},
@@ -1099,26 +1084,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Il loader è determinato dal server"
},
"unknown-pack-warning-modal.body": {
"message": "Solo i file caricati su Modrinth vengono esaminati, qualunque sia il loro formato (.mrpack inclusi)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Non mostrare più questo avviso"
},
"unknown-pack-warning-modal.header": {
"message": "Conferma l'installazione"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Installa comunque"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Spesso i malware vengono nascosti nei pacchetti di mod, poi distribuiti su piattaforme come Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Non è stato possibile trovare questo file su Modrinth. Consigliamo di installare file solo da fonti attendibili."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Tipo di file sconosciuto"
}
}
@@ -278,9 +278,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count} 個のワールドを検索…"
},
"app.modal.install-to-play.content-required": {
"message": "必須コンテンツ"
},
"app.modal.install-to-play.header": {
"message": "インストールしてプレイ"
},
@@ -290,27 +287,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {#個のMod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "必須のModパック"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "このサーバーをプレイするにはModが必要です。インストールをクリックしてModrinthから必要なファイルを設定し、サーバーに接続してください。"
},
"app.modal.install-to-play.shared-instance": {
"message": "共有インスタンス"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "共有サーバーインスタンス"
},
"app.modal.install-to-play.view-contents": {
"message": "コンテンツを見る"
},
"app.modal.update-to-play.header": {
"message": "更新してプレイ"
},
"app.modal.update-to-play.update-required": {
"message": "更新が必要です"
},
"app.modal.update-to-play.update-required-description": {
"message": "{name}をプレイするには更新が必要です。ゲームを起動するには最新版に更新してください。"
},
@@ -811,26 +796,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "ローダーはサーバーによる条件です"
},
"unknown-pack-warning-modal.body": {
"message": "ファイル形式に関わらず、Modrinthにアップロードされたファイルのみが確認されます。(.mrpackを含む)"
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "この警告を次回から表示しない"
},
"unknown-pack-warning-modal.header": {
"message": "インストールの確認"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "インストールを続行"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "一般的にマルウェアは、Discord等のプラットフォーム上でModパックファイルを配布して拡散されます"
},
"unknown-pack-warning-modal.warning.body": {
"message": "このファイルをModrinth上で見つけることができませんでした。信頼できるソースからインストールすることを強くお勧めします。"
},
"unknown-pack-warning-modal.warning.title": {
"message": "不明なファイルの警告"
}
}
@@ -356,9 +356,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count}개의 세계 검색..."
},
"app.modal.install-to-play.content-required": {
"message": "콘텐츠 설치 필요"
},
"app.modal.install-to-play.header": {
"message": "설치하고 플레이"
},
@@ -368,27 +365,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {모드 #개} other {모드 #개}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "필요한 모드팩"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "이 서버를 플레이하려면 모드가 필요합니다. '설치'를 클릭하여 Modrinth에서 필수 파일을 내려받은 후, 서버에 바로 접속하세요."
},
"app.modal.install-to-play.shared-instance": {
"message": "인스턴스 공유됨"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "서버 인스턴스 공유됨"
},
"app.modal.install-to-play.view-contents": {
"message": "구성 요소 보기"
},
"app.modal.update-to-play.header": {
"message": "업데이트하고 플레이"
},
"app.modal.update-to-play.update-required": {
"message": "업데이트 필요"
},
"app.modal.update-to-play.update-required-description": {
"message": "{name}을(를) 플레이하려면 업데이트가 필요합니다. 게임을 실행하려면 최신 버전으로 업데이트해 주세요."
},
@@ -1018,26 +1003,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "로더가 서버에 의해 제공됩니다"
},
"unknown-pack-warning-modal.body": {
"message": "모든 파일은 형식(.mrpack 포함)에 무관하게 Modrinth에 업로드되어야만 검수를 거칩니다."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "이 경고를 다시 표시하지 않음"
},
"unknown-pack-warning-modal.header": {
"message": "설치 확인"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "무시하고 설치"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "악성코드는 흔히 디스코드와 같은 플랫폼을 통해 모드팩 파일을 공유하는 방식으로 유포됩니다."
},
"unknown-pack-warning-modal.warning.body": {
"message": "이 파일을 Modrinth에서 찾을 수 없습니다. 신뢰할 수 있는 출처의 파일만 설치하는 것을 권장합니다."
},
"unknown-pack-warning-modal.warning.title": {
"message": "출처를 알 수 없는 파일 경고"
}
}
@@ -260,9 +260,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Cari {count} dunia..."
},
"app.modal.install-to-play.content-required": {
"message": "Kandungan yang diperlukan"
},
"app.modal.install-to-play.header": {
"message": "Pasang untuk mainkan"
},
@@ -272,27 +269,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pek mod yang diperlukan"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Pelayan ini memerlukan mod untuk dimainkan. Klik Pasang untuk menyediakan fail yang diperlukan daripada Modrinth, kemudian lancarkan permainan terus ke dalam pelayan."
},
"app.modal.install-to-play.shared-instance": {
"message": "Pemasangan yang dikongsi"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Pemasangan pelayan yang dikongsi"
},
"app.modal.install-to-play.view-contents": {
"message": "Lihat kandungan"
},
"app.modal.update-to-play.header": {
"message": "Kemas kini untuk mainkan"
},
"app.modal.update-to-play.update-required": {
"message": "Kemas kini diperlukan"
},
"app.modal.update-to-play.update-required-description": {
"message": "Kemas kini diperlukan untuk memainkan {name}. Sila kemas kini kepada versi terkini untuk melancarkan permainan."
},
@@ -856,26 +841,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Pemuat adalah disediakan oleh pelayan"
},
"unknown-pack-warning-modal.body": {
"message": "Sesuatu fail hanya disemak jika ia dimuat naik ke Modrinth, tanpa mengira format failnya (termasuk .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Jangan tunjukkan amaran ini lagi"
},
"unknown-pack-warning-modal.header": {
"message": "Sahkan pemasangan"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Pasangkan juga"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Perisian hasad sering diedarkan melalui fail pek mod dengan berkongsinya di platform seperti Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Kami tidak dapat menemui fail ini di Modrinth. Kami sangat mengesyorkan anda untuk hanya memasang fail daripada sumber yang anda percayai."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Amaran fail tidak diketahui"
}
}
@@ -245,9 +245,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Zoek werelden"
},
"app.modal.install-to-play.content-required": {
"message": "Content vereist"
},
"app.modal.install-to-play.header": {
"message": "Installeer om te spelen"
},
@@ -257,27 +254,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural,one {# mod}other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Vereist modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Deze server vereist mods om te spelen. Klik op Installeer om de vereiste bestanden van Modrinth in te stellen, en start direct in de server."
},
"app.modal.install-to-play.shared-instance": {
"message": "Gedeelde instantie"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Gedeelde server instantie"
},
"app.modal.install-to-play.view-contents": {
"message": "Toon content"
},
"app.modal.update-to-play.header": {
"message": "Update om te spelen"
},
"app.modal.update-to-play.update-required": {
"message": "Update vereist"
},
"app.modal.update-to-play.update-required-description": {
"message": "Een update is vereist om {name} te spelen. Update naar de laatste versie om het spel te starten."
},
@@ -901,26 +886,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader is gegeven door de server"
},
"unknown-pack-warning-modal.body": {
"message": "Een bestand wordt alleen beoordeeld als het naar Modrinth is geüpload, ongeacht het bestandsformaat (inclusief .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Toon deze waarschuwing niet opnieuw"
},
"unknown-pack-warning-modal.header": {
"message": "Bevestig installatie"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Installeer toch"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Malware wordt vaak verspreid via modpack-bestanden door ze te delen op platforms zoals Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "We konden dit bestand niet vinden op Modrinth. We raden ten zeerste aan om alleen bestanden te installeren van bronnen die u vertrouwt."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Waarschuwing voor onbekend bestand"
}
}
@@ -227,15 +227,9 @@
"app.modal.install-to-play.shared-instance": {
"message": "Delt tilfelle"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Delt servertilfelle"
},
"app.modal.update-to-play.header": {
"message": "Oppdater for å spille"
},
"app.modal.update-to-play.update-required": {
"message": "Krever oppdatering"
},
"app.modal.update-to-play.update-required-description": {
"message": "Ei oppdatering er påkrevd for å spille {name}. Vær så snill å oppdater til den siste versjonen av spillet for å spille det."
},
@@ -422,9 +422,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Szukaj wśród {count} światów..."
},
"app.modal.install-to-play.content-required": {
"message": "Wymagana zawartość"
},
"app.modal.install-to-play.header": {
"message": "Zainstaluj, aby grać"
},
@@ -434,27 +431,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} few {# mody} other {# modów}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Wymagana paczka modów"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Ten serwer wymaga modów, aby na nim grać. Kliknij \"Zainstaluj\" aby otrzymać potrzebne pliki z Modrinth, a potem dołącz bezpośrednio do serwera."
},
"app.modal.install-to-play.shared-instance": {
"message": "Wspólna instancja"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Wspólna instancja serwera"
},
"app.modal.install-to-play.view-contents": {
"message": "Pokaż zawartość"
},
"app.modal.update-to-play.header": {
"message": "Zaktualizuj, by grać"
},
"app.modal.update-to-play.update-required": {
"message": "Wymagana jest aktualizacja"
},
"app.modal.update-to-play.update-required-description": {
"message": "Aktualizacja jest wymagana, aby grać w {name}. Proszę zaktualizować do najnowszej wersji, aby uruchomić grę."
},
@@ -1099,26 +1084,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader jest dostarczony przez serwer"
},
"unknown-pack-warning-modal.body": {
"message": "Plik jest sprawdzony tylko, jeżeli został przesłany na Modrinth, niezależnie od jego formatu (w tym pliki .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Nie pokazuj ponownie tego ostrzeżenia"
},
"unknown-pack-warning-modal.header": {
"message": "Potwierdź instalację"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Instaluj mimo to"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Wirusy są często rozpowszechniane poprzez pliki paczek modów wysyłane na platformach takich jak Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Nie mogliśmy znaleźć tego pliku na Modrinth. Stanowczo zalecamy instalowanie plików tylko ze źródeł, którym ufasz."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Ostrzeżenie o nieznanym pliku"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Buscar {count} mundos..."
},
"app.modal.install-to-play.content-required": {
"message": "Conteúdo necessário"
},
"app.modal.install-to-play.header": {
"message": "Instale para jogar"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, =0 {Nenhum mod} one {# mod} other {# mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pacote de mods necessário"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Este servidor exige mods para jogar. Clique em instalar para configurar os arquivos necessários através do Modrinth, e então iniciar diretamente no servidor."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instância compartilhada"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instância de servidor compartilhada"
},
"app.modal.install-to-play.view-contents": {
"message": "Ver conteúdo"
},
"app.modal.update-to-play.header": {
"message": "Atualize para jogar"
},
"app.modal.update-to-play.update-required": {
"message": "Atualização necessária"
},
"app.modal.update-to-play.update-required-description": {
"message": "Uma atualização é necessária para jogar {name}. Atualize para a versão mais recente para iniciar o jogo."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "O loader é fornecido pelo servidor"
},
"unknown-pack-warning-modal.body": {
"message": "Um arquivo só é revisado se for enviado no Modrinth, independente do formato do arquivo (incluindo .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Não exibir aviso novamente"
},
"unknown-pack-warning-modal.header": {
"message": "Confirmar instalação"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Instalar mesmo assim"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "O malware é distribuído frequentemente através de arquivos de pacote de mods compartilhados em plataformas como Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Não encontramos este arquivo no Modrinth. Nós recomendamos fortemente instalar arquivos apenas de fontes confiáveis."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Aviso de arquivo desconhecido"
}
}
@@ -188,9 +188,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Pesquisar {count} mundos..."
},
"app.modal.install-to-play.content-required": {
"message": "Conteúdo necessário"
},
"app.modal.install-to-play.header": {
"message": "Instala para jogar"
},
@@ -200,27 +197,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {mod} other {mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpack requerido"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Este servidor requer mods para jogares. Clica Instalar para transferir os ficheiros necessários do Modrinth, e então entrar diretamente no servidor."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instância partilhada"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instância de servidor partilhada"
},
"app.modal.install-to-play.view-contents": {
"message": "Ver conteúdo"
},
"app.modal.update-to-play.header": {
"message": "Atualiza para jogar"
},
"app.modal.update-to-play.update-required": {
"message": "Atualização necessária"
},
"app.modal.update-to-play.update-required-description": {
"message": "Uma atualização é necessária para jogar {name}. Por favor atualiza para a versão mais recente para iniciar o jogo."
},
@@ -5,9 +5,6 @@
"app.auth-servers.unreachable.header": {
"message": "Nu se pot accesa serverele de autentificare"
},
"app.modal.install-to-play.content-required": {
"message": "Conținut necesar"
},
"app.modal.install-to-play.header": {
"message": "Instalați pentru a juca"
},
@@ -17,27 +14,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural,one {#mod} other {# moduri}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Pachet de mod necesar"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Acest server necesită modificări pentru a juca. Faceți clic pe Instalare pentru a configura fișierele necesare din Modrinth, apoi lansați direct pe server."
},
"app.modal.install-to-play.shared-instance": {
"message": "Instanță comună"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Instanță de server partajată"
},
"app.modal.install-to-play.view-contents": {
"message": "Vizualizați conținutul"
},
"app.modal.update-to-play.header": {
"message": "Actualizați pentru a juca"
},
"app.modal.update-to-play.update-required": {
"message": "Actualizare necesară"
},
"app.modal.update-to-play.update-required-description": {
"message": "Este necesară o actualizare pentru a juca {name}. Vă rugăm să actualizați la cea mai recentă versiune pentru a lansa jocul."
},
@@ -419,9 +419,6 @@
"app.instance.worlds.remove-server-modal.warning-header": {
"message": "Удаление {name}"
},
"app.modal.install-to-play.content-required": {
"message": "Требуется дополнительный контент"
},
"app.modal.install-to-play.header": {
"message": "Установка перед запуском"
},
@@ -431,27 +428,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# мод} few {# мода} other {# модов}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Необходимая сборка"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Для игры на сервере требуются моды. Установите необходимые файлы с Modrinth, чтобы подключиться."
},
"app.modal.install-to-play.shared-instance": {
"message": "Сборка"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Общая сборка сервера"
},
"app.modal.install-to-play.view-contents": {
"message": "Посмотреть содержимое"
},
"app.modal.update-to-play.header": {
"message": "Обновление перед запуском"
},
"app.modal.update-to-play.update-required": {
"message": "Требуется обновление"
},
"app.modal.update-to-play.update-required-description": {
"message": "Обновите {name} до последней версии, чтобы запустить игру."
},
@@ -1093,26 +1078,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Загрузчик управляется сервером"
},
"unknown-pack-warning-modal.body": {
"message": "Файл проверяется только в том случае, если он загружен на Modrinth, независимо от его формата (включая .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Больше не предупреждать"
},
"unknown-pack-warning-modal.header": {
"message": "Подтверждение установки"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Всё равно установить"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Вредоносное ПО часто распространяется через сборки на таких платформах, как Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Этот файл не найден на Modrinth. Рекомендуется скачивать файлы только из надёжных источников."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Предупреждение о неизвестном файле"
}
}
@@ -371,9 +371,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Pretraži {count} svetova..."
},
"app.modal.install-to-play.content-required": {
"message": "Potreban sadržaj"
},
"app.modal.install-to-play.header": {
"message": "Instaliraj da bi igrao"
},
@@ -383,27 +380,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# modova}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Potreban modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Ovaj server zahteva modove za igranje. Klikni na Instaliraj da bi instalirao potrebne datoteke iz Modrintha, a zatim pokreni direktno na server."
},
"app.modal.install-to-play.shared-instance": {
"message": "Deljena instanca"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Deljena instanca servera"
},
"app.modal.install-to-play.view-contents": {
"message": "Pogledaj sadržaj"
},
"app.modal.update-to-play.header": {
"message": "Ažuriraj da bi igrao"
},
"app.modal.update-to-play.update-required": {
"message": "Potrebno je ažuriranje"
},
"app.modal.update-to-play.update-required-description": {
"message": "Potrebno je ažuriranje da biste igrali {name}. Molimo te da ažuriraš na najnoviju verziju da bi pokrenuo igru."
},
@@ -1048,26 +1033,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Učitavač je obezbeđen od servera"
},
"unknown-pack-warning-modal.body": {
"message": "Datoteka se pregleda samo ako je postavljena na Modrinth, bez obzira na njen format (uključujući .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Ne prikazuj ovo upozorenje ponovo"
},
"unknown-pack-warning-modal.header": {
"message": "Potvrdi instalaciju"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Svejedno instalirati"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Zlonamerni softver se često distribuira putem modpacka datoteka deljenjem na platformama poput Discord-a."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Nismo mogli da pronađemo ovu datoteku na Modrinth-u. Preporučujemo da instaliraš datoteke samo iz izvora kojima veruješ."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Upozorenje nepoznatih datoteka"
}
}
@@ -407,9 +407,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Sök {count} världar..."
},
"app.modal.install-to-play.content-required": {
"message": "Innehåll krävs"
},
"app.modal.install-to-play.header": {
"message": "Installera för att spela"
},
@@ -419,27 +416,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# mod} other {# moddar}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Modpaket som krävs"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Servern kräver moddar för att du ska kunna spela. Klicka på Installera för att sätta upp dem nödvändiga filerna från Modrinth, och starta sedan på servern direkt."
},
"app.modal.install-to-play.shared-instance": {
"message": "Delad instans"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Delad serverinstans"
},
"app.modal.install-to-play.view-contents": {
"message": "Visa innehåll"
},
"app.modal.update-to-play.header": {
"message": "Uppdatera för att spela"
},
"app.modal.update-to-play.update-required": {
"message": "Uppdatering krävs"
},
"app.modal.update-to-play.update-required-description": {
"message": "En uppdatering krävs för att spela {name}. Vänligen uppdatera till senaste version för att starta spelet."
},
@@ -1084,26 +1069,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader tillhandahålls av servern"
},
"unknown-pack-warning-modal.body": {
"message": "En fil granskas bara om den laddas upp till Modrinth, oavsett dess filformat (däribland .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Visa inte denna varning igen"
},
"unknown-pack-warning-modal.header": {
"message": "Bekräfta installation"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Installera ändå"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Skadeprogram distribueras ofta via modpaketfiler genom att dela dem på plattformar som Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Vi kunde inte hitta filen på Modrinth. Vi rekommenderar starkt att endast installera filer från källor du litar på."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Okänd filvarning"
}
}
@@ -245,9 +245,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "ค้าหาโลกทั้งหมด {count} โลก"
},
"app.modal.install-to-play.content-required": {
"message": "เนื้อหาที่จำเป็น"
},
"app.modal.install-to-play.header": {
"message": "ติดตั้งเพื่อเล่น"
},
@@ -257,27 +254,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# ม็อด}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "มอดแพ็กที่จำเป็น"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "เซิร์ฟเวอร์ดังกล่าวจำเป็นต้องใช้ม็อดเพื่อเล่น โปรดติดตั้งและตั้งค่าไฟล์อื่นใดที่จำเป็นจาก Modrinth ก่อน จากนั้นถึงจะสามารถเข้าเล่นเซิร์ฟเวอร์ได้"
},
"app.modal.install-to-play.shared-instance": {
"message": "โปรแกรมที่มีร่วมกัน"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "เซิร์ฟเวอร์ของโปรแกรมที่มีร่วมกัน"
},
"app.modal.install-to-play.view-contents": {
"message": "ดูเนื้อหา"
},
"app.modal.update-to-play.header": {
"message": "อัปเดตเพื่อเล่น"
},
"app.modal.update-to-play.update-required": {
"message": "จำเป็นต้องอัปเดต"
},
"app.modal.update-to-play.update-required-description": {
"message": "จำเป็นต้องอัปเดตเพื่อเล่น {name} กรุณาอัปเดตเป็นเวอร์ชันล่าสุดเพื่อเปิดเกม"
},
@@ -850,26 +835,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "ตัวรันถูกกำหนดโดยเซิร์ฟเวอร์แล้ว"
},
"unknown-pack-warning-modal.body": {
"message": "ไฟล์จะได้รับการตรวจสอบโดยไม่คำนึงถึงประเภทของไฟล์ (รวมทั้ง .mrpack) เมื่อไฟล์ถูกอัปโหลดขึ้น Modrinth"
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "อย่าแสดงคำเตือนนี้อีก"
},
"unknown-pack-warning-modal.header": {
"message": "ยืนยันการติดตั้ง"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "ดำเนินการติดตั้งต่อไป"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "มัลแวร์มักแฝงตัวมากับไฟล์แพ็กม็อดผ่านการแชร์ผ่านแพลตฟอร์มที่ไม่ใช่แพลตฟอร์มเฉพาะ เช่น ดิสคอร์ด"
},
"unknown-pack-warning-modal.warning.body": {
"message": "เราไม่สามารถค้นหาไฟล์ดังกล่าวได้บน Modrinth พวกเราขอแนะนำอย่างมากกว่าควรติดตั้งไฟล์จากแหล่งที่น่าเชื่อถือเท่านั้น"
},
"unknown-pack-warning-modal.warning.title": {
"message": "แจ้งเตือนไฟล์ไม่รู้จัก"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "{count} dünya ara..."
},
"app.modal.install-to-play.content-required": {
"message": "İçerik gerekli"
},
"app.modal.install-to-play.header": {
"message": "Oynamak için yükleyin"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {#mod} other {#mods}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Gerekli mod paketi"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Bu sunucuya girebilmek için modlar gereklidir. Gerekli dosyaları Modrinth üzerinden kurmak için Yükle butonuna tıkla, ardından doğrudan sunucuya başlat."
},
"app.modal.install-to-play.shared-instance": {
"message": "Paylaşılan Kurulum"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Paylaşılan Sunucu Kurulumu"
},
"app.modal.install-to-play.view-contents": {
"message": "İçeriği görüntüle"
},
"app.modal.update-to-play.header": {
"message": "Oynamak için güncelle"
},
"app.modal.update-to-play.update-required": {
"message": "Güncelleme gerekli"
},
"app.modal.update-to-play.update-required-description": {
"message": "{name} oyununu oynamak için güncelleme gereklidir. Oyunu başlatmak için lütfen en son sürüme güncelleyin."
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Yükleyici sunucu tarafından sağlanıyor"
},
"unknown-pack-warning-modal.body": {
"message": "Dosya formatı ne olursa olsun (.mrpack dahil), bir dosya yalnızca Modrinth'e yüklendiğinde denetlenir."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Bu uyarıyı tekrar gösterme"
},
"unknown-pack-warning-modal.header": {
"message": "İndirmeyi onayla"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Yine de indir"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Kötü amaçlı yazılımlar genellikle Discord gibi platformlarda paylaşılan mod paketi dosyaları aracılığıyla yayılır."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Bu dosyayı Modrinth üzerinde bulamadık. Yalnızca güvendiğiniz kaynaklardan gelen dosyaları yüklemenizi şiddetle öneririz."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Bilinmeyen dosya uyarısı"
}
}
@@ -389,9 +389,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Пошук {count} світів…"
},
"app.modal.install-to-play.content-required": {
"message": "Потрібний уміст"
},
"app.modal.install-to-play.header": {
"message": "Установлення для гри"
},
@@ -401,27 +398,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, one {# мод} few {# мода} other {# модів}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Потрібна збірка"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Для гри на цьому сервері потрібні моди. Натисніть «Установити», щоб налаштувати необхідні файли з Modrinth, а потім запустіть безпосередньо на сервері."
},
"app.modal.install-to-play.shared-instance": {
"message": "Профіль"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Серверний профіль"
},
"app.modal.install-to-play.view-contents": {
"message": "Дивитися вміст"
},
"app.modal.update-to-play.header": {
"message": "Оновлення для гри"
},
"app.modal.update-to-play.update-required": {
"message": "Необхідне оновлення"
},
"app.modal.update-to-play.update-required-description": {
"message": "«{name}» потребує оновлення, щоб грати. Будь ласка, оновіть до останньої версії, щоб запустити гру."
},
@@ -1045,26 +1030,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Завантажувач наданий сервером"
},
"unknown-pack-warning-modal.body": {
"message": "Файл перевірятиметься лише, якщо його завантажено на Modrinth, незалежно від його формату (включно з .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Не показувати більше це попередження"
},
"unknown-pack-warning-modal.header": {
"message": "Підтвердити встановлення"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Усе одно встановити"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Шкідливе програмне забезпечення часто поширюють через файли збірок, які публікуються на таких платформах, як Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Ми не змогли знайти цей файл на Modrinth. Ми рекомендуємо встановлювати файли лише з тих джерел яким ви довіряєте."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Попередження про невідомий файл"
}
}
@@ -245,9 +245,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "Tìm kiếm {count} world..."
},
"app.modal.install-to-play.content-required": {
"message": "Nội dung bắt buộc"
},
"app.modal.install-to-play.header": {
"message": "Tải xuống để chơi"
},
@@ -257,27 +254,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# mod}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "Yêu cầu modpack"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "Máy chủ này yêu cầu mod để có thể chơi. Vui lòng ấn vào tải xuống và tải các tệp bắt buộc từ Modrinth và khởi chạy trực tiếp để tham gia máy chủ."
},
"app.modal.install-to-play.shared-instance": {
"message": "Chia sẻ hồ sơ"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "Chia sẻ hồ sơ máy chủ"
},
"app.modal.install-to-play.view-contents": {
"message": "Xem nội dung"
},
"app.modal.update-to-play.header": {
"message": "Cập nhật và bắt đầu chơi"
},
"app.modal.update-to-play.update-required": {
"message": "Yêu cầu cập nhật"
},
"app.modal.update-to-play.update-required-description": {
"message": "Bạn cần cập nhật {name} để có thể chơi. Vui lòng cập nhật lên bản mới nhất để khởi chạy trò chơi."
},
@@ -901,26 +886,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "Loader được cung cấp bởi máy chủ"
},
"unknown-pack-warning-modal.body": {
"message": "Tệp chỉ được xem xét nếu nó được tải lên Modrinth, bất kể định dạng tệp của nó là gì (bao gồm cả .mrpack)."
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "Đừng hiển thị cảnh báo này nữa"
},
"unknown-pack-warning-modal.header": {
"message": "Xác nhận cài đặt"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "Tiếp tục cài đặt"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "Phần mềm độc hại thường được phát tán thông qua các tệp modpack bằng cách chia sẻ chúng trên các nền tảng như Discord."
},
"unknown-pack-warning-modal.warning.body": {
"message": "Chúng tôi không tìm thấy tập tin này trên Modrinth. Chúng tôi đặc biệt khuyên bạn chỉ nên cài đặt các tập tin từ các nguồn đáng tin cậy."
},
"unknown-pack-warning-modal.warning.title": {
"message": "Cảnh báo tệp không xác định"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜索 {count} 个世界……"
},
"app.modal.install-to-play.content-required": {
"message": "需求内容"
},
"app.modal.install-to-play.header": {
"message": "安装以游玩"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count} 个模组"
},
"app.modal.install-to-play.required-modpack": {
"message": "需求整合包"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "此服务器需要安装模组才能游玩。点击安装,从 Modrinth 下载所需文件,然后直接进入服务器。"
},
"app.modal.install-to-play.shared-instance": {
"message": "共享实例"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "共享的服务端实例"
},
"app.modal.install-to-play.view-contents": {
"message": "查看内容"
},
"app.modal.update-to-play.header": {
"message": "更新以游玩"
},
"app.modal.update-to-play.update-required": {
"message": "需要更新"
},
"app.modal.update-to-play.update-required-description": {
"message": "需要更新至最新版本才能运行 {name}。请更新后启动游戏。"
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "加载器由服务器提供"
},
"unknown-pack-warning-modal.body": {
"message": "只有上传到 Modrinth 的文件才会经过审核,无论其文件格式如何(包括 .mrpack)。"
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "不再显示此警告"
},
"unknown-pack-warning-modal.header": {
"message": "确认安装"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "仍然安装"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "恶意软件常常通过整合包文件在 Discord 等平台上分享而传播。"
},
"unknown-pack-warning-modal.warning.body": {
"message": "我们在 Modrinth 上找不到此文件。强烈建议你仅从可信来源安装文件。"
},
"unknown-pack-warning-modal.warning.title": {
"message": "未知文件警告"
}
}
@@ -425,9 +425,6 @@
"app.instance.worlds.search-worlds-placeholder": {
"message": "搜尋 {count} 個世界..."
},
"app.modal.install-to-play.content-required": {
"message": "所需內容"
},
"app.modal.install-to-play.header": {
"message": "安裝以遊玩"
},
@@ -437,27 +434,15 @@
"app.modal.install-to-play.mod-count": {
"message": "{count, plural, other {# 個模組}}"
},
"app.modal.install-to-play.required-modpack": {
"message": "所需模組包"
},
"app.modal.install-to-play.server-requires-mods": {
"message": "這個伺服器需要模組才能遊玩。請點選「安裝」以從 Modrinth 設定所需的檔案,完成後即可直接加入伺服器。"
},
"app.modal.install-to-play.shared-instance": {
"message": "共用實例"
},
"app.modal.install-to-play.shared-server-instance": {
"message": "共用伺服器實例"
},
"app.modal.install-to-play.view-contents": {
"message": "檢視內容"
},
"app.modal.update-to-play.header": {
"message": "更新以遊玩"
},
"app.modal.update-to-play.update-required": {
"message": "需要更新"
},
"app.modal.update-to-play.update-required-description": {
"message": "需要更新才能遊玩「{name}」。請更新至最新版本以啟動遊戲。"
},
@@ -1102,26 +1087,5 @@
},
"search.filter.locked.server-loader.title": {
"message": "載入器由伺服器提供"
},
"unknown-pack-warning-modal.body": {
"message": "只有上傳至 Modrinth 的檔案才會經過審查,無論其檔案格式為何(包含 .mrpack)。"
},
"unknown-pack-warning-modal.dont-show-again": {
"message": "不要再顯示這則警告"
},
"unknown-pack-warning-modal.header": {
"message": "確認安裝"
},
"unknown-pack-warning-modal.install-anyway": {
"message": "仍要安裝"
},
"unknown-pack-warning-modal.malware-statement": {
"message": "惡意軟體經常透過 Discord 等平臺分享模組包檔案來進行傳播。"
},
"unknown-pack-warning-modal.warning.body": {
"message": "我們在 Modrinth 上找不到這個檔案。強烈建議你僅安裝來自信任來源的檔案。"
},
"unknown-pack-warning-modal.warning.title": {
"message": "未知檔案警告"
}
}
+72 -4
View File
@@ -2,6 +2,13 @@
<ReadyTransition :pending="loading">
<ContentPageLayout>
<template #modals>
<UnknownFileWarningModal
ref="unknownFileWarningModal"
mode="mod"
:file-name="unknownFileName"
@cancel="resolveUnknownFileWarning(false)"
@continue="handleUnknownFileContinue"
/>
<ShareModalWrapper
ref="shareModal"
:share-title="formatMessage(messages.shareTitle)"
@@ -87,6 +94,7 @@ import {
provideAppBackup,
provideContentManager,
ReadyTransition,
UnknownFileWarningModal,
useDebugLogger,
useVIntl,
versionChangesGameVersion,
@@ -113,6 +121,7 @@ import {
add_project_from_path,
edit,
get_linked_modpack_content,
is_file_on_modrinth,
list,
remove_project,
switch_project_version_with_dependencies,
@@ -121,10 +130,12 @@ import {
update_managed_modrinth_version,
} from '@/helpers/instance'
import { type InstanceContentData, loadInstanceContentData } from '@/helpers/instance-content'
import { get as getSettings, set as setSettings } from '@/helpers/settings'
import type { CacheBehaviour, GameInstance } from '@/helpers/types'
import { highlightModInInstance } from '@/helpers/utils.js'
import { injectContentInstall } from '@/providers/content-install'
import { useTheming } from '@/store/state'
import type { FeatureFlag } from '@/store/theme'
const messages = defineMessages({
shareTitle: {
@@ -175,6 +186,7 @@ const router = useRouter()
const queryClient = useQueryClient()
const debug = useDebugLogger('Mods:ContentUpdate')
const themeStore = useTheming()
const skipUnknownFileWarningFeatureFlag = 'skip_unknown_pack_warning' as FeatureFlag
const skipNonEssentialWarnings = computed(() =>
themeStore.getFeatureFlag('skip_non_essential_warnings'),
)
@@ -284,6 +296,9 @@ const exportModal = ref(null)
const contentUpdaterModal = ref<InstanceType<typeof ContentUpdaterModal> | null>()
const modpackContentModal = ref<InstanceType<typeof ModpackContentModal> | null>()
const modpackUpdateConfirmModal = ref<InstanceType<typeof ConfirmModpackUpdateModal> | null>()
const unknownFileWarningModal = ref<InstanceType<typeof UnknownFileWarningModal> | null>()
const unknownFileName = ref('')
let resolveUnknownFileConfirmation: ((confirmed: boolean) => void) | null = null
const modpackContentQueryKey = computed(() => ['linkedModpackContent', props.instance.id])
const modpackContentQuery = useQuery({
@@ -482,14 +497,34 @@ async function handleUploadFiles() {
if (!props.instance) return
const files = await open({ multiple: true })
if (!files) return
const addedFiles: string[] = []
const selectedFiles: Array<{ path: string; filename: string }> = []
for (const file of files) {
const path = (file as { path?: string }).path ?? file
const fileName = typeof path === 'string' ? (path.split('/').pop() ?? path) : String(path)
if (typeof path !== 'string') continue
selectedFiles.push({
path,
filename: path.split(/[\\/]/).pop() ?? path,
})
}
const fileRecognition = await Promise.all(
selectedFiles.map(async ({ path }) => {
try {
return await is_file_on_modrinth(path)
} catch {
return true
}
}),
)
const addedFiles: string[] = []
for (const [index, { path, filename }] of selectedFiles.entries()) {
if (!fileRecognition[index] && !(await confirmUnknownFileInstallation(filename))) {
continue
}
try {
await add_project_from_path(props.instance.id, path)
addedFiles.push(fileName)
addedFiles.push(filename)
} catch (e) {
handleError(e as Error)
}
@@ -514,6 +549,39 @@ async function handleUploadFiles() {
}
}
function confirmUnknownFileInstallation(fileName: string) {
if (themeStore.getFeatureFlag(skipUnknownFileWarningFeatureFlag)) {
return Promise.resolve(true)
}
unknownFileName.value = fileName
return new Promise<boolean>((resolve) => {
resolveUnknownFileConfirmation = resolve
void nextTick(() => unknownFileWarningModal.value?.show())
})
}
function resolveUnknownFileWarning(confirmed: boolean) {
const resolve = resolveUnknownFileConfirmation
resolveUnknownFileConfirmation = null
unknownFileName.value = ''
resolve?.(confirmed)
}
async function handleUnknownFileContinue(dontShowAgain: boolean) {
if (dontShowAgain) {
themeStore.featureFlags[skipUnknownFileWarningFeatureFlag] = true
try {
const settings = await getSettings()
settings.feature_flags[skipUnknownFileWarningFeatureFlag] = true
await setSettings(settings)
} catch (error) {
handleError(error as Error)
}
}
resolveUnknownFileWarning(true)
}
async function toggleDisableMod(mod: ContentItem, desiredEnabled?: boolean) {
if (!mod.file_path) return
const operation = beginContentOperation(mod)
@@ -110,7 +110,7 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
}
const preview = await install_get_modpack_preview(location)
if (preview.unknownFile) {
if (preview.unknownFile || preview.externalFilesInModpack.length > 0) {
const splitPath = config.modpackFilePath.value.split(/[\\/]/)
const fileName = splitPath
? splitPath[splitPath.length - 1]
@@ -119,6 +119,7 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
unknownPackWarningModal.value?.show(
() => install_create_modpack_instance(location).then(() => undefined),
fileName,
preview.externalFilesInModpack,
)
} else {
await install_create_modpack_instance(location)
+1
View File
@@ -202,6 +202,7 @@ fn main() {
"instance_install_project_with_dependencies",
"instance_switch_project_version_with_dependencies",
"instance_add_project_from_path",
"instance_is_file_on_modrinth",
"instance_toggle_disable_project",
"instance_remove_project",
"instance_update_managed_modrinth_version",
+6
View File
@@ -41,6 +41,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
instance_install_project_with_dependencies,
instance_switch_project_version_with_dependencies,
instance_add_project_from_path,
instance_is_file_on_modrinth,
instance_toggle_disable_project,
instance_remove_project,
instance_update_managed_modrinth_version,
@@ -633,6 +634,11 @@ pub async fn instance_add_project_from_path(
.await?)
}
#[tauri::command]
pub async fn instance_is_file_on_modrinth(project_path: &Path) -> Result<bool> {
Ok(theseus::instance::is_file_on_modrinth(project_path).await?)
}
#[tauri::command]
pub async fn instance_toggle_disable_project(
instance_id: &str,
@@ -73,6 +73,18 @@ export class LabrinthVersionsV2Module extends AbstractModule {
})
}
public async getVersionFromFileHash(
hash: string,
algorithm: keyof Labrinth.Versions.v2.VersionFileHash,
): Promise<Labrinth.Versions.v2.Version> {
return this.client.request<Labrinth.Versions.v2.Version>(`/version_file/${hash}`, {
api: 'labrinth',
version: 2,
method: 'GET',
params: { algorithm },
})
}
/**
* Get multiple versions by IDs (v2)
*
+1 -1
View File
@@ -26,7 +26,7 @@ pub use self::paths::{get_full_path, get_mod_full_path};
pub use self::projects::{
InstallProjectWithDependenciesRequest, add_project_from_path,
add_project_from_version, install_project_with_dependencies,
remove_project, repair_managed_modrinth,
is_file_on_modrinth, remove_project, repair_managed_modrinth,
switch_project_version_with_dependencies, toggle_disable_project,
update_all_projects, update_managed_modrinth_version, update_project,
};
+16 -1
View File
@@ -1,7 +1,7 @@
use crate::event::emit::{emit_instance, emit_loading, init_loading};
use crate::event::{InstancePayloadType, LoadingBarType};
use crate::state::instances::adapters::sqlite::instance_rows;
use crate::state::{ProjectType, State};
use crate::state::{CacheBehaviour, CachedEntry, ProjectType, State};
use crate::util::fetch;
use modrinth_content_management::{
ContentType, ResolutionPreferences, ResolveContentPlan,
@@ -213,6 +213,21 @@ pub async fn add_project_from_path(
.await
}
#[tracing::instrument]
pub async fn is_file_on_modrinth(path: &Path) -> crate::Result<bool> {
let state = State::get().await?;
let (_, hash) = fetch::sha1_file_async(path).await?;
let files = CachedEntry::get_file_many(
&[&hash],
Some(CacheBehaviour::Bypass),
&state.pool,
&state.api_semaphore,
)
.await?;
Ok(!files.is_empty())
}
#[tracing::instrument]
pub async fn toggle_disable_project(
instance_id: &str,
+10 -2
View File
@@ -113,7 +113,8 @@ pub struct CreatePackInstance {
pub icon: Option<PathBuf>, // the icon for the instance
pub icon_url: Option<String>, // the URL icon for an instance during import
pub link: Option<InstanceLink>,
pub unknown_file: bool, // true when pack file isn't found on Modrinth via hash lookup
pub unknown_file: bool, // true when the mrpack archive isn't found on Modrinth via hash lookup
pub external_files_in_modpack: Vec<String>,
pub skip_install_profile: Option<bool>,
pub no_watch: Option<bool>,
}
@@ -130,6 +131,7 @@ impl Default for CreatePackInstance {
icon_url: None,
link: None,
unknown_file: false,
external_files_in_modpack: Vec::new(),
skip_install_profile: Some(true),
no_watch: Some(false),
}
@@ -149,7 +151,6 @@ pub struct CreatePack {
pub description: CreatePackDescription,
}
// The hash lookup only gates the unknown-pack warning, so avoid a long blocking scan for huge local packs.
const MAX_LOCAL_FILE_HASH_LOOKUP_SIZE: u64 = 1024 * 1024 * 1024;
#[derive(Clone, Debug)]
@@ -214,9 +215,16 @@ pub async fn get_instance_from_pack(
false
};
let external_files_in_modpack =
super::install_mrpack::get_external_files_from_mrpack(
&CreatePackFile::Path(path),
)
.await?;
Ok(CreatePackInstance {
name: file_name,
unknown_file: !is_known_file,
external_files_in_modpack,
..Default::default()
})
}
@@ -23,7 +23,7 @@ use async_zip::base::read::{WithEntry, ZipEntryReader};
use async_zip::tokio::read::fs::ZipFileReader as FsZipFileReader;
use futures::StreamExt;
use path_util::SafeRelativeUtf8UnixPathBuf;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::pin::Pin;
use std::sync::{
@@ -237,6 +237,96 @@ where
Ok((size, hasher.digest().to_string()))
}
pub(crate) async fn get_external_files_from_mrpack(
file: &CreatePackFile,
) -> crate::Result<Vec<String>> {
let mut zip_reader = MrpackZipReader::new(file).await?;
let Some(manifest_idx) =
zip_reader.file().entries().iter().position(|entry| {
matches!(entry.filename().as_str(), Ok("modrinth.index.json"))
})
else {
return Err(crate::Error::from(crate::ErrorKind::InputError(
"No pack manifest found in mrpack".to_string(),
)));
};
let manifest = zip_reader.read_entry_to_string(manifest_idx).await?;
let pack: PackFormat = serde_json::from_str(&manifest)?;
let mut candidates = pack
.files
.into_iter()
.filter_map(|file| {
let path = file.path.as_str();
let hash = file.hashes.get(&PackFileHash::Sha1)?.clone();
let file_name = path.rsplit('/').next()?.to_string();
Some((file_name, hash))
})
.collect::<Vec<_>>();
let override_entries = zip_reader
.file()
.entries()
.iter()
.enumerate()
.filter_map(|(index, entry)| {
let path = entry.filename().as_str().ok()?;
let relative_path = path
.strip_prefix("overrides/")
.or_else(|| path.strip_prefix("client-overrides/"))?;
if path.ends_with('/')
|| ProjectType::get_from_parent_folder(relative_path).is_none()
{
return None;
}
let file_name = relative_path.rsplit('/').next()?.to_string();
Some((index, file_name))
})
.collect::<Vec<_>>();
for (index, file_name) in override_entries {
let (_, hash) = zip_reader.hash_entry(index).await?;
candidates.push((file_name, hash));
}
if candidates.is_empty() {
return Ok(Vec::new());
}
let state = State::get().await?;
let hashes = candidates
.iter()
.map(|(_, hash)| hash.as_str())
.collect::<Vec<_>>();
let recognized_hashes = match CachedEntry::get_file_many(
&hashes,
None,
&state.pool,
&state.api_semaphore,
)
.await
{
Ok(files) => files
.into_iter()
.map(|file| file.hash)
.collect::<HashSet<_>>(),
Err(err) => {
tracing::warn!("Failed to look up files in imported mrpack: {err}");
HashSet::new()
}
};
let mut external_files = candidates
.into_iter()
.filter_map(|(file_name, hash)| {
(!recognized_hashes.contains(&hash)).then_some(file_name)
})
.collect::<Vec<_>>();
external_files.sort();
external_files.dedup();
Ok(external_files)
}
async fn extract_zip_entry<R>(
mut reader: ZipEntryReader<'_, R, WithEntry<'_>>,
path: &Path,
+7 -7
View File
@@ -1,5 +1,5 @@
use {
crate::{Blockchain, FiatAmount, UsdSymbol, WalletDetails},
crate::{Blockchain, FiatAmount, UsdSymbol, WalletDetails},
chrono::{DateTime, Utc},
derive_more::{Deref, Display},
rust_decimal::Decimal,
@@ -133,17 +133,17 @@ pub struct AccountDetails {
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Balance {
#[serde(rename_all = "camelCase")]
Blockchain {
token_symbol: String,
exponent: u32,
Blockchain {
token_symbol: String,
exponent: u32,
#[serde(with = "rust_decimal::serde::str")]
value: Decimal,
blockchain: Blockchain,
},
#[serde(rename_all = "camelCase")]
Fiat {
currency_symbol: UsdSymbol,
exponent: u32,
Fiat {
currency_symbol: UsdSymbol,
exponent: u32,
#[serde(with = "rust_decimal::serde::str")]
value: Decimal,
},
@@ -170,6 +170,7 @@ const props = withDefaults(
header?: string
hideHeader?: boolean
onHide?: () => void
onAfterHide?: () => void
onShow?: () => void
mergeHeader?: boolean
scrollable?: boolean
@@ -196,6 +197,7 @@ const props = withDefaults(
header: undefined,
hideHeader: false,
onHide: () => {},
onAfterHide: () => {},
onShow: () => {},
mergeHeader: false,
// TODO: migrate all modals to use scrollable and remove this prop
@@ -289,6 +291,7 @@ function hide() {
previousFocusEl = null
setTimeout(() => {
open.value = false
nextTick(() => props.onAfterHide?.())
}, 300)
}
@@ -0,0 +1,255 @@
<template>
<NewModal
ref="modal"
:header="formatMessage(messages.header)"
:on-hide="handleHide"
:on-after-hide="handleAfterHide"
max-width="544px"
width="544px"
>
<div class="flex flex-col items-end gap-6">
<Admonition
type="warning"
:header="
formatMessage(
mode === 'modpack' ? messages.modpackWarningTitle : messages.modWarningTitle,
)
"
class="w-full"
>
<span class="font-medium text-contrast">{{ fileName }}</span>
{{
formatMessage(mode === 'modpack' ? messages.modpackWarningBody : messages.modWarningBody)
}}
</Admonition>
<p class="m-0 w-full leading-6 text-primary">
{{ formatMessage(messages.reviewedFiles) }}
</p>
<div v-if="mode === 'modpack'" class="relative w-full">
<div
ref="externalFileTableBody"
class="max-h-[242px] overflow-y-auto rounded-2xl"
@scroll="checkTableScrollState"
>
<Table
:columns="externalFileColumns"
:data="externalFileRows"
row-key="id"
virtualized
:virtual-row-height="48"
class="shadow-sm"
>
<template #cell-name="{ value }">
<span class="block truncate" :title="String(value)">{{ value }}</span>
</template>
</Table>
</div>
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 max-h-0"
enter-to-class="opacity-100 max-h-2"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 max-h-2"
leave-to-class="opacity-0 max-h-0"
>
<div
v-if="showTableTopFade"
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-2 bg-gradient-to-b from-bg-raised to-transparent"
/>
</Transition>
<Transition
enter-active-class="transition-all duration-200 ease-out"
enter-from-class="opacity-0 max-h-0"
enter-to-class="opacity-100 max-h-2"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100 max-h-2"
leave-to-class="opacity-0 max-h-0"
>
<div
v-if="showTableBottomFade"
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-2 bg-gradient-to-t from-bg-raised to-transparent"
/>
</Transition>
</div>
<p class="m-0 w-full font-medium leading-6 text-orange">
{{ formatMessage(messages.malwareWarning) }}
</p>
<Checkbox
v-if="mode === 'mod'"
v-model="dontShowAgain"
class="w-full"
:label="formatMessage(messages.dontShowAgain)"
/>
<div class="flex items-center gap-2">
<ButtonStyled type="transparent" color="orange">
<button type="button" @click="continueInstallation">
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button type="button" @click="cancelInstallation">
<BanIcon aria-hidden="true" />
{{ formatMessage(messages.dontInstall) }}
</button>
</ButtonStyled>
</div>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { BanIcon } from '@modrinth/assets'
import { computed, nextTick, ref, useTemplateRef } from 'vue'
import { defineMessages, useVIntl } from '../../composables/i18n'
import { useScrollIndicator } from '../../composables/scroll-indicator'
import Admonition from '../base/Admonition.vue'
import ButtonStyled from '../base/ButtonStyled.vue'
import Checkbox from '../base/Checkbox.vue'
import Table, { type TableColumn } from '../base/Table.vue'
import NewModal from './NewModal.vue'
const props = withDefaults(
defineProps<{
mode: 'modpack' | 'mod'
fileName: string
externalFilesInModpack?: string[]
}>(),
{
externalFilesInModpack: () => [],
},
)
const emit = defineEmits<{
cancel: []
continue: [dontShowAgain: boolean]
}>()
const { formatMessage } = useVIntl()
const modal = useTemplateRef('modal')
const externalFileTableBody = ref<HTMLElement | null>(null)
const dontShowAgain = ref(false)
let pendingAction: { type: 'cancel' } | { type: 'continue'; dontShowAgain: boolean } | null = null
const {
showTopFade: showTableTopFade,
showBottomFade: showTableBottomFade,
checkScrollState: checkTableScrollState,
forceCheck: forceCheckTableScroll,
} = useScrollIndicator(externalFileTableBody)
const messages = defineMessages({
header: {
id: 'unknown-file-warning-modal.header',
defaultMessage: 'Confirm installation',
},
modpackWarningTitle: {
id: 'unknown-file-warning-modal.modpack-warning-title',
defaultMessage: 'Unknown files warning',
},
modWarningTitle: {
id: 'unknown-file-warning-modal.mod-warning-title',
defaultMessage: 'Unknown file warning',
},
modpackWarningBody: {
id: 'unknown-file-warning-modal.modpack-warning-body',
defaultMessage:
' contains files that arent published on Modrinth. We strongly recommend only installing files from sources you trust.',
},
modWarningBody: {
id: 'unknown-file-warning-modal.mod-warning-body',
defaultMessage:
' isnt published on Modrinth. We strongly recommend only installing files from sources you trust.',
},
reviewedFiles: {
id: 'unknown-file-warning-modal.reviewed-files',
defaultMessage:
'A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack).',
},
unrecognizedFiles: {
id: 'unknown-file-warning-modal.unrecognized-files',
defaultMessage: 'Unrecognized files',
},
malwareWarning: {
id: 'unknown-file-warning-modal.malware-warning',
defaultMessage:
'Malware is often distributed through mod files by sharing them on platforms like Discord.',
},
dontShowAgain: {
id: 'unknown-file-warning-modal.dont-show-again',
defaultMessage: 'Dont show this warning again',
},
installAnyway: {
id: 'unknown-file-warning-modal.install-anyway',
defaultMessage: 'Install anyway',
},
dontInstall: {
id: 'unknown-file-warning-modal.dont-install',
defaultMessage: 'Dont install',
},
})
type ExternalFileColumn = 'name'
type ExternalFileRow = {
id: string
name: string
}
const externalFileColumns = computed<TableColumn<ExternalFileColumn>[]>(() => [
{
key: 'name',
label: formatMessage(messages.unrecognizedFiles),
cellClass: '!h-12',
},
])
const externalFileRows = computed<ExternalFileRow[]>(() =>
props.externalFilesInModpack.map((name, index) => ({
id: `${index}-${name}`,
name,
})),
)
async function show() {
dontShowAgain.value = false
pendingAction = null
modal.value?.show()
await nextTick()
forceCheckTableScroll()
}
function hide() {
modal.value?.hide()
}
function handleHide() {
pendingAction ??= { type: 'cancel' }
}
function handleAfterHide() {
const action = pendingAction
pendingAction = null
dontShowAgain.value = false
if (action?.type === 'continue') {
emit('continue', action.dontShowAgain)
} else {
emit('cancel')
}
}
function cancelInstallation() {
pendingAction = { type: 'cancel' }
modal.value?.hide()
}
function continueInstallation() {
pendingAction = { type: 'continue', dontShowAgain: dontShowAgain.value }
modal.value?.hide()
}
defineExpose({ show, hide })
</script>
@@ -8,4 +8,5 @@ export { default as OpenInAppModal } from './OpenInAppModal.vue'
export { default as ShareModal } from './ShareModal.vue'
export type { Tab as TabbedModalTab } from './TabbedModal.vue'
export { default as TabbedModal } from './TabbedModal.vue'
export { default as UnknownFileWarningModal } from './UnknownFileWarningModal.vue'
export { default as UploadProgressModal } from './UploadProgressModal.vue'
@@ -1,86 +1,134 @@
<template>
<NewModal ref="modal" :header="header" :closable="true" :disable-close="disableClose" no-padding>
<div class="max-w-[500px]">
<div class="flex flex-col gap-4 p-4">
<Admonition :type="hasUnknownContent ? 'warning' : 'info'" :header="admonitionHeader">
<div class="flex flex-col gap-2">
<span>{{ description }}</span>
<span v-if="hasUnknownContent">{{ formatMessage(messages.unknownContentBody) }}</span>
</div>
<NewModal
ref="modal"
:header="header"
:closable="true"
:disable-close="disableClose"
max-width="544px"
width="544px"
no-padding
>
<div class="flex flex-col gap-4" :class="hasExternalDiffs ? 'px-6 py-4' : 'p-4'">
<template v-if="hasExternalDiffs">
<p v-if="description" class="m-0 text-primary">{{ description }}</p>
<Admonition
v-if="hasExternalDiffs"
type="warning"
:header="formatMessage(messages.unknownFilesWarning)"
>
{{ formatMessage(messages.unknownFilesDescription) }}
</Admonition>
</template>
<Admonition v-else :type="hasUnknownContent ? 'warning' : 'info'" :header="admonitionHeader">
<div class="flex flex-col gap-2">
<span>{{ description }}</span>
<span v-if="hasUnknownContent">{{ formatMessage(messages.unknownContentBody) }}</span>
</div>
</Admonition>
<div v-if="diffs.length" class="flex gap-2">
<div v-if="removedCount" class="flex gap-1 items-center">
<MinusIcon />
{{ formatMessage(messages.removedCount, { count: removedCount }) }}
</div>
<div v-if="addedCount" class="flex gap-1 items-center">
<PlusIcon />
{{ formatMessage(messages.addedCount, { count: addedCount }) }}
</div>
<div v-if="updatedCount" class="flex gap-1 items-center">
<RefreshCwIcon />
<div v-if="diffs.length" class="flex flex-col gap-1">
<span v-if="versionDate" class="font-semibold text-contrast">{{ versionDate }}</span>
<div class="flex flex-wrap items-center gap-2 text-primary">
<div v-if="updatedCount" class="flex items-center gap-1">
<RefreshCwIcon class="size-4" />
{{ formatMessage(messages.updatedCount, { count: updatedCount }) }}
</div>
</div>
</div>
<div
v-if="diffs.length"
class="flex flex-col bg-surface-2 p-4 max-h-[272px] overflow-y-auto border-t border-b border-r-0 border-l-0 border-solid border-surface-5"
>
<div
v-for="(diff, index) in sortedDiffs"
:key="diff.projectName || diff.fileName || index"
class="grid items-center min-h-10 h-10 gap-2"
:class="diff.projectName ? 'grid-cols-[auto_auto_1fr]' : 'grid-cols-[auto_auto_1fr]'"
>
<div class="flex flex-col justify-between items-center">
<div class="w-[1px] h-2"></div>
<PlusIcon v-if="diff.type === 'added'" />
<MinusIcon v-else-if="diff.type === 'removed'" class="text-red" />
<RefreshCwIcon v-else />
<div
:class="index === sortedDiffs.length - 1 ? 'bg-transparent' : 'bg-surface-5'"
class="w-[1px] h-2 relative top-1"
></div>
<div v-if="addedCount" class="flex items-center gap-1">
<PlusIcon class="size-4" />
{{ formatMessage(messages.addedCount, { count: addedCount }) }}
</div>
<div v-if="removedCount" class="flex items-center gap-1">
<MinusIcon class="size-4" />
{{ formatMessage(messages.removedCount, { count: removedCount }) }}
</div>
<span class="text-sm shrink-0 whitespace-nowrap">{{
diff.type === 'removed' && props.removedLabel
? props.removedLabel
: formatMessage(diffTypeMessages[diff.type])
}}</span>
<span
v-if="diff.projectName"
class="text-sm text-contrast font-medium whitespace-nowrap overflow-hidden text-ellipsis"
>
{{ diff.projectName }}
</span>
<span
v-else-if="diff.fileName"
class="text-sm text-contrast font-medium whitespace-nowrap overflow-hidden text-ellipsis"
>
{{ decodeURIComponent(diff.fileName) }}
</span>
</div>
</div>
</div>
<div
v-if="diffs.length"
class="flex max-h-[272px] flex-col overflow-y-auto border-0 border-y border-solid border-surface-5 bg-surface-2 px-3 py-4"
>
<div
v-if="showBackupCreator"
class="p-4 border-t border-solid border-surface-5 border-b-0 border-l-0 border-r-0"
v-for="(diff, index) in sortedDiffs"
:key="diff.projectName || diff.fileName || index"
class="flex h-10 min-h-10 items-center gap-2"
:class="showExternalWarning(diff) ? '-mx-3 px-5' : 'px-2'"
:style="
showExternalWarning(diff)
? {
backgroundColor: 'color-mix(in srgb, var(--color-orange) 10%, transparent)',
}
: undefined
"
>
<InlineBackupCreator
ref="backupCreator"
backup-name="Before version change"
hide-shift-click-hint
@update:buttons-disabled="buttonsDisabled = $event"
/>
<div class="relative flex w-4 shrink-0 self-stretch items-center justify-center">
<div
v-if="index > 0"
class="absolute left-1/2 top-0 h-3 w-px -translate-x-1/2 bg-surface-5"
/>
<PlusIcon v-if="diff.type === 'added'" class="relative z-[1] size-4" />
<MinusIcon v-else-if="diff.type === 'removed'" class="relative z-[1] size-4 text-red" />
<RefreshCwIcon v-else class="relative z-[1] size-4" />
<div
v-if="index < sortedDiffs.length - 1"
class="absolute bottom-0 left-1/2 top-7 w-px -translate-x-1/2 bg-surface-5"
/>
</div>
<div class="flex min-w-0 flex-1 items-center gap-1 text-sm">
<span class="shrink-0 whitespace-nowrap text-primary">{{ getDiffTypeLabel(diff) }}</span>
<template v-if="showExternalWarning(diff)">
<CircleAlertIcon class="size-4 shrink-0 text-orange" />
<span class="truncate font-medium text-orange">
{{ formatMessage(messages.unknownProject) }}
</span>
</template>
<span v-else class="truncate font-medium text-contrast">
{{ diff.projectName || (diff.fileName ? decodeURIComponent(diff.fileName) : '') }}
</span>
</div>
<span
v-if="getVersionLabel(diff)"
class="ml-2 max-w-[60%] min-w-0 shrink truncate text-right text-xs"
:class="showExternalWarning(diff) ? 'text-orange' : 'text-primary'"
:title="getVersionLabel(diff)"
>
{{ getVersionLabel(diff) }}
</span>
</div>
</div>
<div
v-if="showBackupCreator"
class="p-4 border-t border-solid border-surface-5 border-b-0 border-l-0 border-r-0"
>
<InlineBackupCreator
ref="backupCreator"
backup-name="Before version change"
hide-shift-click-hint
@update:buttons-disabled="buttonsDisabled = $event"
/>
</div>
<template #actions>
<div class="flex justify-between gap-2 pt-4">
<div v-if="hasExternalDiffs" class="flex flex-col gap-6 p-2">
<p class="m-0 text-primary">{{ formatMessage(messages.reviewedFiles) }}</p>
<div class="flex justify-end gap-2">
<ButtonStyled type="transparent" color="orange">
<button :disabled="buttonsDisabled" @click="handleConfirm">
{{ formatMessage(messages.installAnyway) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button @click="handleCancel">
<BanIcon />
{{ formatMessage(messages.dontInstall) }}
</button>
</ButtonStyled>
</div>
</div>
<div v-else class="flex justify-between gap-2 pt-4">
<div>
<ButtonStyled v-if="showReportButton" color="red" type="transparent">
<button @click="emit('report')">
@@ -109,7 +157,15 @@
</template>
<script setup lang="ts">
import { MinusIcon, PlusIcon, RefreshCwIcon, ReportIcon, XIcon } from '@modrinth/assets'
import {
BanIcon,
CircleAlertIcon,
MinusIcon,
PlusIcon,
RefreshCwIcon,
ReportIcon,
XIcon,
} from '@modrinth/assets'
import { type Component, computed, ref } from 'vue'
import Admonition from '#ui/components/base/Admonition.vue'
@@ -133,6 +189,8 @@ const props = defineProps<{
showBackupCreator?: boolean
removedLabel?: string
disableClose?: boolean
showExternalWarnings?: boolean
versionDate?: string
}>()
const emit = defineEmits<{
@@ -150,14 +208,34 @@ const buttonsDisabled = ref(false)
const removedCount = computed(() => props.diffs.filter((d) => d.type === 'removed').length)
const addedCount = computed(() => props.diffs.filter((d) => d.type === 'added').length)
const updatedCount = computed(() => props.diffs.filter((d) => d.type === 'updated').length)
const hasExternalDiffs = computed(() => props.diffs.some(showExternalWarning))
const sortedDiffs = computed(() =>
[...props.diffs].sort((a, b) => {
const aExternal = showExternalWarning(a)
const bExternal = showExternalWarning(b)
if (aExternal !== bExternal) return aExternal ? -1 : 1
const typeOrder = { added: 0, updated: 1, removed: 2 }
return typeOrder[a.type] - typeOrder[b.type]
}),
)
function getDiffTypeLabel(diff: ContentDiffItem) {
if (showExternalWarning(diff)) return formatMessage(externalDiffTypeMessages[diff.type])
if (diff.type === 'removed' && props.removedLabel) return props.removedLabel
return formatMessage(diffTypeMessages[diff.type])
}
function getVersionLabel(diff: ContentDiffItem) {
if (showExternalWarning(diff) && diff.fileName) return decodeURIComponent(diff.fileName)
return diff.type === 'removed' ? diff.currentVersionName : diff.newVersionName
}
function showExternalWarning(diff: ContentDiffItem) {
return Boolean(props.showExternalWarnings && diff.external && diff.type !== 'removed')
}
function show(e?: MouseEvent) {
modal.value?.show(e)
}
@@ -194,6 +272,32 @@ const messages = defineMessages({
defaultMessage:
'Some content on your server could not be analyzed and may be affected by this change.',
},
unknownFilesWarning: {
id: 'content.diff-modal.unknown-files-warning',
defaultMessage: 'Unknown files warning',
},
unknownFilesDescription: {
id: 'content.diff-modal.unknown-files-description',
defaultMessage:
'This update contains files that arent published on Modrinth. We strongly recommend only installing files from sources you trust.',
},
unknownProject: {
id: 'content.diff-modal.unknown-project',
defaultMessage: 'Unknown',
},
reviewedFiles: {
id: 'content.diff-modal.reviewed-files',
defaultMessage:
'A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack).',
},
installAnyway: {
id: 'content.diff-modal.install-anyway',
defaultMessage: 'Install anyway',
},
dontInstall: {
id: 'content.diff-modal.dont-install',
defaultMessage: "Don't install",
},
})
const diffTypeMessages = defineMessages({
@@ -211,5 +315,20 @@ const diffTypeMessages = defineMessages({
},
})
const externalDiffTypeMessages = defineMessages({
added: {
id: 'content.diff-modal.external-diff-type.added',
defaultMessage: 'Added',
},
removed: {
id: 'content.diff-modal.external-diff-type.removed',
defaultMessage: 'Removed',
},
updated: {
id: 'content.diff-modal.external-diff-type.updated',
defaultMessage: 'Updated',
},
})
defineExpose({ show, hide })
</script>
@@ -37,6 +37,7 @@ export interface LoaderVersionEntry {
export interface ContentDiffItem {
type: 'added' | 'removed' | 'updated'
external?: boolean
projectName?: string
fileName?: string
currentVersionName?: string
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { Archon, Labrinth } from '@modrinth/api-client'
import { type Archon, type Labrinth, ModrinthApiError } from '@modrinth/api-client'
import { ClipboardCopyIcon } from '@modrinth/assets'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { useIntervalFn } from '@vueuse/core'
@@ -7,6 +7,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import ReadyTransition from '#ui/components/base/ReadyTransition.vue'
import UnknownFileWarningModal from '#ui/components/modal/UnknownFileWarningModal.vue'
import { useUploadSessionUpload } from '#ui/composables/hosting/kyros-session-upload'
import { defineMessages, useVIntl } from '#ui/composables/i18n'
import { useServerPermissions } from '#ui/composables/server-permissions'
@@ -124,6 +125,10 @@ const contentUploadSession = useUploadSessionUpload({
uploadState,
cancelUpload,
})
const unknownFileWarningModal = ref<InstanceType<typeof UnknownFileWarningModal> | null>()
const unknownFileName = ref('')
let resolveUnknownFileConfirmation: ((confirmed: boolean) => void) | null = null
const skipUnknownFileWarningKey = 'hosting-skip-unknown-file-warning'
const { addNotification } = injectNotificationManager()
const { openServerSettings, browseServerContent } = injectServerSettingsModal()
const { canSetup, permissionDeniedMessage } = useServerPermissions()
@@ -932,8 +937,18 @@ function handleUploadFiles() {
if (!wid) return
try {
const fileRecognition = await Promise.all(files.map(isFileOnModrinth))
const unrecognizedFileSet = new Set(files.filter((_, index) => !fileRecognition[index]))
const confirmedFiles: File[] = []
for (const file of files) {
if (!unrecognizedFileSet.has(file) || (await confirmUnknownFileInstallation(file.name))) {
confirmedFiles.push(file)
}
}
if (confirmedFiles.length === 0) return
const result = await contentUploadSession.uploadFiles(
files.map((file) => ({ file, filename: file.name })),
confirmedFiles.map((file) => ({ file, filename: file.name })),
)
if (result === 'completed') await contentQuery.refetch()
} catch (err) {
@@ -947,6 +962,45 @@ function handleUploadFiles() {
input.click()
}
async function isFileOnModrinth(file: File) {
const buffer = await file.arrayBuffer()
const digest = await crypto.subtle.digest('SHA-1', buffer)
const hash = Array.from(new Uint8Array(digest), (byte) =>
byte.toString(16).padStart(2, '0'),
).join('')
try {
await client.labrinth.versions_v2.getVersionFromFileHash(hash, 'sha1')
return true
} catch (error) {
return !(error instanceof ModrinthApiError && error.statusCode === 404)
}
}
function confirmUnknownFileInstallation(fileName: string) {
if (localStorage.getItem(skipUnknownFileWarningKey) === 'true') {
return Promise.resolve(true)
}
unknownFileName.value = fileName
return new Promise<boolean>((resolve) => {
resolveUnknownFileConfirmation = resolve
void nextTick(() => unknownFileWarningModal.value?.show())
})
}
function resolveUnknownFileWarning(confirmed: boolean) {
const resolve = resolveUnknownFileConfirmation
resolveUnknownFileConfirmation = null
unknownFileName.value = ''
resolve?.(confirmed)
}
function handleUnknownFileContinue(dontShowAgain: boolean) {
if (dontShowAgain) localStorage.setItem(skipUnknownFileWarningKey, 'true')
resolveUnknownFileWarning(true)
}
function addonToContentItem(addon: AddonWithUiState): ContentItem {
return {
project: {
@@ -1380,6 +1434,13 @@ provideContentManager({
<ReadyTransition :pending="contentReadyPending">
<ContentPageLayout :bottom-padding="false">
<template #modals>
<UnknownFileWarningModal
ref="unknownFileWarningModal"
mode="mod"
:file-name="unknownFileName"
@cancel="resolveUnknownFileWarning(false)"
@continue="handleUnknownFileContinue"
/>
<ConfirmUnlinkModal
ref="modpackUnlinkModal"
server
+60
View File
@@ -455,12 +455,39 @@
"content.diff-modal.diff-type.updated": {
"defaultMessage": "Updated"
},
"content.diff-modal.dont-install": {
"defaultMessage": "Don't install"
},
"content.diff-modal.external-diff-type.added": {
"defaultMessage": "Added"
},
"content.diff-modal.external-diff-type.removed": {
"defaultMessage": "Removed"
},
"content.diff-modal.external-diff-type.updated": {
"defaultMessage": "Updated"
},
"content.diff-modal.install-anyway": {
"defaultMessage": "Install anyway"
},
"content.diff-modal.removed-count": {
"defaultMessage": "{count} removed"
},
"content.diff-modal.reviewed-files": {
"defaultMessage": "A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack)."
},
"content.diff-modal.unknown-content-body": {
"defaultMessage": "Some content on your server could not be analyzed and may be affected by this change."
},
"content.diff-modal.unknown-files-description": {
"defaultMessage": "This update contains files that arent published on Modrinth. We strongly recommend only installing files from sources you trust."
},
"content.diff-modal.unknown-files-warning": {
"defaultMessage": "Unknown files warning"
},
"content.diff-modal.unknown-project": {
"defaultMessage": "Unknown"
},
"content.diff-modal.updated-count": {
"defaultMessage": "{count} updated"
},
@@ -5555,6 +5582,39 @@
"ui.stacked-admonitions.dismiss-all": {
"defaultMessage": "Dismiss all"
},
"unknown-file-warning-modal.dont-install": {
"defaultMessage": "Dont install"
},
"unknown-file-warning-modal.dont-show-again": {
"defaultMessage": "Dont show this warning again"
},
"unknown-file-warning-modal.header": {
"defaultMessage": "Confirm installation"
},
"unknown-file-warning-modal.install-anyway": {
"defaultMessage": "Install anyway"
},
"unknown-file-warning-modal.malware-warning": {
"defaultMessage": "Malware is often distributed through mod files by sharing them on platforms like Discord."
},
"unknown-file-warning-modal.mod-warning-body": {
"defaultMessage": " isnt published on Modrinth. We strongly recommend only installing files from sources you trust."
},
"unknown-file-warning-modal.mod-warning-title": {
"defaultMessage": "Unknown file warning"
},
"unknown-file-warning-modal.modpack-warning-body": {
"defaultMessage": " contains files that arent published on Modrinth. We strongly recommend only installing files from sources you trust."
},
"unknown-file-warning-modal.modpack-warning-title": {
"defaultMessage": "Unknown files warning"
},
"unknown-file-warning-modal.reviewed-files": {
"defaultMessage": "A file is only reviewed if its published to Modrinth, regardless of its file format (including .mrpack)."
},
"unknown-file-warning-modal.unrecognized-files": {
"defaultMessage": "Unrecognized files"
},
"user.profile.badge.alpha.about.1": {
"defaultMessage": "This user has been around since Modrinth Alpha, which ended in November 2020."
},
@@ -0,0 +1,64 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import { ref } from 'vue'
import ButtonStyled from '../../components/base/ButtonStyled.vue'
import UnknownFileWarningModal from '../../components/modal/UnknownFileWarningModal.vue'
const meta = {
title: 'Modal/UnknownFileWarningModal',
component: UnknownFileWarningModal,
parameters: {
layout: 'centered',
},
} satisfies Meta<typeof UnknownFileWarningModal>
export default meta
type Story = StoryObj<typeof meta>
export const Modpack: Story = {
render: () => ({
components: { ButtonStyled, UnknownFileWarningModal },
setup() {
const modalRef = ref<InstanceType<typeof UnknownFileWarningModal> | null>(null)
return { modalRef }
},
template: /* html */ `
<ButtonStyled color="brand">
<button @click="modalRef?.show()">Open modpack warning</button>
</ButtonStyled>
<UnknownFileWarningModal
ref="modalRef"
mode="modpack"
file-name="cozy-cottage-1.4.0.mrpack"
:external-files-in-modpack="[
'voicechat-fabric-1.20.1-2.5.26.jar',
'xaeros-minimap-24.6.1_Fabric_1.20.jar',
'InventoryProfilesNext-forge-1.20.1-1.10.12.jar',
'MouseTweaks-forge-mc1.20.1-2.25.jar',
'Terralith_1.20.x_v2.5.4.jar',
'YungsApi-1.20-Forge-4.0.5.jar',
]"
/>
`,
}),
}
export const Mod: Story = {
render: () => ({
components: { ButtonStyled, UnknownFileWarningModal },
setup() {
const modalRef = ref<InstanceType<typeof UnknownFileWarningModal> | null>(null)
return { modalRef }
},
template: /* html */ `
<ButtonStyled color="brand">
<button @click="modalRef?.show()">Open file warning</button>
</ButtonStyled>
<UnknownFileWarningModal
ref="modalRef"
mode="mod"
file-name="voicechat-fabric-1.20.1-2.5.26.jar"
/>
`,
}),
}
@@ -0,0 +1,91 @@
import { DownloadIcon } from '@modrinth/assets'
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import { ref } from 'vue'
import ButtonStyled from '../../components/base/ButtonStyled.vue'
import ContentDiffModal from '../../layouts/shared/installation-settings/components/ContentDiffModal.vue'
import type { ContentDiffItem } from '../../layouts/shared/installation-settings/types'
const meta = {
title: 'Modal/UpdateToPlayModal',
component: ContentDiffModal,
parameters: {
layout: 'centered',
},
} satisfies Meta<typeof ContentDiffModal>
export default meta
type Story = StoryObj<typeof meta>
const diffs: ContentDiffItem[] = [
{
type: 'added',
external: true,
fileName: 'voicechat-fabric-1.20.1-2.5.26.jar',
},
{
type: 'added',
external: true,
fileName: 'xaeros-minimap-24.6.1_Fabric_1.20.jar',
},
{
type: 'updated',
projectName: 'Cloth Config API',
currentVersionName: '18.0.145+neoforge',
newVersionName: '20.0.149+neoforge',
},
{
type: 'added',
projectName: 'Sodium',
newVersionName: '1.21.10-0.7.3-neoforge',
},
{
type: 'updated',
projectName: 'Iris Shaders',
currentVersionName: '1.8.8+1.21.8-neoforge',
newVersionName: '1.9.6+1.21.10-neoforge',
},
{
type: 'updated',
projectName: 'Entity Culling',
currentVersionName: '1.8.1',
newVersionName: '1.9.3',
},
{
type: 'updated',
projectName: 'FerriteCore',
currentVersionName: '7.0.2',
newVersionName: '8.0.0',
},
{
type: 'removed',
projectName: 'Lithium',
currentVersionName: '0.15.0+mc1.21.8',
},
]
export const ExternalFiles: Story = {
render: () => ({
components: { ButtonStyled, ContentDiffModal },
setup() {
const modalRef = ref<InstanceType<typeof ContentDiffModal> | null>(null)
return { diffs, DownloadIcon, modalRef }
},
template: /* html */ `
<ButtonStyled color="brand">
<button @click="modalRef?.show()">Open update warning</button>
</ButtonStyled>
<ContentDiffModal
ref="modalRef"
header="Update to play"
description="An update is required to play Epic Modrinth Pack. Please update to the latest version to launch the game."
:diffs="diffs"
version-date="November 25, 2025"
show-external-warnings
confirm-label="Update"
:confirm-icon="DownloadIcon"
removed-label="Removed"
/>
`,
}),
}